From 286c9dcf281e6751918a056b630614b447aa6d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20Le=C3=9Fmann=20=28=40MarkL4YG=29?= Date: Sun, 24 Nov 2024 10:17:45 +0100 Subject: [PATCH] feat: Unify Trust- and KeyStoreManager services --- .../keystore/AutoBootKeyStoreManagement.java | 6 -- .../certassist/keystore/KeyStoreManager.java | 85 +++++++++++++++---- .../keystore/TruststoreManager.java | 62 -------------- .../certassist/TestKeystoreCreation.java | 5 +- 4 files changed, 69 insertions(+), 89 deletions(-) delete mode 100644 src/main/java/de/mlessmann/certassist/keystore/TruststoreManager.java diff --git a/src/main/java/de/mlessmann/certassist/keystore/AutoBootKeyStoreManagement.java b/src/main/java/de/mlessmann/certassist/keystore/AutoBootKeyStoreManagement.java index b1a4035..b87c16b 100644 --- a/src/main/java/de/mlessmann/certassist/keystore/AutoBootKeyStoreManagement.java +++ b/src/main/java/de/mlessmann/certassist/keystore/AutoBootKeyStoreManagement.java @@ -17,10 +17,4 @@ public class AutoBootKeyStoreManagement { ) { return new KeyStoreManager(certificateCreator, passwordProvider); } - - @Bean - @ConditionalOnMissingBean(TruststoreManager.class) - public TruststoreManager truststoreProvider() { - return new TruststoreManager(); - } } diff --git a/src/main/java/de/mlessmann/certassist/keystore/KeyStoreManager.java b/src/main/java/de/mlessmann/certassist/keystore/KeyStoreManager.java index 93da782..251c1cf 100644 --- a/src/main/java/de/mlessmann/certassist/keystore/KeyStoreManager.java +++ b/src/main/java/de/mlessmann/certassist/keystore/KeyStoreManager.java @@ -13,8 +13,10 @@ import java.security.*; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Base64; +import java.util.concurrent.atomic.AtomicReference; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -29,23 +31,19 @@ public class KeyStoreManager { }; private final OpenSSLService certificateCreator; private final CertificatePasswordProvider passwordProvider; + private final AtomicReference certFactory = new AtomicReference<>(); public KeystoreUsage createKeyStore(String keyStorePassphrase, CertificateUsage... serverCerts) throws JavaSecurityException { try { Path keystorePath = Files.createTempFile("keystore", ".jks"); - - // Load the keystore + keystorePath.toFile().deleteOnExit(); + log.debug("Creating keyStore at {}", keystorePath); KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); - keystore.load(null, null); + keystore.load(null, keyStorePassphrase.toCharArray()); for (CertificateUsage serverCert : serverCerts) { - PrivateKey privateKey = loadPrivateKey( - serverCert.certificateKeyPath(), - passwordProvider.getPasswordFor(serverCert.fingerprint()) - ); - Certificate[] certChain = loadCertificateChain(serverCert.fullchainPath()); - keystore.setKeyEntry(serverCert.fingerprint(), privateKey, null, certChain); + loadPrivateKeyIntoStore(keystore, serverCert); } // Save the keystore @@ -58,6 +56,48 @@ public class KeyStoreManager { } } + public KeystoreUsage createTruststore(String truststorePassphrase, CertificateUsage... trustedCertificates) { + try { + Path truststorePath = Files.createTempFile("truststore", ".jks"); + truststorePath.toFile().deleteOnExit(); + log.debug("Creating truststore at {}", truststorePath); + KeyStore truststore = KeyStore.getInstance(KeyStore.getDefaultType()); + truststore.load(null, truststorePassphrase.toCharArray()); + + for (CertificateUsage serverCert : trustedCertificates) { + loadPublicKeyIntoTrust(truststore, serverCert); + } + + try (var outputStream = Files.newOutputStream(truststorePath, CREATE_TRUNCATE)) { + truststore.store(outputStream, truststorePassphrase.toCharArray()); + } + return new KeystoreResult(truststorePath); + } catch (IOException | KeyStoreException | CertificateException | NoSuchAlgorithmException e) { + throw new IllegalStateException("Failed to create truststore!", e); + } + } + + public void loadPrivateKeyIntoStore(KeyStore keyStore, CertificateUsage cert) throws KeyStoreException { + String passphrase = passwordProvider.getPasswordFor(cert.fingerprint()); + var pKey = loadPrivateKey(cert.certificateKeyPath(), passphrase); + var certChain = loadCertificateChain(cert.fullchainPath()); + keyStore.setKeyEntry("KEY;%s".formatted(cert.fingerprint()), pKey, null, certChain); + } + + public void loadPublicKeyIntoTrust(KeyStore keyStore, CertificateUsage cert) throws KeyStoreException { + try (var inputStream = Files.newInputStream(cert.certificatePath())) { + var parsedCert = getX509Factory().generateCertificate(inputStream); + if (!(parsedCert instanceof X509Certificate x509cert)) { + throw new IllegalStateException("CertificateUsage did not parse to X509 certificate??"); + } + keyStore.setCertificateEntry("TRUST;%s".formatted(cert.fingerprint()), x509cert); + } catch (IOException e) { + throw new IllegalStateException("CertificateUsage could not be read??", e); + } catch (CertificateException e) { + throw new IllegalStateException("CertificateUsage is not a parsable certificate??"); + } + } + @SneakyThrows private PrivateKey loadPrivateKey(Path privateKey, String passphrase) { String pemContent; @@ -67,14 +107,12 @@ public class KeyStoreManager { pemContent = Files.readString(privateKey); } - try (var fis = Files.newInputStream(privateKey)) { - String privateKeyPEM = pemContent - .replaceAll(".*?-----BEGIN PRIVATE KEY-----", "") - .replace("-----END PRIVATE KEY-----", "") - .replaceAll("\\s", ""); - byte[] decodedKey = Base64.getDecoder().decode(privateKeyPEM); - return KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decodedKey)); - } + String privateKeyPEM = pemContent + .replaceAll(".*?-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s", ""); + byte[] decodedKey = Base64.getDecoder().decode(privateKeyPEM); + return KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decodedKey)); } @SneakyThrows @@ -85,4 +123,17 @@ public class KeyStoreManager { return certFactory.generateCertificates(fis).toArray(Certificate[]::new); } } + + protected final synchronized CertificateFactory getX509Factory() { + CertificateFactory instance = certFactory.get(); + if (instance == null) { + try { + instance = CertificateFactory.getInstance("X.509"); + certFactory.set(instance); + } catch (CertificateException e) { + throw new IllegalStateException("X.509 factory missing??", e); + } + } + return instance; + } } diff --git a/src/main/java/de/mlessmann/certassist/keystore/TruststoreManager.java b/src/main/java/de/mlessmann/certassist/keystore/TruststoreManager.java deleted file mode 100644 index 131ecbd..0000000 --- a/src/main/java/de/mlessmann/certassist/keystore/TruststoreManager.java +++ /dev/null @@ -1,62 +0,0 @@ -package de.mlessmann.certassist.keystore; - -import de.mlessmann.certassist.openssl.CertificateUsage; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.OpenOption; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@RequiredArgsConstructor -public class TruststoreManager { - - private static final OpenOption[] CREATE_TRUNCATE = { - StandardOpenOption.CREATE, - StandardOpenOption.TRUNCATE_EXISTING, - }; - - public KeystoreUsage createTruststore(String truststorePassphrase, CertificateUsage... trustedCertificates) { - try { - Path truststorePath = Files.createTempFile("truststore", ".jks"); - truststorePath.toFile().deleteOnExit(); - log.debug("Creating truststore at {}", truststorePath); - - KeyStore truststore = buildTruststore(trustedCertificates); - try (var outputStream = Files.newOutputStream(truststorePath, CREATE_TRUNCATE)) { - truststore.store(outputStream, truststorePassphrase.toCharArray()); - } - return new KeystoreResult(truststorePath); - } catch (IOException | KeyStoreException | CertificateException | NoSuchAlgorithmException e) { - throw new IllegalStateException("Failed to create truststore!", e); - } - } - - @SneakyThrows - private static KeyStore buildTruststore(CertificateUsage[] trustedCertificates) { - KeyStore truststore = KeyStore.getInstance(KeyStore.getDefaultType()); - truststore.load(null, null); - CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - - if (trustedCertificates == null || trustedCertificates.length == 0) { - log.warn("No trusted certificates provided, truststore will be empty!"); - } else { - for (CertificateUsage trustedCertificate : trustedCertificates) { - try (var inputStream = Files.newInputStream(trustedCertificate.certificatePath())) { - X509Certificate jdkCert = (X509Certificate) certificateFactory.generateCertificate(inputStream); - truststore.setCertificateEntry(trustedCertificate.fingerprint(), jdkCert); - } - } - } - return truststore; - } -} diff --git a/src/test/java/de/mlessmann/certassist/TestKeystoreCreation.java b/src/test/java/de/mlessmann/certassist/TestKeystoreCreation.java index d8b657a..f480b71 100644 --- a/src/test/java/de/mlessmann/certassist/TestKeystoreCreation.java +++ b/src/test/java/de/mlessmann/certassist/TestKeystoreCreation.java @@ -3,7 +3,6 @@ package de.mlessmann.certassist; import static org.assertj.core.api.Assertions.assertThat; import de.mlessmann.certassist.keystore.KeyStoreManager; -import de.mlessmann.certassist.keystore.TruststoreManager; import de.mlessmann.certassist.openssl.CertificateProvider; import de.mlessmann.certassist.openssl.CertificateUsage; import de.mlessmann.certassist.openssl.InMemoryCertificatePasswordProvider; @@ -53,13 +52,12 @@ public class TestKeystoreCreation { var certificateProvider = Mockito.mock(CertificateProvider.class); var opensslCertCreator = new OpenSSLService(new ExecutableResolver(), passwordProvider, certificateProvider); - var truststoreManager = new TruststoreManager(); var keyStoreManager = new KeyStoreManager(opensslCertCreator, passwordProvider); AtomicBoolean serverAccepted = new AtomicBoolean(false); AtomicBoolean clientAccepted = new AtomicBoolean(false); try ( - var tmpTruststore = truststoreManager.createTruststore(STORE_PASSPHRASE, dummyCert); + var tmpTruststore = keyStoreManager.createTruststore(STORE_PASSPHRASE, dummyCert); var tmpKeyStore = keyStoreManager.createKeyStore(STORE_PASSPHRASE, dummyCert) ) { KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance( @@ -69,7 +67,6 @@ public class TestKeystoreCreation { SSLContext tlsSrvContext = SSLContext.getInstance("TLS"); tlsSrvContext.init(keyManagerFactory.getKeyManagers(), null, TEST_RANDOM); - int serverPort = 1024 + TEST_RANDOM.nextInt(22_000); ServerSocket serverSocket = tlsSrvContext.getServerSocketFactory().createServerSocket(0); var serverThread = Thread.startVirtualThread(() -> {