feat: Unify Trust- and KeyStoreManager services
This commit is contained in:
parent
335eb3ed8d
commit
286c9dcf28
4 changed files with 69 additions and 89 deletions
|
@ -17,10 +17,4 @@ public class AutoBootKeyStoreManagement {
|
||||||
) {
|
) {
|
||||||
return new KeyStoreManager(certificateCreator, passwordProvider);
|
return new KeyStoreManager(certificateCreator, passwordProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean(TruststoreManager.class)
|
|
||||||
public TruststoreManager truststoreProvider() {
|
|
||||||
return new TruststoreManager();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,10 @@ import java.security.*;
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
|
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.concurrent.atomic.AtomicReference;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
@ -29,23 +31,19 @@ public class KeyStoreManager {
|
||||||
};
|
};
|
||||||
private final OpenSSLService certificateCreator;
|
private final OpenSSLService certificateCreator;
|
||||||
private final CertificatePasswordProvider passwordProvider;
|
private final CertificatePasswordProvider passwordProvider;
|
||||||
|
private final AtomicReference<CertificateFactory> certFactory = new AtomicReference<>();
|
||||||
|
|
||||||
public KeystoreUsage createKeyStore(String keyStorePassphrase, CertificateUsage... serverCerts)
|
public KeystoreUsage createKeyStore(String keyStorePassphrase, CertificateUsage... serverCerts)
|
||||||
throws JavaSecurityException {
|
throws JavaSecurityException {
|
||||||
try {
|
try {
|
||||||
Path keystorePath = Files.createTempFile("keystore", ".jks");
|
Path keystorePath = Files.createTempFile("keystore", ".jks");
|
||||||
|
keystorePath.toFile().deleteOnExit();
|
||||||
// Load the keystore
|
log.debug("Creating keyStore at {}", keystorePath);
|
||||||
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
|
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
keystore.load(null, null);
|
keystore.load(null, keyStorePassphrase.toCharArray());
|
||||||
|
|
||||||
for (CertificateUsage serverCert : serverCerts) {
|
for (CertificateUsage serverCert : serverCerts) {
|
||||||
PrivateKey privateKey = loadPrivateKey(
|
loadPrivateKeyIntoStore(keystore, serverCert);
|
||||||
serverCert.certificateKeyPath(),
|
|
||||||
passwordProvider.getPasswordFor(serverCert.fingerprint())
|
|
||||||
);
|
|
||||||
Certificate[] certChain = loadCertificateChain(serverCert.fullchainPath());
|
|
||||||
keystore.setKeyEntry(serverCert.fingerprint(), privateKey, null, certChain);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the keystore
|
// 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
|
@SneakyThrows
|
||||||
private PrivateKey loadPrivateKey(Path privateKey, String passphrase) {
|
private PrivateKey loadPrivateKey(Path privateKey, String passphrase) {
|
||||||
String pemContent;
|
String pemContent;
|
||||||
|
@ -67,7 +107,6 @@ public class KeyStoreManager {
|
||||||
pemContent = Files.readString(privateKey);
|
pemContent = Files.readString(privateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
try (var fis = Files.newInputStream(privateKey)) {
|
|
||||||
String privateKeyPEM = pemContent
|
String privateKeyPEM = pemContent
|
||||||
.replaceAll(".*?-----BEGIN PRIVATE KEY-----", "")
|
.replaceAll(".*?-----BEGIN PRIVATE KEY-----", "")
|
||||||
.replace("-----END PRIVATE KEY-----", "")
|
.replace("-----END PRIVATE KEY-----", "")
|
||||||
|
@ -75,7 +114,6 @@ public class KeyStoreManager {
|
||||||
byte[] decodedKey = Base64.getDecoder().decode(privateKeyPEM);
|
byte[] decodedKey = Base64.getDecoder().decode(privateKeyPEM);
|
||||||
return KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decodedKey));
|
return KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decodedKey));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private Certificate[] loadCertificateChain(Path certChainPath) {
|
private Certificate[] loadCertificateChain(Path certChainPath) {
|
||||||
|
@ -85,4 +123,17 @@ public class KeyStoreManager {
|
||||||
return certFactory.generateCertificates(fis).toArray(Certificate[]::new);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,7 +3,6 @@ package de.mlessmann.certassist;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
import de.mlessmann.certassist.keystore.KeyStoreManager;
|
import de.mlessmann.certassist.keystore.KeyStoreManager;
|
||||||
import de.mlessmann.certassist.keystore.TruststoreManager;
|
|
||||||
import de.mlessmann.certassist.openssl.CertificateProvider;
|
import de.mlessmann.certassist.openssl.CertificateProvider;
|
||||||
import de.mlessmann.certassist.openssl.CertificateUsage;
|
import de.mlessmann.certassist.openssl.CertificateUsage;
|
||||||
import de.mlessmann.certassist.openssl.InMemoryCertificatePasswordProvider;
|
import de.mlessmann.certassist.openssl.InMemoryCertificatePasswordProvider;
|
||||||
|
@ -53,13 +52,12 @@ public class TestKeystoreCreation {
|
||||||
|
|
||||||
var certificateProvider = Mockito.mock(CertificateProvider.class);
|
var certificateProvider = Mockito.mock(CertificateProvider.class);
|
||||||
var opensslCertCreator = new OpenSSLService(new ExecutableResolver(), passwordProvider, certificateProvider);
|
var opensslCertCreator = new OpenSSLService(new ExecutableResolver(), passwordProvider, certificateProvider);
|
||||||
var truststoreManager = new TruststoreManager();
|
|
||||||
var keyStoreManager = new KeyStoreManager(opensslCertCreator, passwordProvider);
|
var keyStoreManager = new KeyStoreManager(opensslCertCreator, passwordProvider);
|
||||||
AtomicBoolean serverAccepted = new AtomicBoolean(false);
|
AtomicBoolean serverAccepted = new AtomicBoolean(false);
|
||||||
AtomicBoolean clientAccepted = new AtomicBoolean(false);
|
AtomicBoolean clientAccepted = new AtomicBoolean(false);
|
||||||
|
|
||||||
try (
|
try (
|
||||||
var tmpTruststore = truststoreManager.createTruststore(STORE_PASSPHRASE, dummyCert);
|
var tmpTruststore = keyStoreManager.createTruststore(STORE_PASSPHRASE, dummyCert);
|
||||||
var tmpKeyStore = keyStoreManager.createKeyStore(STORE_PASSPHRASE, dummyCert)
|
var tmpKeyStore = keyStoreManager.createKeyStore(STORE_PASSPHRASE, dummyCert)
|
||||||
) {
|
) {
|
||||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
|
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
|
||||||
|
@ -69,7 +67,6 @@ public class TestKeystoreCreation {
|
||||||
|
|
||||||
SSLContext tlsSrvContext = SSLContext.getInstance("TLS");
|
SSLContext tlsSrvContext = SSLContext.getInstance("TLS");
|
||||||
tlsSrvContext.init(keyManagerFactory.getKeyManagers(), null, TEST_RANDOM);
|
tlsSrvContext.init(keyManagerFactory.getKeyManagers(), null, TEST_RANDOM);
|
||||||
int serverPort = 1024 + TEST_RANDOM.nextInt(22_000);
|
|
||||||
|
|
||||||
ServerSocket serverSocket = tlsSrvContext.getServerSocketFactory().createServerSocket(0);
|
ServerSocket serverSocket = tlsSrvContext.getServerSocketFactory().createServerSocket(0);
|
||||||
var serverThread = Thread.startVirtualThread(() -> {
|
var serverThread = Thread.startVirtualThread(() -> {
|
||||||
|
|
Loading…
Add table
Reference in a new issue