Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions avaje-jex-ssl/src/main/java/io/avaje/jex/ssl/SslPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -30,4 +33,7 @@ public sealed interface SslPlugin extends JexPlugin permits DSslPlugin {
static SslPlugin create(Consumer<SslConfig> consumer) {
return new DSslPlugin(consumer);
}

/** The configured SSLContext */
SSLContext sslContext();
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.avaje.jex.ssl;
package io.avaje.jex.ssl.core;

import java.io.ByteArrayInputStream;
import java.io.IOException;
Expand All @@ -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);
Expand All @@ -38,10 +32,6 @@ String identityPassword() {
return identityPassword;
}

X509ExtendedKeyManager keyManager() {
return keyManager;
}

KeyStore keyStore() {
return keyStore;
}
Expand Down Expand Up @@ -82,7 +72,7 @@ private InputStream loadCP(String path) {
return url;
}

LoadedIdentity loadedIdentity() {
boolean loadedIdentity() {
return loadedIdentity;
}

Expand All @@ -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);
}
Expand All @@ -113,21 +106,23 @@ 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);
}
}

@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
Expand All @@ -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;
}
}
Expand All @@ -174,4 +159,4 @@ public void withTrustConfig(Consumer<TrustConfig> trustConfigConsumer) {
trustConfig = trustConfig == null ? new DTrustConfig(this::loadCP) : trustConfig;
trustConfigConsumer.accept(trustConfig);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<SslConfig> consumer) {
public DSslPlugin(Consumer<SslConfig> consumer) {
final var config = new DSslConfig();
consumer.accept(config);
this.sslConfigurator = SSLConfigurator.create(config);
Expand All @@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.avaje.jex.ssl;
package io.avaje.jex.ssl.core;

import java.io.InputStream;
import java.nio.file.Files;
Expand All @@ -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<Certificate> certificates = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.avaje.jex.ssl;
package io.avaje.jex.ssl.core;

import static java.util.Base64.getDecoder;

Expand All @@ -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;
Expand All @@ -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 =
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand All @@ -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);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.avaje.jex.ssl;
package io.avaje.jex.ssl.core;

import java.security.KeyStore;
import java.security.KeyStoreException;
Expand All @@ -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;
}

Expand All @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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
Expand All @@ -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<KeyStore> trustStores, List<Certificate> certificates) throws Exception {
private static KeyStore createCombinedTrustStore(
List<KeyStore> trustStores, List<Certificate> certificates) throws Exception {
var combinedTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
combinedTrustStore.load(null, null);

Expand Down Expand Up @@ -148,4 +155,12 @@ private static int addCertificatesFromKeyStore(
}
return aliasCounter;
}

public KeyStore keyStore() {
return keyStore;
}

public String password() {
return password;
}
}