From 9da774b2f4ff7a914c4425541e21b0b9e9b5bf06 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 10 Nov 2025 23:20:26 -0500 Subject: [PATCH] [ssl] simplify ssl plugin --- .../main/java/io/avaje/jex/ssl/SslPlugin.java | 6 +++ .../avaje/jex/ssl/{ => core}/DSslConfig.java | 49 +++++++------------ .../avaje/jex/ssl/{ => core}/DSslPlugin.java | 15 ++++-- .../jex/ssl/{ => core}/DTrustConfig.java | 5 +- .../jex/ssl/{ => core}/KeyStoreUtil.java | 29 +++-------- .../jex/ssl/{ => core}/SSLConfigurator.java | 45 +++++++++++------ 6 files changed, 76 insertions(+), 73 deletions(-) rename avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/{ => core}/DSslConfig.java (84%) rename avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/{ => core}/DSslPlugin.java (54%) rename avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/{ => core}/DTrustConfig.java (95%) rename avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/{ => core}/KeyStoreUtil.java (87%) rename avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/{ => core}/SSLConfigurator.java (82%) diff --git a/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/SslPlugin.java b/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/SslPlugin.java index 595c03d3..6452327d 100644 --- a/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/SslPlugin.java +++ b/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/SslPlugin.java @@ -2,7 +2,10 @@ import java.util.function.Consumer; +import javax.net.ssl.SSLContext; + import io.avaje.jex.spi.JexPlugin; +import io.avaje.jex.ssl.core.DSslPlugin; /** * Plugin that Configures Jex with SSL and mTLS. @@ -30,4 +33,7 @@ public sealed interface SslPlugin extends JexPlugin permits DSslPlugin { static SslPlugin create(Consumer consumer) { return new DSslPlugin(consumer); } + + /** The configured SSLContext */ + SSLContext sslContext(); } diff --git a/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/DSslConfig.java b/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/core/DSslConfig.java similarity index 84% rename from avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/DSslConfig.java rename to avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/core/DSslConfig.java index 088314b2..c7d8a39d 100644 --- a/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/DSslConfig.java +++ b/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/core/DSslConfig.java @@ -1,4 +1,4 @@ -package io.avaje.jex.ssl; +package io.avaje.jex.ssl.core; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -11,25 +11,19 @@ import java.security.Provider; import java.util.function.Consumer; -import javax.net.ssl.X509ExtendedKeyManager; - import io.avaje.jex.spi.ClassResourceLoader; +import io.avaje.jex.ssl.SslConfig; +import io.avaje.jex.ssl.SslConfigException; +import io.avaje.jex.ssl.TrustConfig; final class DSslConfig implements SslConfig { private static final String MULTIPLE_IDENTITY = "Both the certificate and key must be provided using the same method"; - enum LoadedIdentity { - KEY_MANAGER, - KEY_STORE, - NONE - } - private String identityPassword; - private X509ExtendedKeyManager keyManager = null; private KeyStore keyStore = null; - private LoadedIdentity loadedIdentity = LoadedIdentity.NONE; + private boolean loadedIdentity; private Provider securityProvider = null; private DTrustConfig trustConfig = null; private ClassResourceLoader resourceLoader = ClassResourceLoader.fromClass(SslConfig.class); @@ -38,10 +32,6 @@ String identityPassword() { return identityPassword; } - X509ExtendedKeyManager keyManager() { - return keyManager; - } - KeyStore keyStore() { return keyStore; } @@ -82,7 +72,7 @@ private InputStream loadCP(String path) { return url; } - LoadedIdentity loadedIdentity() { + boolean loadedIdentity() { return loadedIdentity; } @@ -96,11 +86,14 @@ public void pemFromInputStream( InputStream certificateInputStream, InputStream privateKeyInputStream, String password) { try { var keyContent = new String(privateKeyInputStream.readAllBytes()); - setKeyManager( + + setKeyStore( KeyStoreUtil.loadIdentityFromPem( certificateInputStream, keyContent, password != null ? password.toCharArray() : null)); + this.identityPassword = identityPassword != null ? identityPassword : ""; + } catch (IOException e) { throw new SslConfigException("Failed to read PEM content from streams", e); } @@ -113,9 +106,10 @@ public void pemFromPath(String certificatePath, String privateKeyPath, String pa var keyPath = Paths.get(privateKeyPath); var keyContent = Files.readString(keyPath); - setKeyManager( + setKeyStore( KeyStoreUtil.loadIdentityFromPem( certContent, keyContent, password != null ? password.toCharArray() : null)); + this.identityPassword = identityPassword != null ? identityPassword : ""; } catch (IOException e) { throw new SslConfigException("Failed to read PEM files", e); } @@ -123,11 +117,12 @@ public void pemFromPath(String certificatePath, String privateKeyPath, String pa @Override public void pemFromString(String certificateString, String privateKeyString, String password) { - setKeyManager( + setKeyStore( KeyStoreUtil.loadIdentityFromPem( new ByteArrayInputStream(certificateString.getBytes(StandardCharsets.UTF_8)), privateKeyString, password != null ? password.toCharArray() : null)); + this.identityPassword = identityPassword != null ? identityPassword : ""; } @Override @@ -145,22 +140,12 @@ public void securityProvider(Provider securityProvider) { this.securityProvider = securityProvider; } - private void setKeyManager(X509ExtendedKeyManager keyManager) { - if (loadedIdentity != LoadedIdentity.NONE) { - throw new SslConfigException(MULTIPLE_IDENTITY); - } - if (keyManager != null) { - loadedIdentity = LoadedIdentity.KEY_MANAGER; - this.keyManager = keyManager; - } - } - private void setKeyStore(KeyStore keyStore) { - if (loadedIdentity != LoadedIdentity.NONE) { + if (loadedIdentity) { throw new SslConfigException(MULTIPLE_IDENTITY); } if (keyStore != null) { - loadedIdentity = LoadedIdentity.KEY_STORE; + loadedIdentity = true; this.keyStore = keyStore; } } @@ -174,4 +159,4 @@ public void withTrustConfig(Consumer trustConfigConsumer) { trustConfig = trustConfig == null ? new DTrustConfig(this::loadCP) : trustConfig; trustConfigConsumer.accept(trustConfig); } -} +} \ No newline at end of file diff --git a/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/DSslPlugin.java b/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/core/DSslPlugin.java similarity index 54% rename from avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/DSslPlugin.java rename to avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/core/DSslPlugin.java index 10c8253f..d90f363e 100644 --- a/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/DSslPlugin.java +++ b/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/core/DSslPlugin.java @@ -1,16 +1,20 @@ -package io.avaje.jex.ssl; +package io.avaje.jex.ssl.core; import java.util.function.Consumer; +import javax.net.ssl.SSLContext; + import com.sun.net.httpserver.HttpsConfigurator; import io.avaje.jex.Jex; +import io.avaje.jex.ssl.SslConfig; +import io.avaje.jex.ssl.SslPlugin; -final class DSslPlugin implements SslPlugin { +public final class DSslPlugin implements SslPlugin { private final HttpsConfigurator sslConfigurator; - DSslPlugin(Consumer consumer) { + public DSslPlugin(Consumer consumer) { final var config = new DSslConfig(); consumer.accept(config); this.sslConfigurator = SSLConfigurator.create(config); @@ -20,4 +24,9 @@ final class DSslPlugin implements SslPlugin { public void apply(Jex jex) { jex.config().httpsConfig(sslConfigurator); } + + @Override + public SSLContext sslContext() { + return sslConfigurator.getSSLContext(); + } } diff --git a/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/DTrustConfig.java b/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/core/DTrustConfig.java similarity index 95% rename from avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/DTrustConfig.java rename to avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/core/DTrustConfig.java index 644a361b..933249de 100644 --- a/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/DTrustConfig.java +++ b/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/core/DTrustConfig.java @@ -1,4 +1,4 @@ -package io.avaje.jex.ssl; +package io.avaje.jex.ssl.core; import java.io.InputStream; import java.nio.file.Files; @@ -9,6 +9,9 @@ import java.util.List; import java.util.function.Function; +import io.avaje.jex.ssl.SslConfigException; +import io.avaje.jex.ssl.TrustConfig; + final class DTrustConfig implements TrustConfig { private final List certificates = new ArrayList<>(); diff --git a/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/KeyStoreUtil.java b/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/core/KeyStoreUtil.java similarity index 87% rename from avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/KeyStoreUtil.java rename to avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/core/KeyStoreUtil.java index 2fe32d6e..a9210782 100644 --- a/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/KeyStoreUtil.java +++ b/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/core/KeyStoreUtil.java @@ -1,4 +1,4 @@ -package io.avaje.jex.ssl; +package io.avaje.jex.ssl.core; import static java.util.Base64.getDecoder; @@ -11,7 +11,6 @@ import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; -import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; @@ -22,8 +21,7 @@ import java.util.List; import java.util.regex.Pattern; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.X509ExtendedKeyManager; +import io.avaje.jex.ssl.SslConfigException; final class KeyStoreUtil { private static final Pattern CERT_PATTERN = @@ -59,7 +57,8 @@ static KeyStore loadKeyStore(InputStream inputStream, char[] password) { return keyStore; } - throw new SslConfigException("Unable to load KeyStore - format not recognized or invalid password"); + throw new SslConfigException( + "Unable to load KeyStore - format not recognized or invalid password"); } private static KeyStore tryLoadKeyStore(byte[] data, String type, char[] password) { @@ -73,7 +72,7 @@ private static KeyStore tryLoadKeyStore(byte[] data, String type, char[] passwor } } - static X509ExtendedKeyManager loadIdentityFromPem( + static KeyStore loadIdentityFromPem( InputStream certificateInputStream, String privateKeyContent, char[] password) { try { var certificates = parseCertificates(certificateInputStream); @@ -86,22 +85,8 @@ static X509ExtendedKeyManager loadIdentityFromPem( var keyPassword = password != null ? password : new char[0]; keyStore.setKeyEntry(alias, privateKey, keyPassword, certChain); - var kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(keyStore, keyPassword); - - for (var km : kmf.getKeyManagers()) { - if (km instanceof X509ExtendedKeyManager m) { - return m; - } - } - - throw new SslConfigException("No X509ExtendedKeyManager found"); - - } catch (KeyStoreException - | NoSuchAlgorithmException - | UnrecoverableKeyException - | CertificateException - | IOException e) { + return keyStore; + } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) { throw new SslConfigException("Failed to create KeyManager from PEM content", e); } } diff --git a/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/SSLConfigurator.java b/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/core/SSLConfigurator.java similarity index 82% rename from avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/SSLConfigurator.java rename to avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/core/SSLConfigurator.java index bcb3e921..db859571 100644 --- a/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/SSLConfigurator.java +++ b/avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/core/SSLConfigurator.java @@ -1,4 +1,4 @@ -package io.avaje.jex.ssl; +package io.avaje.jex.ssl.core; import java.security.KeyStore; import java.security.KeyStoreException; @@ -17,16 +17,22 @@ import com.sun.net.httpserver.HttpsConfigurator; import com.sun.net.httpserver.HttpsParameters; -final class SSLConfigurator extends HttpsConfigurator { +import io.avaje.jex.ssl.SslConfigException; + +public final class SSLConfigurator extends HttpsConfigurator { private static final String SSL_PROTOCOL = "TLSv1.3"; private static final String KEY_MANAGER_ALGORITHM = "SunX509"; private static final String TRUST_MANAGER_ALGORITHM = "PKIX"; private final boolean clientAuth; + private final KeyStore keyStore; + private final String password; - SSLConfigurator(SSLContext context, boolean clientAuth) { + SSLConfigurator(SSLContext context, DSslConfig sslConfig, boolean clientAuth) { super(context); + this.keyStore = sslConfig.keyStore(); + this.password = sslConfig.identityPassword(); this.clientAuth = clientAuth; } @@ -42,10 +48,8 @@ static SSLConfigurator create(DSslConfig sslConfig) throws SslConfigException { var sslContext = createContext(sslConfig); var keyManagers = createKeyManagers(sslConfig); var trustManagers = createTrustManagers(sslConfig); - sslContext.init(keyManagers, trustManagers, new SecureRandom()); - - return new SSLConfigurator(sslContext, trustManagers != null); + return new SSLConfigurator(sslContext, sslConfig, trustManagers !=null); } catch (Exception e) { throw new SslConfigException("Failed to build SSLContext", e); } @@ -60,11 +64,10 @@ private static SSLContext createContext(DSslConfig sslConfig) throws NoSuchAlgor private static KeyManager[] createKeyManagers(DSslConfig sslConfig) throws SslConfigException { try { - return switch (sslConfig.loadedIdentity()) { - case KEY_MANAGER -> new KeyManager[] {sslConfig.keyManager()}; - case KEY_STORE -> createKeyManagersFromKeyStore(sslConfig); - default -> throw new IllegalStateException("No SSL Identity provided"); - }; + if (sslConfig.loadedIdentity()) { + return createKeyManagersFromKeyStore(sslConfig); + } + throw new IllegalStateException("No SSL Identity provided"); } catch (Exception e) { throw new SslConfigException("Failed to create key managers", e); } @@ -79,14 +82,16 @@ private static KeyManager[] createKeyManagersFromKeyStore(DSslConfig sslConfig) return keyManagerFactory.getKeyManagers(); } - private static KeyManagerFactory createKeyManagerFactory(DSslConfig sslConfig) throws NoSuchAlgorithmException { + private static KeyManagerFactory createKeyManagerFactory(DSslConfig sslConfig) + throws NoSuchAlgorithmException { if (sslConfig.securityProvider() != null) { return KeyManagerFactory.getInstance(KEY_MANAGER_ALGORITHM, sslConfig.securityProvider()); } return KeyManagerFactory.getInstance(KEY_MANAGER_ALGORITHM); } - private static TrustManager[] createTrustManagers(DSslConfig sslConfig) throws SslConfigException { + private static TrustManager[] createTrustManagers(DSslConfig sslConfig) + throws SslConfigException { var trustConfig = sslConfig.trustConfig(); if (trustConfig == null) { return null; // Use system default trust managers @@ -109,14 +114,16 @@ private static TrustManager[] createTrustManagers(DSslConfig sslConfig) throws S } } - private static TrustManagerFactory createTrustManagerFactory(DSslConfig sslConfig) throws NoSuchAlgorithmException { + private static TrustManagerFactory createTrustManagerFactory(DSslConfig sslConfig) + throws NoSuchAlgorithmException { if (sslConfig.securityProvider() != null) { return TrustManagerFactory.getInstance(TRUST_MANAGER_ALGORITHM, sslConfig.securityProvider()); } return TrustManagerFactory.getInstance(TRUST_MANAGER_ALGORITHM); } - private static KeyStore createCombinedTrustStore(List trustStores, List certificates) throws Exception { + private static KeyStore createCombinedTrustStore( + List trustStores, List certificates) throws Exception { var combinedTrustStore = KeyStore.getInstance(KeyStore.getDefaultType()); combinedTrustStore.load(null, null); @@ -148,4 +155,12 @@ private static int addCertificatesFromKeyStore( } return aliasCounter; } + + public KeyStore keyStore() { + return keyStore; + } + + public String password() { + return password; + } }