feat: Unify Trust- and KeyStoreManager services
This commit is contained in:
parent
335eb3ed8d
commit
301579cb87
4 changed files with 69 additions and 89 deletions
|
@ -17,10 +17,4 @@ public class AutoBootKeyStoreManagement {
|
|||
) {
|
||||
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.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<CertificateFactory> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 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(() -> {
|
||||
|
|
Loading…
Add table
Reference in a new issue