172 lines
8.2 KiB
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;
|
|
};
|
|
}
|
|
}
|