home-cert-assistant/src/main/java/de/mlessmann/certassist/service/CertificateCreationService.java

172 lines
8.2 KiB
Java

package de.mlessmann.certassist.service;
import de.mlessmann.certassist.except.CommandLineOperationException;
import de.mlessmann.certassist.models.*;
import de.mlessmann.certassist.openssl.*;
import de.mlessmann.certassist.repositories.CertificateRepository;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.StreamSupport;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class CertificateCreationService {
private final CertificateRepository certificateRepository;
private final OpenSSLService openSSLService;
private final PassphraseService passphraseService;
private final Pattern CERT_START_PATTERN = Pattern.compile("-+BEGIN CERTIFICATE-+");
private final Pattern CERT_END_PATTERN = Pattern.compile("-+END CERTIFICATE-+");
public Certificate createCertificate(final CertificateInfo certificateInfo) {
final Certificate certificate = createEntityFromRequest(certificateInfo);
try (OpenSSLCertificateResult certificateCreatorResult = openSSLService.createCertificate(certificateInfo);) {
Path keyPath = certificateCreatorResult.certificateKeyPath();
certificate.setFingerprint(certificateCreatorResult.fingerprint());
if (keyPath != null) {
certificate.setPrivateKey(Files.readAllBytes(keyPath));
}
certificate.setCert(Files.readAllBytes(certificateCreatorResult.certificatePath()));
} catch (CommandLineOperationException | IOException e) {
throw new IllegalStateException("Failed to create certificate!", e);
}
certificateRepository.save(certificate);
return certificate;
}
private Certificate createEntityFromRequest(CertificateInfo certificateInfo) {
final Certificate certificate = new Certificate();
certificate.setType(mapCertificateRequestType(certificateInfo.getType()));
certificate.setSubjectCommonName(certificateInfo.getSubject().getCommonName());
certificate.setTrustingAuthority(certificateInfo.getTrustingAuthority());
certificate.setRequestedKeyLength(certificateInfo.getRequestedKeyLength());
certificate.setNotBefore(OffsetDateTime.now());
certificate.setNotAfter(OffsetDateTime.now().plusDays(certificateInfo.getRequestedValidityDays()));
final CertificateInfoSubject subjectInfo = certificateInfo.getSubject();
certificate.setSubjectEmailAddress(subjectInfo.getEmailAddress());
certificate.setSubjectOrganization(subjectInfo.getOrganization());
certificate.setSubjectOrganizationalUnit(subjectInfo.getOrganizationalUnit());
certificate.setSubjectCountry(subjectInfo.getCountry());
certificate.setSubjectState(subjectInfo.getState());
certificate.setSubjectLocality(subjectInfo.getLocality());
final CertificateInfoExtension extension = certificateInfo.getExtension();
if (extension != null) {
final CertificateExtension certificateExtension = new CertificateExtension();
certificateExtension.setIdentifier("alternativeNames");
certificateExtension.setValue(String.join(",", extension.getAlternativeDnsNames()));
certificate.setCertificateExtension(List.of(certificateExtension));
}
return certificate;
}
private Certificate createEntityFromInfo(X509CertificateInfo info) {
final Certificate certificate = new Certificate();
certificate.setType(
mapCertificateRequestType(
info.issuer() != null
? CertificateInfo.RequestType.NORMAL_CERTIFICATE
: CertificateInfo.RequestType.STANDALONE_CERTIFICATE
)
);
certificate.setSubjectCommonName(info.subject().getCommonName());
certificate.setTrustingAuthority(info.issuer().getCommonName());
certificate.setRequestedKeyLength(-1);
certificate.setNotBefore(info.notBefore());
certificate.setNotAfter(info.notAfter());
final CertificateInfoSubject subjectInfo = info.subject();
certificate.setSubjectEmailAddress(subjectInfo.getEmailAddress());
certificate.setSubjectOrganization(subjectInfo.getOrganization());
certificate.setSubjectOrganizationalUnit(subjectInfo.getOrganizationalUnit());
certificate.setSubjectCountry(subjectInfo.getCountry());
certificate.setSubjectState(subjectInfo.getState());
certificate.setSubjectLocality(subjectInfo.getLocality());
final CertificateInfoExtension extension = info.extensions().getFirst();
if (extension != null) {
final CertificateExtension certificateExtension = new CertificateExtension();
certificateExtension.setIdentifier("alternativeNames");
certificateExtension.setValue(String.join(",", extension.getAlternativeDnsNames()));
certificate.setCertificateExtension(List.of(certificateExtension));
}
return certificate;
}
@NonNull
public Certificate importCertificate(
@NonNull Path certificate,
@Nullable Path keyFile,
@Nullable String keyPassphrase
) {
try {
String fingerprint = openSSLService.getCertificateFingerprint(certificate);
Certificate entity = createEntityFromInfo(openSSLService.getCertificateInfo(certificate));
entity.setFingerprint(fingerprint);
entity.setCert(Files.readAllBytes(certificate));
if (keyFile != null) {
entity.setPrivateKey(Files.readAllBytes(keyFile));
}
if (StringUtils.isNotBlank(keyPassphrase)) {
passphraseService.storePassphrase("cert:" + fingerprint, keyPassphrase);
}
return certificateRepository.save(entity);
} catch (CommandLineOperationException | IOException e) {
throw new RuntimeException("Unable to import certificate", e);
}
}
public List<Certificate> importCertificateTrustBundle(@NonNull Path bundleFile) {
try {
Map<String, Certificate> certsInBundle = new HashMap<>();
String pemContent = Files.readString(bundleFile);
Matcher beginMatcher = CERT_START_PATTERN.matcher(pemContent);
while (beginMatcher.find()) {
int startIdx = beginMatcher.start();
Matcher endMatcher = CERT_END_PATTERN.matcher(pemContent);
if (!endMatcher.find(startIdx)) {
throw new IllegalStateException("Certificate has a startIdx but not an end??");
}
int endIdx = endMatcher.end();
String singleCert = pemContent.substring(startIdx, endIdx);
String fingerprint = openSSLService.getCertificateFingerprint(singleCert);
Certificate entity = createEntityFromInfo(openSSLService.getCertificateInfo(singleCert));
entity.setFingerprint(fingerprint);
entity.setCert(singleCert.getBytes());
certsInBundle.put(fingerprint, entity);
log.debug("Found certificate in bundle at {} to {}: {}", startIdx, endIdx, fingerprint);
}
var saveResult = certificateRepository.saveAll(certsInBundle.values());
return StreamSupport.stream(saveResult.spliterator(), false).toList();
} catch (CommandLineOperationException | IOException e) {
throw new RuntimeException("Unable to import certificate", e);
}
}
private CertificateType mapCertificateRequestType(CertificateInfo.RequestType requestType) {
return switch (requestType) {
case ROOT_AUTHORITY -> CertificateType.ROOT_CA;
case STANDALONE_CERTIFICATE -> CertificateType.STANDALONE_CERT;
case NORMAL_CERTIFICATE -> CertificateType.SIGNED_CERT;
};
}
}