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 importCertificateTrustBundle(@NonNull Path bundleFile) { try { Map 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; }; } }