feat: Allow import of trust-only certificates
- These certificates do not have keys, because they are solely for trusting them. - Also more rigorously verify arguments passed to OpenSSL
This commit is contained in:
parent
286c9dcf28
commit
a4f495ab91
8 changed files with 157 additions and 30 deletions
|
@ -16,6 +16,7 @@ import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
@ -79,7 +80,10 @@ public class KeyStoreManager {
|
||||||
|
|
||||||
public void loadPrivateKeyIntoStore(KeyStore keyStore, CertificateUsage cert) throws KeyStoreException {
|
public void loadPrivateKeyIntoStore(KeyStore keyStore, CertificateUsage cert) throws KeyStoreException {
|
||||||
String passphrase = passwordProvider.getPasswordFor(cert.fingerprint());
|
String passphrase = passwordProvider.getPasswordFor(cert.fingerprint());
|
||||||
var pKey = loadPrivateKey(cert.certificateKeyPath(), passphrase);
|
var pKey = loadPrivateKey(
|
||||||
|
Objects.requireNonNull(cert.certificateKeyPath(), "Certificate does not have a key to import."),
|
||||||
|
passphrase
|
||||||
|
);
|
||||||
var certChain = loadCertificateChain(cert.fullchainPath());
|
var certChain = loadCertificateChain(cert.fullchainPath());
|
||||||
keyStore.setKeyEntry("KEY;%s".formatted(cert.fingerprint()), pKey, null, certChain);
|
keyStore.setKeyEntry("KEY;%s".formatted(cert.fingerprint()), pKey, null, certChain);
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ public class Certificate {
|
||||||
private byte[] cert = new byte[0];
|
private byte[] cert = new byte[0];
|
||||||
|
|
||||||
//@Lob - Cannot annotate column: https://github.com/xerial/sqlite-jdbc/issues/135
|
//@Lob - Cannot annotate column: https://github.com/xerial/sqlite-jdbc/issues/135
|
||||||
@Column(nullable = false)
|
@Column
|
||||||
private byte[] privateKey = new byte[0];
|
private byte[] privateKey = new byte[0];
|
||||||
|
|
||||||
//@Lob - Cannot annotate column: https://github.com/xerial/sqlite-jdbc/issues/135
|
//@Lob - Cannot annotate column: https://github.com/xerial/sqlite-jdbc/issues/135
|
||||||
|
|
|
@ -16,9 +16,11 @@ public interface CertificateUsage extends AutoCloseable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the path to the private key file (on disk, potentially temporary depending on the storage implementation).
|
* Returns the path to the private key file (on disk, potentially temporary depending on the storage implementation).
|
||||||
* This file should also be encrypted.
|
* This file should also be encrypted as the file system is considered in-flight.
|
||||||
* @see CertificatePasswordProvider
|
* @see CertificatePasswordProvider
|
||||||
|
* @apiNote Return value can be null, when there is no private key (e.g. imported certificates for trust only)
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
Path certificateKeyPath();
|
Path certificateKeyPath();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package de.mlessmann.certassist.openssl;
|
package de.mlessmann.certassist.openssl;
|
||||||
|
|
||||||
import static de.mlessmann.certassist.Constants.CERTASSIST_TMP_PREFIX;
|
import static de.mlessmann.certassist.Constants.CERTASSIST_TMP_PREFIX;
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
import static java.util.concurrent.TimeUnit.*;
|
import static java.util.concurrent.TimeUnit.*;
|
||||||
import static org.slf4j.LoggerFactory.getLogger;
|
import static org.slf4j.LoggerFactory.getLogger;
|
||||||
|
|
||||||
|
@ -33,6 +34,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.zeroturnaround.exec.ProcessExecutor;
|
import org.zeroturnaround.exec.ProcessExecutor;
|
||||||
|
@ -105,6 +107,12 @@ public class OpenSSLService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void requireNotBlank(@Nullable String str, @NonNull String failMessage) {
|
||||||
|
if (StringUtils.isBlank(str)) {
|
||||||
|
throw new IllegalArgumentException(failMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public OpenSSLCertificateResult createCertificate(CertificateInfo request) throws CommandLineOperationException {
|
public OpenSSLCertificateResult createCertificate(CertificateInfo request) throws CommandLineOperationException {
|
||||||
Path tmpDir;
|
Path tmpDir;
|
||||||
|
@ -126,11 +134,16 @@ public class OpenSSLService {
|
||||||
}
|
}
|
||||||
|
|
||||||
try (var certAuthority = certificateProvider.requestCertificateUsage(request.getTrustingAuthority())) {
|
try (var certAuthority = certificateProvider.requestCertificateUsage(request.getTrustingAuthority())) {
|
||||||
|
Path pKeyPath = requireNonNull(
|
||||||
|
certAuthority.certificateKeyPath(),
|
||||||
|
"Cannot sign a certificate with a CA for which the key is not available!"
|
||||||
|
);
|
||||||
|
|
||||||
Path signingRequest = createSigningRequest(request, keyFile, tmpDir.resolve("child.csr"), keyPassphrase);
|
Path signingRequest = createSigningRequest(request, keyFile, tmpDir.resolve("child.csr"), keyPassphrase);
|
||||||
Path signedCert = signCertificate(
|
Path signedCert = signCertificate(
|
||||||
request,
|
request,
|
||||||
certAuthority.certificatePath(),
|
certAuthority.certificatePath(),
|
||||||
certAuthority.certificateKeyPath(),
|
pKeyPath,
|
||||||
passwordProvider.getPasswordFor(certAuthority.fingerprint()),
|
passwordProvider.getPasswordFor(certAuthority.fingerprint()),
|
||||||
signingRequest
|
signingRequest
|
||||||
);
|
);
|
||||||
|
@ -153,11 +166,14 @@ public class OpenSSLService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path createKeyfile(CertificateInfo request, Path outFile, String filePassword)
|
@NonNull
|
||||||
|
private Path createKeyfile(@NonNull CertificateInfo request, @NonNull Path outFile, @NonNull String keyPassphrase)
|
||||||
throws CommandLineOperationException {
|
throws CommandLineOperationException {
|
||||||
|
requireNotBlank(keyPassphrase, "A passphrase must be provided to generate private keys!");
|
||||||
|
requireNonNull(outFile, "Output file must be provided to generate keys!");
|
||||||
Path keyFile = outFile.toAbsolutePath();
|
Path keyFile = outFile.toAbsolutePath();
|
||||||
log.debug("Writing new certificate key to {}", keyFile);
|
|
||||||
|
|
||||||
|
log.debug("Writing new certificate key to {}", keyFile);
|
||||||
StartedProcess keygenProc = null;
|
StartedProcess keygenProc = null;
|
||||||
try {
|
try {
|
||||||
keygenProc =
|
keygenProc =
|
||||||
|
@ -172,7 +188,7 @@ public class OpenSSLService {
|
||||||
OSSL_ARG_KEY_PW,
|
OSSL_ARG_KEY_PW,
|
||||||
Integer.toString(request.getRequestedKeyLength())
|
Integer.toString(request.getRequestedKeyLength())
|
||||||
)
|
)
|
||||||
.environment(OSSL_ENV_KEY_PW, filePassword)
|
.environment(OSSL_ENV_KEY_PW, keyPassphrase)
|
||||||
.redirectOutput(Slf4jStream.of(openSSLLogger).asDebug())
|
.redirectOutput(Slf4jStream.of(openSSLLogger).asDebug())
|
||||||
.redirectError(Slf4jStream.of(openSSLLogger).asError())
|
.redirectError(Slf4jStream.of(openSSLLogger).asError())
|
||||||
.start();
|
.start();
|
||||||
|
@ -185,10 +201,18 @@ public class OpenSSLService {
|
||||||
return keyFile;
|
return keyFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path createCertificate(CertificateInfo request, Path keyFile, Path outFile, String keyPassphrase)
|
@NonNull
|
||||||
throws CommandLineOperationException {
|
private Path createCertificate(
|
||||||
log.debug("Writing new certificate file {}", outFile);
|
@NonNull CertificateInfo request,
|
||||||
|
@NonNull Path keyFile,
|
||||||
|
Path outFile,
|
||||||
|
@NonNull String keyPassphrase
|
||||||
|
) throws CommandLineOperationException {
|
||||||
|
requireNonNull(outFile, "Output file must be provided to generate certificates!");
|
||||||
|
requireNonNull(keyFile, "Private key file must be provided to generate certificates!");
|
||||||
|
requireNotBlank(keyPassphrase, "Private key passphrase must be provided to generate certificates.");
|
||||||
|
|
||||||
|
log.debug("Writing new certificate file {}", outFile);
|
||||||
String certSubject = buildSubjectArg(request);
|
String certSubject = buildSubjectArg(request);
|
||||||
StartedProcess certGenProc = null;
|
StartedProcess certGenProc = null;
|
||||||
try {
|
try {
|
||||||
|
@ -225,9 +249,17 @@ public class OpenSSLService {
|
||||||
return outFile;
|
return outFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path createSigningRequest(CertificateInfo request, Path keyFile, Path outFile, String certPassword)
|
@NonNull
|
||||||
throws CommandLineOperationException {
|
private Path createSigningRequest(
|
||||||
log.atDebug().log("Writing new certificate signing request file {}", outFile);
|
@NonNull CertificateInfo request,
|
||||||
|
@NonNull Path keyFile,
|
||||||
|
@NonNull Path outFile,
|
||||||
|
@NonNull String pkeyPassphrase
|
||||||
|
) throws CommandLineOperationException {
|
||||||
|
requireNonNull(keyFile, "Private key must be provided to generate CSR!");
|
||||||
|
requireNonNull(outFile, "Output file must be provided to generate CSR!");
|
||||||
|
requireNotBlank(pkeyPassphrase, "Passphrase for private key must be provided to create CSR.");
|
||||||
|
log.debug("Writing new certificate signing request file {}", outFile);
|
||||||
|
|
||||||
String certSubject = buildSubjectArg(request);
|
String certSubject = buildSubjectArg(request);
|
||||||
StartedProcess certGenProc = null;
|
StartedProcess certGenProc = null;
|
||||||
|
@ -249,7 +281,7 @@ public class OpenSSLService {
|
||||||
"-subj",
|
"-subj",
|
||||||
certSubject
|
certSubject
|
||||||
)
|
)
|
||||||
.environment(OSSL_ENV_KEY_PW, certPassword)
|
.environment(OSSL_ENV_KEY_PW, pkeyPassphrase)
|
||||||
.redirectOutput(Slf4jStream.of(openSSLLogger).asDebug())
|
.redirectOutput(Slf4jStream.of(openSSLLogger).asDebug())
|
||||||
.redirectError(Slf4jStream.of(openSSLLogger).asError())
|
.redirectError(Slf4jStream.of(openSSLLogger).asError())
|
||||||
.start();
|
.start();
|
||||||
|
@ -270,10 +302,10 @@ public class OpenSSLService {
|
||||||
public boolean verifyCertificate(@NonNull Path fullChainFile, @NonNull List<Path> trustedCAs)
|
public boolean verifyCertificate(@NonNull Path fullChainFile, @NonNull List<Path> trustedCAs)
|
||||||
throws CommandLineOperationException {
|
throws CommandLineOperationException {
|
||||||
if (CollectionUtils.isEmpty(trustedCAs)) {
|
if (CollectionUtils.isEmpty(trustedCAs)) {
|
||||||
throw new IllegalArgumentException(
|
log.warn("No trusted CA provided for verification. Certificate is untrusted.");
|
||||||
"At least one trusted CA certificate must be provided to run the verification command."
|
return false;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
requireNonNull(fullChainFile, "Cannot verify certificate without fullChainFile");
|
||||||
|
|
||||||
Path tmpDir = null;
|
Path tmpDir = null;
|
||||||
StartedProcess verifyCommand = null;
|
StartedProcess verifyCommand = null;
|
||||||
|
@ -339,6 +371,8 @@ public class OpenSSLService {
|
||||||
*/
|
*/
|
||||||
public boolean verifyKeyPassphrase(@NonNull Path keyFile, @NonNull String passphrase)
|
public boolean verifyKeyPassphrase(@NonNull Path keyFile, @NonNull String passphrase)
|
||||||
throws CommandLineOperationException {
|
throws CommandLineOperationException {
|
||||||
|
requireNonNull(keyFile, "Key file must be provided to check encryption.");
|
||||||
|
requireNonNull(passphrase, "Must provide a passphrase to check encryption.");
|
||||||
StartedProcess verifyCommand = null;
|
StartedProcess verifyCommand = null;
|
||||||
try {
|
try {
|
||||||
verifyCommand =
|
verifyCommand =
|
||||||
|
@ -365,16 +399,22 @@ public class OpenSSLService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
private Path signCertificate(
|
private Path signCertificate(
|
||||||
CertificateInfo request,
|
CertificateInfo request,
|
||||||
Path caCert,
|
@NonNull Path caCert,
|
||||||
Path caKey,
|
@NonNull Path caKey,
|
||||||
String caKeyPassphrase,
|
@NonNull String caKeyPassphrase,
|
||||||
Path csrFile
|
@NonNull Path csrFile
|
||||||
) throws CommandLineOperationException {
|
) throws CommandLineOperationException {
|
||||||
|
requireNonNull(caCert, "CA certificate must be provided to sign certs!");
|
||||||
|
requireNonNull(caKey, "CA private key must be provided to sign certs!");
|
||||||
|
requireNotBlank(caKeyPassphrase, "CA private key passphrase must be provided to sign certs!");
|
||||||
|
requireNonNull(csrFile, "CSR to be signed must be provided.");
|
||||||
|
|
||||||
Path outFile = csrFile.resolveSibling(csrFile.getFileName().toString().replace(".csr", ".crt"));
|
Path outFile = csrFile.resolveSibling(csrFile.getFileName().toString().replace(".csr", ".crt"));
|
||||||
log.debug("Writing new signed certificate file {}", outFile);
|
|
||||||
Path extFile = csrFile.resolveSibling(csrFile.getFileName().toString().replace(".csr", ".ext"));
|
Path extFile = csrFile.resolveSibling(csrFile.getFileName().toString().replace(".csr", ".ext"));
|
||||||
|
log.debug("Writing new signed certificate file {}", outFile);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String extContent = CSR_EXT_TEMPLATE;
|
String extContent = CSR_EXT_TEMPLATE;
|
||||||
|
@ -394,7 +434,7 @@ public class OpenSSLService {
|
||||||
extContent = extContent.replaceAll("\\[alt_names]\n?", "");
|
extContent = extContent.replaceAll("\\[alt_names]\n?", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("Writing extension file content: \n {}", extContent);
|
log.debug("Writing extension file {} with content: \n {}", extFile, extContent);
|
||||||
Files.writeString(
|
Files.writeString(
|
||||||
extFile,
|
extFile,
|
||||||
extContent,
|
extContent,
|
||||||
|
@ -449,7 +489,10 @@ public class OpenSSLService {
|
||||||
return outFile;
|
return outFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCertificateFingerprint(Path certificate) throws CommandLineOperationException {
|
@NonNull
|
||||||
|
public String getCertificateFingerprint(@NonNull Path certificate) throws CommandLineOperationException {
|
||||||
|
requireNonNull(certificate, "Certificate must be provided to generate fingerprint.");
|
||||||
|
|
||||||
StartedProcess fingerprintProc = null;
|
StartedProcess fingerprintProc = null;
|
||||||
try {
|
try {
|
||||||
fingerprintProc =
|
fingerprintProc =
|
||||||
|
@ -492,7 +535,10 @@ public class OpenSSLService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public CertificateInfo getCertificateInfo(Path path) throws CommandLineOperationException {
|
public CertificateInfo getCertificateInfo(Path path) throws CommandLineOperationException {
|
||||||
|
requireNonNull(path, "Certificate file must be provided to read the info.");
|
||||||
|
|
||||||
StartedProcess infoProc = null;
|
StartedProcess infoProc = null;
|
||||||
try {
|
try {
|
||||||
infoProc =
|
infoProc =
|
||||||
|
@ -534,9 +580,12 @@ public class OpenSSLService {
|
||||||
return getCertificateInfo(output.lines().toArray(String[]::new));
|
return getCertificateInfo(output.lines().toArray(String[]::new));
|
||||||
} catch (IOException | ExecutionException | InterruptedException | TimeoutException e) {
|
} catch (IOException | ExecutionException | InterruptedException | TimeoutException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
} finally {
|
||||||
|
killIfActive(infoProc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
private String resolveOpenSSL() throws CommandLineOperationException {
|
private String resolveOpenSSL() throws CommandLineOperationException {
|
||||||
try {
|
try {
|
||||||
String path = executableResolver.getOpenSSLPath();
|
String path = executableResolver.getOpenSSLPath();
|
||||||
|
@ -621,7 +670,12 @@ public class OpenSSLService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public String readDecryptedKey(Path keyFile, String passphrase) throws CommandLineOperationException {
|
@NonNull
|
||||||
|
public String readDecryptedKey(@NonNull Path keyFile, @NonNull String passphrase)
|
||||||
|
throws CommandLineOperationException {
|
||||||
|
requireNonNull(keyFile, "Key to be decrypted must be provided!");
|
||||||
|
requireNotBlank(passphrase, "Passphrase for private key must be provided to run decryption.");
|
||||||
|
|
||||||
StartedProcess keyReadProc = null;
|
StartedProcess keyReadProc = null;
|
||||||
try {
|
try {
|
||||||
keyReadProc =
|
keyReadProc =
|
||||||
|
|
|
@ -10,6 +10,8 @@ import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@ -24,7 +26,10 @@ public class CertificateCreationService {
|
||||||
final Certificate certificate = createEntityFromRequest(certificateInfo);
|
final Certificate certificate = createEntityFromRequest(certificateInfo);
|
||||||
|
|
||||||
try (OpenSSLCertificateResult certificateCreatorResult = openSSLService.createCertificate(certificateInfo);) {
|
try (OpenSSLCertificateResult certificateCreatorResult = openSSLService.createCertificate(certificateInfo);) {
|
||||||
certificate.setPrivateKey(Files.readAllBytes(certificateCreatorResult.certificateKeyPath()));
|
Path keyPath = certificateCreatorResult.certificateKeyPath();
|
||||||
|
if (keyPath != null) {
|
||||||
|
certificate.setPrivateKey(Files.readAllBytes(keyPath));
|
||||||
|
}
|
||||||
certificate.setCert(Files.readAllBytes(certificateCreatorResult.certificatePath()));
|
certificate.setCert(Files.readAllBytes(certificateCreatorResult.certificatePath()));
|
||||||
} catch (CommandLineOperationException | IOException e) {
|
} catch (CommandLineOperationException | IOException e) {
|
||||||
throw new IllegalStateException("Failed to create certificate!", e);
|
throw new IllegalStateException("Failed to create certificate!", e);
|
||||||
|
@ -59,15 +64,22 @@ public class CertificateCreationService {
|
||||||
return certificate;
|
return certificate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Certificate importCertificate(Path certificate, Path keyFile, String passphrase) {
|
@NonNull
|
||||||
|
public Certificate importCertificate(
|
||||||
|
@NonNull Path certificate,
|
||||||
|
@Nullable Path keyFile,
|
||||||
|
@Nullable String keyPassphrase
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
String fingerprint = openSSLService.getCertificateFingerprint(certificate);
|
String fingerprint = openSSLService.getCertificateFingerprint(certificate);
|
||||||
var generatedRequest = openSSLService.getCertificateInfo(certificate);
|
var generatedRequest = openSSLService.getCertificateInfo(certificate);
|
||||||
Certificate entity = createEntityFromRequest(generatedRequest);
|
Certificate entity = createEntityFromRequest(generatedRequest);
|
||||||
entity.setCert(Files.readAllBytes(certificate));
|
entity.setCert(Files.readAllBytes(certificate));
|
||||||
entity.setPrivateKey(Files.readAllBytes(keyFile));
|
if (keyFile != null) {
|
||||||
if (StringUtils.isNotBlank(passphrase)) {
|
entity.setPrivateKey(Files.readAllBytes(keyFile));
|
||||||
passphraseService.storePassphrase("cert:" + fingerprint, passphrase);
|
}
|
||||||
|
if (StringUtils.isNotBlank(keyPassphrase)) {
|
||||||
|
passphraseService.storePassphrase("cert:" + fingerprint, keyPassphrase);
|
||||||
}
|
}
|
||||||
return certificateRepository.save(entity);
|
return certificateRepository.save(entity);
|
||||||
} catch (CommandLineOperationException | IOException e) {
|
} catch (CommandLineOperationException | IOException e) {
|
||||||
|
|
|
@ -14,6 +14,8 @@ import org.springframework.boot.test.context.SpringBootTest;
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
public class CertificateServiceTest {
|
public class CertificateServiceTest {
|
||||||
|
|
||||||
|
public static final Path LETSENCRYPT_CERT_PATH = Path.of("src/test/resources/letsencrypt");
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private CertificateCreationService certificateService;
|
private CertificateCreationService certificateService;
|
||||||
|
|
||||||
|
@ -49,4 +51,12 @@ public class CertificateServiceTest {
|
||||||
assertThat(importedCert).isNotNull();
|
assertThat(importedCert).isNotNull();
|
||||||
assertThat(importedCert.getId()).isGreaterThan("0");
|
assertThat(importedCert.getId()).isGreaterThan("0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCanImportTrustCertificate() {
|
||||||
|
Path cert = LETSENCRYPT_CERT_PATH.resolve("2024-11-isrgrootx1.pem");
|
||||||
|
var importedCert = certificateService.importCertificate(cert, null, null);
|
||||||
|
assertThat(importedCert).isNotNull();
|
||||||
|
assertThat(importedCert.getId()).isGreaterThan("0");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
14
src/test/resources/letsencrypt/2024-11-isrg-root-x2.pem
Normal file
14
src/test/resources/letsencrypt/2024-11-isrg-root-x2.pem
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
|
||||||
|
CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
|
||||||
|
R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
|
||||||
|
MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
|
||||||
|
ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
|
||||||
|
EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
|
||||||
|
+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
|
||||||
|
ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
|
||||||
|
AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
|
||||||
|
zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
|
||||||
|
tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
|
||||||
|
/q4AaOeMSQ+2b1tbFfLn
|
||||||
|
-----END CERTIFICATE-----
|
31
src/test/resources/letsencrypt/2024-11-isrgrootx1.pem
Normal file
31
src/test/resources/letsencrypt/2024-11-isrgrootx1.pem
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
Loading…
Add table
Reference in a new issue