diff --git a/ELY_Messages.txt b/ELY_Messages.txt
index e3ecb9c83e..00c3b509d3 100644
--- a/ELY_Messages.txt
+++ b/ELY_Messages.txt
@@ -126,7 +126,7 @@
24000 - 24999 wildfly-elytron-jose-jwk
25000 - 25999 wildfly-elytron-jose-jws
26000 - 26999 wildfly-elytron-jose-util
-27000 - 27999
+27000 - 27999 wildfly-elytron-dynamic-ssl
28000 - 28999
29000 - 29999
30000 - 30999
diff --git a/auth/client/pom.xml b/auth/client/pom.xml
index 728484b00b..4000135bb4 100644
--- a/auth/client/pom.xml
+++ b/auth/client/pom.xml
@@ -80,7 +80,6 @@
org.wildfly.security
wildfly-elytron-ssh-util
-
org.jboss.logging
jboss-logging-annotations
diff --git a/auth/client/src/main/java/org/wildfly/security/auth/client/ActiveSessionsSSLContext.java b/auth/client/src/main/java/org/wildfly/security/auth/client/ActiveSessionsSSLContext.java
new file mode 100644
index 0000000000..691066b170
--- /dev/null
+++ b/auth/client/src/main/java/org/wildfly/security/auth/client/ActiveSessionsSSLContext.java
@@ -0,0 +1,33 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2024 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wildfly.security.auth.client;
+
+/**
+ * An interface indicating active sessions of an SSLContext
+ */
+public interface ActiveSessionsSSLContext {
+ /**
+ * Indicates if the SSLContext has active sessions.
+ *
+ * @return true if SSLContext has active sessions. Otherwise, false
+ */
+ default boolean hasActiveSessions() {
+ return false;
+ }
+}
diff --git a/auth/client/src/main/java/org/wildfly/security/auth/client/AuthenticationContext.java b/auth/client/src/main/java/org/wildfly/security/auth/client/AuthenticationContext.java
index ba7e75a22b..aadc6c7b8e 100644
--- a/auth/client/src/main/java/org/wildfly/security/auth/client/AuthenticationContext.java
+++ b/auth/client/src/main/java/org/wildfly/security/auth/client/AuthenticationContext.java
@@ -361,6 +361,10 @@ public T runAsSupplierEx(ExceptionSupplier action
return runExFunction(ExceptionSupplier::get, action);
}
+ RuleNode> getSslRules() {
+ return this.sslRules;
+ }
+
public ContextManager getInstanceContextManager() {
return getContextManager();
}
diff --git a/auth/client/src/main/java/org/wildfly/security/auth/client/AuthenticationContextConfigurationClient.java b/auth/client/src/main/java/org/wildfly/security/auth/client/AuthenticationContextConfigurationClient.java
index e1519a7917..c0f915766c 100644
--- a/auth/client/src/main/java/org/wildfly/security/auth/client/AuthenticationContextConfigurationClient.java
+++ b/auth/client/src/main/java/org/wildfly/security/auth/client/AuthenticationContextConfigurationClient.java
@@ -30,7 +30,9 @@
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.Provider;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
@@ -196,6 +198,42 @@ private static AuthenticationConfiguration initializeConfiguration(final URI uri
return configuration;
}
+ /**
+ * Get all SSL contexts configured for this authentication context.
+ *
+ * @param authenticationContext the authentication context to examine (must not be {@code null})
+ * @return List of all configured SSL contexts belonging to the provided authentication context
+ */
+ public List getConfiguredSSLContexts(AuthenticationContext authenticationContext) throws GeneralSecurityException {
+ Assert.checkNotNullParam("authenticationContext", authenticationContext);
+ List sslContexts = new ArrayList<>();
+ RuleNode> node = authenticationContext.getSslRules();
+ while (node != null) {
+ sslContexts.add(node.getConfiguration().create());
+ node = node.getNext();
+ }
+ return sslContexts;
+ }
+
+ /**
+ * Get the default SSL context that should be used when no other rules match, or {@link SSLContext#getDefault()} if there is none configured.
+ *
+ * @param authenticationContext the authentication context to examine (must not be {@code null})
+ * @return the default SSL context configured if no other rules match
+ */
+ public SSLContext getDefaultSSLContext(AuthenticationContext authenticationContext) throws GeneralSecurityException {
+ Assert.checkNotNullParam("authenticationContext", authenticationContext);
+ SSLContext defaultSSLContext = null;
+ RuleNode> node = authenticationContext.getSslRules();
+ while (node != null) {
+ if (node.getRule().equals(MatchRule.ALL)) {
+ defaultSSLContext = node.getConfiguration().create();
+ }
+ node = node.getNext();
+ }
+ return defaultSSLContext == null ? SSLContext.getDefault() : defaultSSLContext;
+ }
+
/**
* Get the configured SSL context which matches ALL rules from provided AuthenticationContext, or {@link SSLContext#getDefault()} if there is none.
*
diff --git a/dynamic-ssl/pom.xml b/dynamic-ssl/pom.xml
new file mode 100644
index 0000000000..69c243af44
--- /dev/null
+++ b/dynamic-ssl/pom.xml
@@ -0,0 +1,76 @@
+
+
+
+ org.wildfly.security
+ wildfly-elytron-parent
+ 2.3.2.CR1-SNAPSHOT
+
+
+ 4.0.0
+
+ wildfly-elytron-dynamic-ssl
+
+ WildFly Elytron - Dynamic SSL
+ WildFly Security Dynamic SSL Implementation
+
+
+ org.jboss.logging
+ jboss-logging-annotations
+ provided
+
+
+ org.jboss.logging
+ jboss-logging
+ provided
+
+
+ org.jboss.logging
+ jboss-logging-processor
+ provided
+
+
+ org.jboss.logmanager
+ jboss-logmanager
+ provided
+
+
+ org.wildfly.security
+ wildfly-elytron-client
+
+
+ org.kohsuke.metainf-services
+ metainf-services
+ provided
+
+
+ org.wildfly.common
+ wildfly-common
+ compile
+
+
+
+
+ junit
+ junit
+ test
+
+
+
+ com.squareup.okhttp3
+ mockwebserver
+ test
+
+
+ org.mock-server
+ mockserver-netty
+ test
+
+
+ org.wildfly.client
+ wildfly-client-config
+ test
+
+
+
diff --git a/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContext.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContext.java
new file mode 100644
index 0000000000..500d03badc
--- /dev/null
+++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContext.java
@@ -0,0 +1,56 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2024 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.wildfly.security.dynamic.ssl;
+
+import org.wildfly.security.auth.client.ActiveSessionsSSLContext;
+
+import javax.net.ssl.SSLContext;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * SSLContext that resolves which SSLContext to use based on peer's host and port information.
+ *
+ * @author Diana Krepinska
+ */
+public final class DynamicSSLContext extends SSLContext implements ActiveSessionsSSLContext {
+
+ private static SSLContext resolverSSLContext(DynamicSSLContextSPI dynamicSSLContextSPIImpl) throws NoSuchAlgorithmException, DynamicSSLContextException {
+ return dynamicSSLContextSPIImpl.getConfiguredDefault() == null ?
+ SSLContext.getDefault() : dynamicSSLContextSPIImpl.getConfiguredDefault();
+ }
+
+ /**
+ * This constructor uses ServiceLoader to find provider of DynamicSSLContextSPI on classpath.
+ */
+ public DynamicSSLContext() throws NoSuchAlgorithmException {
+ // this does not use provider and protocol from DynamicSSLContextSPI implementation found on classpath
+ // to avoid this ServiceLoader.load would have to be called 3 times in separate static method
+ super(new DynamicSSLContextSpiImpl(), SSLContext.getDefault().getProvider(), SSLContext.getDefault().getProtocol());
+ }
+
+ /**
+ * This constructor uses received DynamicSSLContextSPI implementation or finds it on classpath if received is null.
+ *
+ * @param dynamicSSLContextSPIImpl DynamicSSLContextSPI implementation to use. If null then ServiceLoader is used to locate it on classpath.
+ */
+ public DynamicSSLContext(DynamicSSLContextSPI dynamicSSLContextSPIImpl) throws NoSuchAlgorithmException, DynamicSSLContextException {
+ super(new DynamicSSLContextSpiImpl(dynamicSSLContextSPIImpl),
+ resolverSSLContext(dynamicSSLContextSPIImpl).getProvider(),
+ resolverSSLContext(dynamicSSLContextSPIImpl).getProtocol());
+ }
+}
diff --git a/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextException.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextException.java
new file mode 100644
index 0000000000..a47dbea76b
--- /dev/null
+++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextException.java
@@ -0,0 +1,42 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2024 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.wildfly.security.dynamic.ssl;
+
+/**
+ * Exception to indicate a failure related to the DynamicSSLContext.
+ *
+ * @author Diana Krepinska
+ */
+public class DynamicSSLContextException extends Exception {
+ private static final long serialVersionUID = 894798122053539237L;
+
+ public DynamicSSLContextException() {
+ }
+
+ public DynamicSSLContextException(String msg) {
+ super(msg);
+ }
+
+ public DynamicSSLContextException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public DynamicSSLContextException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextImpl.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextImpl.java
new file mode 100644
index 0000000000..153f59c5a0
--- /dev/null
+++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextImpl.java
@@ -0,0 +1,91 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2024 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wildfly.security.dynamic.ssl;
+
+import org.kohsuke.MetaInfServices;
+import org.wildfly.security.auth.client.AuthenticationContext;
+import org.wildfly.security.auth.client.AuthenticationContextConfigurationClient;
+
+import javax.net.ssl.SSLContext;
+import java.net.URI;
+import java.security.AccessController;
+import java.security.GeneralSecurityException;
+import java.security.PrivilegedAction;
+import java.util.List;
+
+import static org.wildfly.common.Assert.checkNotNullParam;
+
+/**
+ * Elytron client implementation of DynamicSSLContextSPI. It uses configuration from either provided instance of AuthenticationContext
+ * or from current AuthenticationContext if a configuration was not provided.
+ *
+ * @author Diana Krepinska (Vilkolakova)
+ */
+@MetaInfServices(value = DynamicSSLContextSPI.class)
+public class DynamicSSLContextImpl implements DynamicSSLContextSPI {
+
+ private final AuthenticationContextConfigurationClient AUTH_CONTEXT_CLIENT =
+ AccessController.doPrivileged((PrivilegedAction) AuthenticationContextConfigurationClient::new);
+ private AuthenticationContext authenticationContext;
+ private SSLContext configuredDefaultSSLContext;
+ private List configuredSSLContexts;
+
+ public DynamicSSLContextImpl() throws GeneralSecurityException {
+ }
+
+ public DynamicSSLContextImpl(AuthenticationContext authenticationContext) throws GeneralSecurityException {
+ checkNotNullParam("authenticationContext", authenticationContext);
+ this.authenticationContext = authenticationContext;
+ this.configuredSSLContexts = AUTH_CONTEXT_CLIENT.getConfiguredSSLContexts(authenticationContext);
+ this.configuredDefaultSSLContext = AUTH_CONTEXT_CLIENT.getDefaultSSLContext(authenticationContext);
+ }
+
+ @Override
+ public SSLContext getConfiguredDefault() throws DynamicSSLContextException {
+ if (this.configuredDefaultSSLContext != null) {
+ return this.configuredDefaultSSLContext;
+ }
+ try {
+ return AUTH_CONTEXT_CLIENT.getDefaultSSLContext(AuthenticationContext.captureCurrent());
+ } catch (GeneralSecurityException e) {
+ throw ElytronMessages.log.cannotObtainDefaultSSLContext(e);
+ }
+ }
+
+ @Override
+ public List getConfiguredSSLContexts() throws DynamicSSLContextException {
+ if (this.configuredSSLContexts != null) {
+ return this.configuredSSLContexts;
+ }
+ try {
+ return AUTH_CONTEXT_CLIENT.getConfiguredSSLContexts(AuthenticationContext.captureCurrent());
+ } catch (GeneralSecurityException e) {
+ throw ElytronMessages.log.cannotObtainConfiguredSSLContexts(e);
+ }
+ }
+
+ @Override
+ public SSLContext getSSLContext(URI uri) throws DynamicSSLContextException {
+ try {
+ return AUTH_CONTEXT_CLIENT.getSSLContext(uri, authenticationContext == null ? AuthenticationContext.captureCurrent() : authenticationContext);
+ } catch (GeneralSecurityException e) {
+ throw ElytronMessages.log.cannotObtainSSLContextForGivenURI(e);
+ }
+ }
+}
diff --git a/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextSPI.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextSPI.java
new file mode 100644
index 0000000000..91985a60fe
--- /dev/null
+++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextSPI.java
@@ -0,0 +1,52 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2024 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wildfly.security.dynamic.ssl;
+
+import javax.net.ssl.SSLContext;
+import java.net.URI;
+import java.util.List;
+
+/**
+ * This interface provides configuration that is used by DynamicSSLContext.
+ *
+ * @author Diana Krepinska
+ */
+public interface DynamicSSLContextSPI {
+
+ /**
+ * Get SSLContext that will be used as a default, eg. when no URI is provided.
+ *
+ * @return configured default SSLContext
+ */
+ SSLContext getConfiguredDefault() throws DynamicSSLContextException;
+
+ /**
+ * Get list of all configured SSLContexts. This is used to obtain cipher suites supported by all SSLContexts.
+ *
+ * @return list of all configured SSLContexts
+ */
+ List getConfiguredSSLContexts() throws DynamicSSLContextException;
+
+ /**
+ * Get the SSLContext that matches the given URI.
+ *
+ * @return SSLContext that matches the given URI
+ */
+ SSLContext getSSLContext(URI uri) throws DynamicSSLContextException;
+}
diff --git a/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextSpiImpl.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextSpiImpl.java
new file mode 100644
index 0000000000..d3085b9636
--- /dev/null
+++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextSpiImpl.java
@@ -0,0 +1,145 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2024 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.wildfly.security.dynamic.ssl;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLContextSpi;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSessionContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+/**
+ * SSLContextSpi that uses ServiceLoader to find implementations of DynamicSSLContextSPI.
+ * DynamicSSLContextSPI implementation is being used to obtain authentication configuration for DynamicSSLContext.
+ * if no provider is found then SSLContext.getDefault() is used.
+ *
+ * @author Diana Krepinska
+ */
+final class DynamicSSLContextSpiImpl extends SSLContextSpi {
+
+ private final DynamicSSLContextSPI dynamicSSLContextImpl;
+ private volatile SSLSocketFactory sslSocketFactory;
+
+ DynamicSSLContextSpiImpl() {
+ this(null);
+ }
+
+ DynamicSSLContextSpiImpl(DynamicSSLContextSPI dynamicSSLContextSPIImpl) {
+ if (dynamicSSLContextSPIImpl != null) {
+ dynamicSSLContextImpl = dynamicSSLContextSPIImpl;
+ } else {
+ Iterator dynamicSSLContextSPIIterator = ServiceLoader.load(DynamicSSLContextSPI.class).iterator();
+ if (dynamicSSLContextSPIIterator.hasNext()) {
+ dynamicSSLContextImpl = dynamicSSLContextSPIIterator.next();
+ } else {
+ dynamicSSLContextImpl = null;
+ }
+ }
+ }
+
+ private SSLContext getConfiguredDefaultSSLContext() {
+ try {
+ if (dynamicSSLContextImpl != null) {
+ SSLContext configuredDefault = dynamicSSLContextImpl.getConfiguredDefault();
+ if (configuredDefault != null) {
+ return configuredDefault;
+ }
+ }
+ return SSLContext.getDefault();
+ } catch (NoSuchAlgorithmException | DynamicSSLContextException e) {
+ throw ElytronMessages.log.cannotObtainConfiguredDefaultSSLContext();
+ }
+ }
+
+ @Override
+ protected void engineInit(KeyManager[] keyManagers, TrustManager[] trustManagers, SecureRandom secureRandom) {
+ // initialization of SSL context is delegated to providers of {@link org.wildfly.security.dynamic.ssl.DynamicSSLContextSPI}
+ }
+
+ @Override
+ protected SSLSocketFactory engineGetSocketFactory() {
+ if (dynamicSSLContextImpl == null) {
+ return this.getConfiguredDefaultSSLContext().getSocketFactory();
+ }
+ if (sslSocketFactory == null) {
+ synchronized (this) {
+ if (sslSocketFactory == null) {
+ sslSocketFactory = new DynamicSSLSocketFactory(this.getConfiguredDefaultSSLContext().getSocketFactory(), dynamicSSLContextImpl);
+ }
+ }
+ }
+ return sslSocketFactory;
+ }
+
+ @Override
+ protected SSLServerSocketFactory engineGetServerSocketFactory() {
+ return this.getConfiguredDefaultSSLContext().getServerSocketFactory();
+ }
+
+ @Override
+ protected SSLEngine engineCreateSSLEngine() {
+ return this.getConfiguredDefaultSSLContext().createSSLEngine();
+ }
+
+ @Override
+ protected SSLEngine engineCreateSSLEngine(String host, int port) throws IllegalStateException {
+ try {
+ if (dynamicSSLContextImpl == null) {
+ return this.getConfiguredDefaultSSLContext().createSSLEngine(host, port);
+ }
+ SSLContext sslContext = dynamicSSLContextImpl
+ .getSSLContext(new URI(null, null, host, port, null, null, null));
+ if (sslContext == null) {
+ throw ElytronMessages.log.receivedSSLContextFromDynamicSSLContextProviderWasNull();
+ }
+ if (sslContext instanceof DynamicSSLContext && sslContext.getSocketFactory().equals(this.engineGetSocketFactory())) {
+ throw ElytronMessages.log.dynamicSSLContextCreatesLoop();
+ }
+ return sslContext.createSSLEngine(host, port);
+ } catch (URISyntaxException e) {
+ throw ElytronMessages.log.couldNotCreateURI();
+ } catch (DynamicSSLContextException e) {
+ throw ElytronMessages.log.couldNotCreateDynamicSSLContextEngine();
+ }
+ }
+
+ @Override
+ protected SSLSessionContext engineGetServerSessionContext() {
+ throw new UnsupportedOperationException(ElytronMessages.log.dynamicSSLContextDoesNotSupportSessions());
+ }
+
+ @Override
+ protected SSLSessionContext engineGetClientSessionContext() {
+ throw new UnsupportedOperationException(ElytronMessages.log.dynamicSSLContextDoesNotSupportSessions());
+ }
+
+ @Override
+ protected SSLParameters engineGetSupportedSSLParameters() {
+ return this.getConfiguredDefaultSSLContext().getSupportedSSLParameters();
+ }
+}
diff --git a/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLSocketFactory.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLSocketFactory.java
new file mode 100644
index 0000000000..a06badab0c
--- /dev/null
+++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLSocketFactory.java
@@ -0,0 +1,160 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2024 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.wildfly.security.dynamic.ssl;
+
+import org.wildfly.common.Assert;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * SSLSocketFactory that is being used by DynamicSSLContext.
+ *
+ * @author Diana Krepinska
+ */
+final class DynamicSSLSocketFactory extends SSLSocketFactory {
+
+ private DynamicSSLContextSPI dynamicSSLContextImpl;
+ private volatile String[] intersectionCipherSuite;
+ private SSLSocketFactory configuredDefaultSslSocketFactory;
+
+ DynamicSSLSocketFactory(SSLSocketFactory configuredDefaultSslSocketFactory, DynamicSSLContextSPI dynamicSSLContextImpl) {
+ super();
+ Assert.assertNotNull(configuredDefaultSslSocketFactory);
+ Assert.assertNotNull(dynamicSSLContextImpl);
+ this.configuredDefaultSslSocketFactory = configuredDefaultSslSocketFactory;
+ this.dynamicSSLContextImpl = dynamicSSLContextImpl;
+ }
+
+ @Override
+ public Socket createSocket() throws IOException {
+ return configuredDefaultSslSocketFactory.createSocket();
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port) throws IOException {
+ return createSocketBasedOnPeerInfo(null, port, address, null, null, null, null);
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException {
+ return createSocketBasedOnPeerInfo(host, port, null, null, null, null, null);
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localAddress, int localPort) throws IOException {
+ return createSocketBasedOnPeerInfo(host, port, null, localAddress, localPort, null, null);
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
+ return createSocketBasedOnPeerInfo(null, port, address, localAddress, localPort, null, null);
+ }
+
+ @Override
+ public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
+ return createSocketBasedOnPeerInfo(host, port, null, null, null, socket, autoClose);
+ }
+
+ @Override
+ public String[] getDefaultCipherSuites() {
+ return configuredDefaultSslSocketFactory.getDefaultCipherSuites();
+ }
+
+ @Override
+ public String[] getSupportedCipherSuites() {
+ String[] val = intersectionCipherSuite;
+ if (val == null) {
+ synchronized (this) {
+ val = intersectionCipherSuite;
+ if (intersectionCipherSuite == null) {
+ val = intersectionCipherSuite = getIntersection();
+ }
+ }
+ }
+ return val;
+ }
+
+ private Socket createSocketBasedOnPeerInfo(String hostname, Integer port, InetAddress address, InetAddress localAddress, Integer localPort, Socket socket, Boolean autoClose) throws IOException {
+ try {
+ SSLContext sslContext = this.dynamicSSLContextImpl.getSSLContext(new URI(null, null, hostname == null ? address.getHostName() : hostname, port, null, null, null));
+ if (sslContext == null) {
+ throw ElytronMessages.log.configuredSSLContextIsNull();
+ }
+ SSLSocketFactory socketFactory = sslContext.getSocketFactory();
+ if (socketFactory instanceof DynamicSSLSocketFactory && socketFactory.equals(this)) {
+ throw ElytronMessages.log.dynamicSSLContextCreatesLoop();
+ }
+ // resolve socket
+ if (socket != null && autoClose != null) {
+ return socketFactory.createSocket(socket, hostname, port, autoClose);
+ }
+
+ // resolves InetAddresses callbacks
+ if (address != null) {
+ return localAddress == null ?
+ socketFactory.createSocket(address, port) : socketFactory.createSocket(address, port, localAddress, localPort);
+ }
+ if (localAddress != null && localPort != null) {
+ return socketFactory.createSocket(hostname, port, localAddress, localPort);
+ }
+ return socketFactory.createSocket(hostname, port);
+ } catch (URISyntaxException e) {
+ throw new UnknownHostException(e.getMessage());
+ } catch (DynamicSSLContextException e) {
+ throw new IOException(e);
+ }
+ }
+
+ private String[] getIntersection() {
+ List sslContexts;
+ try {
+ sslContexts = dynamicSSLContextImpl.getConfiguredSSLContexts();
+ } catch (DynamicSSLContextException e) {
+ throw ElytronMessages.log.unableToGetConfiguredSSLContexts();
+ }
+ if (sslContexts == null) {
+ throw ElytronMessages.log.configuredSSLContextsAreNull();
+ }
+ Map counts = new HashMap<>();
+ List intersection = new ArrayList<>();
+ sslContexts.forEach(c -> {
+ String[] cipherSuites = c.getSocketFactory().getSupportedCipherSuites();
+ for (String cipherSuite : cipherSuites) {
+ counts.merge(cipherSuite, 1, (a, b) -> a + b);
+ }
+ });
+ List finalSslContexts = sslContexts;
+ counts.forEach((c, v) -> {
+ if (finalSslContexts.size() == v) {
+ intersection.add(c);
+ }
+ });
+ return intersection.toArray(new String[0]);
+ }
+}
diff --git a/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/ElytronMessages.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/ElytronMessages.java
new file mode 100644
index 0000000000..feab5f75ee
--- /dev/null
+++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/ElytronMessages.java
@@ -0,0 +1,77 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2024 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.wildfly.security.dynamic.ssl;
+
+import org.jboss.logging.BasicLogger;
+import org.jboss.logging.Logger;
+import org.jboss.logging.annotations.Cause;
+import org.jboss.logging.annotations.Message;
+import org.jboss.logging.annotations.MessageLogger;
+import org.jboss.logging.annotations.ValidIdRange;
+import org.jboss.logging.annotations.ValidIdRanges;
+
+/**
+ * Log messages and exceptions for Elytron.
+ *
+ * @author David M. Lloyd
+ * @author Darran Lofthouse
+ */
+@MessageLogger(projectCode = "ELY", length = 5)
+@ValidIdRanges({
+ @ValidIdRange(min = 21000, max = 21999)
+})
+interface ElytronMessages extends BasicLogger {
+
+ ElytronMessages log = Logger.getMessageLogger(ElytronMessages.class, "org.wildfly.security");
+
+ @Message(id = 21000, value = "DynamicSSLContext creates loop")
+ IllegalStateException dynamicSSLContextCreatesLoop();
+
+ @Message(id = 21001, value = "Received SSLContext from DynamicSSLContextProvider was null")
+ IllegalStateException receivedSSLContextFromDynamicSSLContextProviderWasNull();
+
+ @Message(id = 21002, value = "Dynamic SSLContext does not support sessions")
+ UnsupportedOperationException dynamicSSLContextDoesNotSupportSessions();
+
+ @Message(id = 21003, value = "Provider for DynamicSSLContextSPI threw an exception when getting configured SSLContexts")
+ IllegalStateException unableToGetConfiguredSSLContexts();
+
+ @Message(id = 21004, value = "Provider for DynamicSSLContextSPI returned null configured SSLContexts")
+ IllegalStateException configuredSSLContextsAreNull();
+
+ @Message(id = 21005, value = "Cannot obtain default SSLContext from DynamicSSLContext implementation")
+ IllegalStateException cannotObtainConfiguredDefaultSSLContext();
+
+ @Message(id = 21006, value = "Could not create URI from host and port")
+ IllegalStateException couldNotCreateURI();
+
+ @Message(id = 21007, value = "Could not create dynamic ssl context engine")
+ IllegalStateException couldNotCreateDynamicSSLContextEngine();
+
+ @Message(id = 21008, value = "Provider for DynamicSSLContextSPI returned null SSLContext")
+ IllegalStateException configuredSSLContextIsNull();
+
+ @Message(id = 21009, value = "Obtaining of the default SSLContext from current authentication context resulted in exception.")
+ DynamicSSLContextException cannotObtainDefaultSSLContext(@Cause Throwable cause);
+
+ @Message(id = 21010, value = "Obtaining of all configured SSLContexts from current authentication context resulted in exception.")
+ DynamicSSLContextException cannotObtainConfiguredSSLContexts(@Cause Throwable cause);
+
+ @Message(id = 21011, value = "Obtaining of the SSLContext from current authentication context and provided URI resulted in exception.")
+ DynamicSSLContextException cannotObtainSSLContextForGivenURI(@Cause Throwable cause);
+}
diff --git a/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextTest.java b/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextTest.java
new file mode 100644
index 0000000000..cd74cba609
--- /dev/null
+++ b/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextTest.java
@@ -0,0 +1,454 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2024 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wildfly.security.dynamic.ssl;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.wildfly.security.auth.client.AuthenticationContext;
+import org.wildfly.security.auth.client.AuthenticationContextConfigurationClient;
+import org.wildfly.security.auth.client.ElytronXmlParser;
+import org.wildfly.security.auth.client.InvalidAuthenticationConfigurationException;
+import org.wildfly.security.auth.client.MatchRule;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.GeneralSecurityException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivilegedAction;
+import java.security.cert.CertificateException;
+
+import static java.security.AccessController.doPrivileged;
+import static org.wildfly.security.dynamic.ssl.SSLServerSocketTestInstance.ServerThread.STATUS_OK;
+
+/**
+ * Functional tests of DynamicSSLContext.
+ *
+ * @author Diana Krepinska (Vilkolakova)
+ */
+public class DynamicSSLContextTest {
+ static final String RESOURCES = "./target/keystores/";
+ private static org.wildfly.security.dynamic.ssl.SSLServerSocketTestInstance sslServerSocketTestInstancePort10001;
+ private static org.wildfly.security.dynamic.ssl.SSLServerSocketTestInstance sslServerSocketTestInstancePort10002;
+ private static org.wildfly.security.dynamic.ssl.SSLServerSocketTestInstance sslServerSocketTestInstancePort10003;
+ private static org.wildfly.security.dynamic.ssl.SSLServerSocketTestInstance sslServerSocketTestInstancePort10000Default;
+
+ @BeforeClass
+ public static void before() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException {
+ DynamicSSLTestUtils.createKeystores();
+ sslServerSocketTestInstancePort10001 = new SSLServerSocketTestInstance(RESOURCES + "server1.keystore.jks", RESOURCES + "server1.truststore.jks", 10001);
+ sslServerSocketTestInstancePort10002 = new SSLServerSocketTestInstance(RESOURCES + "server2.keystore.jks", RESOURCES + "server2.truststore.jks", 10002);
+ sslServerSocketTestInstancePort10003 = new SSLServerSocketTestInstance(RESOURCES + "server3.keystore.jks", RESOURCES + "server3.truststore.jks", 10003);
+ sslServerSocketTestInstancePort10000Default = new SSLServerSocketTestInstance(RESOURCES + "default-server.keystore.jks", RESOURCES + "default-server.truststore.jks", 10000);
+
+ sslServerSocketTestInstancePort10001.run();
+ sslServerSocketTestInstancePort10002.run();
+ sslServerSocketTestInstancePort10003.run();
+ sslServerSocketTestInstancePort10000Default.run();
+ }
+
+ @AfterClass
+ public static void after() {
+ sslServerSocketTestInstancePort10001.stop();
+ sslServerSocketTestInstancePort10002.stop();
+ sslServerSocketTestInstancePort10003.stop();
+ sslServerSocketTestInstancePort10000Default.stop();
+ org.wildfly.security.dynamic.ssl.DynamicSSLTestUtils.deleteKeystores();
+ }
+
+ @Test
+ public void smokeTestWith4Servers() throws NoSuchAlgorithmException {
+ SSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory();
+ getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml").run(() -> {
+ try {
+ SSLSocket clientSslSocket1 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10001);
+ clientSslSocket1.setUseClientMode(true);
+ clientSslSocket1.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket1);
+ clientSslSocket1.close();
+
+ SSLSocket clientSslSocket2 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10002);
+ clientSslSocket2.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket2);
+ clientSslSocket2.close();
+
+ SSLSocket clientSslSocket3 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10003);
+ clientSslSocket3.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket3);
+ clientSslSocket3.close();
+
+ SSLSocket clientSslSocket4 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10000);
+ clientSslSocket4.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket4);
+ clientSslSocket4.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+ }
+
+ @Test
+ public void smokeTestAuthenticationContextPassedExplicitly() throws DynamicSSLContextException, GeneralSecurityException {
+ SSLContext dynamicSSLContext = new DynamicSSLContext(new DynamicSSLContextImpl(getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml")));
+ SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory();
+ try {
+ SSLSocket clientSslSocket1 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10001);
+ clientSslSocket1.setUseClientMode(true);
+ clientSslSocket1.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket1);
+ clientSslSocket1.close();
+
+ SSLSocket clientSslSocket2 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10002);
+ clientSslSocket2.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket2);
+ clientSslSocket2.close();
+
+ SSLSocket clientSslSocket3 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10003);
+ clientSslSocket3.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket3);
+ clientSslSocket3.close();
+
+ SSLSocket clientSslSocket4 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10000);
+ clientSslSocket4.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket4);
+ clientSslSocket4.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ }
+
+ @Test(expected = SocketException.class)
+ public void smokeTestWithoutElytronClientContextWillFail() throws NoSuchAlgorithmException, IOException {
+ SSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory();
+ SSLSocket clientSslSocket1 = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10002);
+ clientSslSocket1.setUseClientMode(true);
+ clientSslSocket1.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket1);
+ clientSslSocket1.close();
+ }
+
+ @Test
+ public void testCreateSocketByInetAddressPort() throws NoSuchAlgorithmException {
+ SSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory();
+ getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml").run(() -> {
+ try {
+ SSLSocket clientSslSocket = (SSLSocket) dynamicSSLContextSocketFactory.createSocket(InetAddress.getByName("localhost"), 10002);
+ clientSslSocket.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket);
+ clientSslSocket.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+ }
+
+ @Test
+ public void testCreateSocketByHostPortLocalAddressLocalPort() throws NoSuchAlgorithmException {
+ SSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory();
+ getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml").run(() -> {
+ try {
+ SSLSocket clientSslSocket = (SSLSocket) dynamicSSLContextSocketFactory.createSocket("localhost", 10001, InetAddress.getByName("localhost"), 0);
+ clientSslSocket.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket);
+ clientSslSocket.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+ }
+
+ @Test
+ public void testCreateSocketByAddressPortLocalAddressLocalPort() throws NoSuchAlgorithmException {
+ SSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory();
+ getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml").run(() -> {
+ try {
+ SSLSocket clientSslSocket = (SSLSocket) dynamicSSLContextSocketFactory.createSocket(InetAddress.getByName("localhost"), 10001, InetAddress.getByName("localhost"), 12555);
+ clientSslSocket.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket);
+ clientSslSocket.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+ }
+
+ @Test
+ public void testCreateSocketBySocketHostPortAutoCloseTrue() throws NoSuchAlgorithmException {
+ SSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory();
+ getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml").run(() -> {
+ try {
+ Socket plainSocket = new Socket();
+ plainSocket.connect(new InetSocketAddress("localhost", 10001));
+ SSLSocket clientSslSocket = (SSLSocket) dynamicSSLContextSocketFactory.createSocket(plainSocket, "localhost", 10001, true);
+ clientSslSocket.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket);
+ clientSslSocket.close();
+ plainSocket.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+ }
+
+ @Test
+ public void testCreateSocketsBySocketHostPortAutoCloseFalse() throws NoSuchAlgorithmException {
+ SSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLSocketFactory dynamicSSLContextSocketFactory = dynamicSSLContext.getSocketFactory();
+ getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml").run(() -> {
+ try {
+ Socket plainSocket = new Socket();
+ plainSocket.connect(new InetSocketAddress("localhost", 10001));
+ SSLSocket clientSslSocket = (SSLSocket) dynamicSSLContextSocketFactory.createSocket(plainSocket, "localhost", 10001, false);
+ clientSslSocket.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket);
+ clientSslSocket.close();
+ plainSocket.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+ }
+
+ @Test
+ public void testCreateSocketbyHostAndPortAndConfiguredSSLParams2() throws NoSuchAlgorithmException {
+ DynamicSSLContext dynamicSSLContext = new DynamicSSLContext();
+ AuthenticationContext context = getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml");
+ context.run(() -> {
+ try {
+ DynamicSSLSocketFactory dynamicSSLContextSocketFactory = (DynamicSSLSocketFactory) dynamicSSLContext.getSocketFactory();
+ dynamicSSLContext.getDefaultSSLParameters().setCipherSuites(new String[]{"TLS_RSA_WITH_AES_128_CBC_SHA256"});
+ SSLSocket clientSslSocket = (SSLSocket) dynamicSSLContextSocketFactory.createSocket();
+ SSLParameters sslParameters = clientSslSocket.getSSLParameters();
+ sslParameters.setCipherSuites(new String[]{"TLS_RSA_WITH_AES_128_CBC_SHA256"});
+ clientSslSocket.setSSLParameters(sslParameters);
+ dynamicSSLContext.getDefaultSSLParameters().setCipherSuites(new String[]{"TLS_RSA_WITH_AES_128_CBC_SHA256"});
+ clientSslSocket.connect(new InetSocketAddress("localhost", 10000));
+ clientSslSocket.startHandshake();
+ Assert.assertEquals("TLS_RSA_WITH_AES_128_CBC_SHA256", clientSslSocket.getSession().getCipherSuite());
+ checkOutputIsOK(clientSslSocket);
+ clientSslSocket.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void checkExceptionThrownClientSessionContext() throws Exception {
+ SSLContext sslContext = new DynamicSSLContext();
+ sslContext.getClientSessionContext();
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void checkExceptionThrownServerSessionContext() throws Exception {
+ SSLContext sslContext = new DynamicSSLContext();
+ sslContext.getServerSessionContext();
+ }
+
+ // thorough testing of sslEngine would need a lot of code with socket implementation that is pretty low level
+ // it is reasonable to assume that it is being tested anyway since sockets created by SSLSocketFactory seem to always use this SSLEngine
+ // here I at least test that the SSLEngine was created with correct host and port
+ @Test
+ public void smokeTestCorrectSSLEngineIsUsed() throws NoSuchAlgorithmException {
+ DynamicSSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLEngine sslEngine = dynamicSSLContext.createSSLEngine("localhost", 10000);
+ Assert.assertEquals("localhost", sslEngine.getPeerHost());
+ Assert.assertEquals(10000, sslEngine.getPeerPort());
+
+ SSLEngine sslEngine2 = dynamicSSLContext.createSSLEngine();
+ Assert.assertNull(sslEngine2.getPeerHost());
+ Assert.assertEquals(-1, sslEngine2.getPeerPort());
+ }
+
+ @Test
+ public void smokeTestIntersectionOfCipherSuites() throws NoSuchAlgorithmException {
+ DynamicSSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLServerSocketTestInstance testSSLServerSingleCipherSuite =
+ new SSLServerSocketTestInstance(RESOURCES + "default-server.keystore.jks", RESOURCES + "default-server.truststore.jks", 10004);
+ testSSLServerSingleCipherSuite.setConfiguredEnabledCipherSuites(new String[]{"TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA256"});
+ testSSLServerSingleCipherSuite.run();
+ AuthenticationContext context = getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml");
+ context.run(() -> {
+ try {
+ SSLSocket clientSslSocket = (SSLSocket) dynamicSSLContext.getSocketFactory().createSocket();
+ SSLParameters sslParameters = clientSslSocket.getSSLParameters();
+ sslParameters.setCipherSuites(new String[]{"TLS_RSA_WITH_AES_256_CBC_SHA256"});
+ clientSslSocket.setSSLParameters(sslParameters);
+ clientSslSocket.connect(new InetSocketAddress("localhost", 10000));
+ clientSslSocket.startHandshake();
+ Assert.assertEquals("TLS_RSA_WITH_AES_256_CBC_SHA256", clientSslSocket.getSession().getCipherSuite());
+ checkOutputIsOK(clientSslSocket);
+ clientSslSocket.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+ }
+
+ @Test
+ public void testChangingAuthenticationContextsTest() throws NoSuchAlgorithmException {
+ DynamicSSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLSocketFactory socketFactory = dynamicSSLContext.getSocketFactory();
+
+ AuthenticationContext.empty().withSsl(MatchRule.ALL.matchPort(10001), () -> DynamicSSLTestUtils
+ .createSSLContext(RESOURCES + "client1.keystore.jks", RESOURCES + "client1.truststore.jks", "secret")).run(() -> {
+ try {
+ Socket clientSslSocket = socketFactory.createSocket("localhost", 10001);
+ checkOutputIsOK((SSLSocket) clientSslSocket);
+ clientSslSocket.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+
+ AuthenticationContext.empty().withSsl(MatchRule.ALL.matchPort(10002), () -> DynamicSSLTestUtils
+ .createSSLContext(RESOURCES + "client2.keystore.jks", RESOURCES + "client2.truststore.jks", "secret")).run(() -> {
+ try {
+ Socket clientSslSocket = socketFactory.createSocket("localhost", 10002);
+ checkOutputIsOK((SSLSocket) clientSslSocket);
+ clientSslSocket.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+
+ AuthenticationContext.empty().withSsl(MatchRule.ALL.matchPort(10003), () -> DynamicSSLTestUtils
+ .createSSLContext(RESOURCES + "client3.keystore.jks", RESOURCES + "client3.truststore.jks", "secret")).run(() -> {
+ try {
+ Socket clientSslSocket = socketFactory.createSocket("localhost", 10003);
+ checkOutputIsOK((SSLSocket) clientSslSocket);
+ clientSslSocket.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ });
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testThrowAnExceptionWhenLoop() throws NoSuchAlgorithmException {
+
+ DynamicSSLContext dynamicSSLContext = new DynamicSSLContext();
+ SSLSocketFactory socketFactory = dynamicSSLContext.getSocketFactory();
+ SSLContext previousDefaultSSLContext = SSLContext.getDefault();
+ SSLContext.setDefault(dynamicSSLContext);
+ AuthenticationContext.empty().withSsl(MatchRule.ALL.matchPort(10000), () -> DynamicSSLTestUtils
+ .createSSLContext(RESOURCES + "client1.keystore.jks", RESOURCES + "client1.truststore.jks", "secret")).run(() -> {
+ try {
+ Socket clientSslSocket = socketFactory.createSocket("localhost", 12345);
+ checkOutputIsOK((SSLSocket) clientSslSocket);
+ clientSslSocket.close();
+ } catch (IOException e) {
+ Assert.assertEquals("fine", e.getMessage());
+ } finally {
+ SSLContext.setDefault(previousDefaultSSLContext);
+ }
+ });
+ }
+
+
+ @Test
+ public void testPreconfiguredDefault() throws NoSuchAlgorithmException {
+ DynamicSSLContext dynamicSSLContext = new DynamicSSLContext();
+ final AuthenticationContextConfigurationClient AUTH_CONTEXT_CLIENT =
+ AccessController.doPrivileged((PrivilegedAction) AuthenticationContextConfigurationClient::new);
+ try {
+
+ AuthenticationContext contextWithConfiguredDefault = getAuthenticationContext("wildfly-config-dynamic-ssl-test.xml");
+ AuthenticationContext contextWithoutConfiguredDefault = getAuthenticationContext("wildfly-config-dynamic-ssl-test-without-default-sslcontext.xml");
+
+ SSLContext preconfiguredDefault = AUTH_CONTEXT_CLIENT.getDefaultSSLContext(contextWithConfiguredDefault);
+ SSLContext jvmDefault = AUTH_CONTEXT_CLIENT.getDefaultSSLContext(contextWithoutConfiguredDefault);
+
+ Assert.assertEquals(jvmDefault, SSLContext.getDefault());
+
+ // AuthenticationContextConfigurationClient always creates new instances. So we can check that preconfigured SSLContext was received
+ // correctly by successful connection to the host and port that requires that ssl context.
+
+ // We first test configured default by using createSocket(host, port) with port not specified in any match rules.
+ // Second we use empty createSocket method that will later connect to the same host and port successfully.
+
+ contextWithConfiguredDefault.run(() -> {
+ try {
+ SSLSocket clientSslSocket1 = (SSLSocket) preconfiguredDefault.getSocketFactory().createSocket("localhost", 10000);
+ clientSslSocket1.setReuseAddress(true);
+ checkOutputIsOK(clientSslSocket1);
+ clientSslSocket1.close();
+ //preconfigured default will be used to create socket since no host and port was provided
+ SSLSocket clientSocketWithDynamicDefaultSSLContext = (SSLSocket) dynamicSSLContext.getSocketFactory().createSocket();
+ clientSocketWithDynamicDefaultSSLContext.setUseClientMode(true);
+ // configured default is the one which passes for this host and port
+ clientSocketWithDynamicDefaultSSLContext.connect(new InetSocketAddress("localhost", 10000));
+ checkOutputIsOK(clientSocketWithDynamicDefaultSSLContext);
+ clientSocketWithDynamicDefaultSSLContext.close();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ }
+ );
+
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ }
+ }
+
+ private void checkOutputIsOK(SSLSocket clientSslSocket) throws IOException {
+ PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(clientSslSocket.getOutputStream()));
+ printWriter.println("Client Hello");
+ printWriter.flush();
+ InputStream inputStream = clientSslSocket.getInputStream();
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
+ String line = bufferedReader.readLine().trim();
+ Assert.assertEquals(STATUS_OK, line);
+ }
+
+ private AuthenticationContext getAuthenticationContext(String path) {
+ return doPrivileged((PrivilegedAction) () -> {
+ URL config = getClass().getResource(path);
+ try {
+ return ElytronXmlParser.parseAuthenticationClientConfiguration(config.toURI()).create();
+ } catch (Exception e) {
+ Assert.assertEquals("fine", e.getMessage());
+ throw new InvalidAuthenticationConfigurationException(e);
+ }
+ });
+ }
+}
diff --git a/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLTestUtils.java b/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLTestUtils.java
new file mode 100644
index 0000000000..3f73dec293
--- /dev/null
+++ b/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLTestUtils.java
@@ -0,0 +1,214 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2024 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.wildfly.security.dynamic.ssl;
+
+import org.junit.Assert;
+import org.wildfly.security.x500.cert.BasicConstraintsExtension;
+import org.wildfly.security.x500.cert.X509CertificateBuilder;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.security.auth.x500.X500Principal;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+/**
+ * Utility class for DynamicSSLContextTest class.
+ *
+ * @author Diana Krepinska (Vilkolakova)
+ */
+public class DynamicSSLTestUtils {
+
+ private static final String CLIENT_ALIAS = "client";
+ private static final String LOCALHOST_ALIAS = "localhost";
+ private static final String KEYSTORE_TYPE = "JKS";
+ private static final String SHA_1_WITH_RSA = "SHA1withRSA";
+ private static final String TLS_PROTOCOL_VERSION = "TLSv1.2";
+ public static final String KEY_MANAGER_FACTORY_ALGORITHM = "SunX509";
+ private static char[] PASSWORD = "secret".toCharArray();
+ private static File KEYSTORES_DIR = new File("./target/keystores");
+
+ private static String CLIENT1_KEYSTORE_FILENAME = "client1.keystore.jks";
+ private static String CLIENT1_TRUSTSTORE_FILENAME ="client1.truststore.jks";
+ private static String SERVER1_KEYSTORE_FILENAME = "server1.keystore.jks";
+ private static String SERVER1_TRUSTSTORE_FILENAME = "server1.truststore.jks";
+
+ private static String CLIENT2_KEYSTORE_FILENAME = "client2.keystore.jks";
+ private static String CLIENT2_TRUSTSTORE_FILENAME ="client2.truststore.jks";
+ private static String SERVER2_KEYSTORE_FILENAME = "server2.keystore.jks";
+ private static String SERVER2_TRUSTSTORE_FILENAME = "server2.truststore.jks";
+
+ private static String CLIENT3_KEYSTORE_FILENAME = "client3.keystore.jks";
+ private static String CLIENT3_TRUSTSTORE_FILENAME ="client3.truststore.jks";
+ private static String SERVER3_KEYSTORE_FILENAME = "server3.keystore.jks";
+ private static String SERVER3_TRUSTSTORE_FILENAME = "server3.truststore.jks";
+
+ private static String DEFAULT_CLIENT_KEYSTORE_FILENAME = "default-client.keystore.jks";
+ private static String DEFAULT_CLIENT_TRUSTSTORE_FILENAME ="default-client.truststore.jks";
+ private static String DEFAULT_SERVER_KEYSTORE_FILENAME = "default-server.keystore.jks";
+ private static String DEFAULT_SERVER_TRUSTSTORE_FILENAME = "default-server.truststore.jks";
+
+ static SSLContext createSSLContext(String keystorePath, String truststorePath, String password) {
+ try {
+ KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
+ keyStore.load(new FileInputStream(keystorePath), password.toCharArray());
+
+ // Create key manager
+ KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM);
+ keyManagerFactory.init(keyStore, password.toCharArray());
+ KeyManager[] km = keyManagerFactory.getKeyManagers();
+
+ KeyStore trustStore = KeyStore.getInstance(KEYSTORE_TYPE);
+ trustStore.load(new FileInputStream(truststorePath), password.toCharArray());
+ // Create trust manager
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM);
+ trustManagerFactory.init(trustStore);
+ TrustManager[] tm = trustManagerFactory.getTrustManagers();
+
+ // Initialize SSLContext
+ SSLContext sslContext = SSLContext.getInstance(TLS_PROTOCOL_VERSION);
+ sslContext.init(km, tm, null);
+
+ return sslContext;
+ } catch (Exception ex) {
+ Assert.fail();
+ }
+ return null;
+ }
+
+ static void createKeystores() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
+ if (!KEYSTORES_DIR.exists()) {
+ KEYSTORES_DIR.mkdirs();
+ }
+ generateTwoWaySSLKeystoresAndTruststores(CLIENT1_KEYSTORE_FILENAME, SERVER1_KEYSTORE_FILENAME, CLIENT1_TRUSTSTORE_FILENAME, SERVER1_TRUSTSTORE_FILENAME);
+ generateTwoWaySSLKeystoresAndTruststores(CLIENT2_KEYSTORE_FILENAME, SERVER2_KEYSTORE_FILENAME, CLIENT2_TRUSTSTORE_FILENAME, SERVER2_TRUSTSTORE_FILENAME);
+ generateTwoWaySSLKeystoresAndTruststores(CLIENT3_KEYSTORE_FILENAME, SERVER3_KEYSTORE_FILENAME, CLIENT3_TRUSTSTORE_FILENAME, SERVER3_TRUSTSTORE_FILENAME);
+ generateTwoWaySSLKeystoresAndTruststores(DEFAULT_CLIENT_KEYSTORE_FILENAME, DEFAULT_SERVER_KEYSTORE_FILENAME, DEFAULT_CLIENT_TRUSTSTORE_FILENAME, DEFAULT_SERVER_TRUSTSTORE_FILENAME);
+ }
+
+ private static void generateTwoWaySSLKeystoresAndTruststores(String clientKeystoreFilename, String serverKeystoreFilename,
+ String clientTruststoreFilename, String serverTruststoreFilename) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
+ // Generates client certificate and keystore
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ KeyStore clientKeyStore = KeyStore.getInstance(KEYSTORE_TYPE);
+ clientKeyStore.load(null, null);
+
+ KeyPair clientKeyPair = keyPairGenerator.generateKeyPair();
+ PrivateKey signingKey = clientKeyPair.getPrivate();
+ PublicKey publicKey = clientKeyPair.getPublic();
+
+ X500Principal testClient10DN = new X500Principal("CN=" + CLIENT_ALIAS);
+ X509Certificate clientCertificate = new X509CertificateBuilder()
+ .setIssuerDn(testClient10DN)
+ .setSubjectDn(new X500Principal("OU=Elytron"))
+ .setSignatureAlgorithmName(SHA_1_WITH_RSA)
+ .setSigningKey(signingKey)
+ .setPublicKey(publicKey)
+ .setSerialNumber(new BigInteger("3"))
+ .addExtension(new BasicConstraintsExtension(false, false, -1))
+ .build();
+ clientKeyStore.setKeyEntry(CLIENT_ALIAS, signingKey, PASSWORD, new X509Certificate[]{clientCertificate});
+
+
+ // Generates server certificate and keystore
+ KeyStore serverKeyStore = KeyStore.getInstance(KEYSTORE_TYPE);
+ serverKeyStore.load(null, null);
+
+ KeyPair serverKeyPair = keyPairGenerator.generateKeyPair();
+ PrivateKey serverSigningKey = serverKeyPair.getPrivate();
+ PublicKey serverPublicKey = serverKeyPair.getPublic();
+
+ X500Principal testServer10DN = new X500Principal("CN=" + LOCALHOST_ALIAS);
+ X509Certificate serverCertificate = new X509CertificateBuilder()
+ .setIssuerDn(testServer10DN)
+ .setSubjectDn(new X500Principal("OU=Elytron"))
+ .setSignatureAlgorithmName(SHA_1_WITH_RSA)
+ .setSigningKey(serverSigningKey)
+ .setPublicKey(serverPublicKey)
+ .setSerialNumber(new BigInteger("4"))
+ .addExtension(new BasicConstraintsExtension(false, false, -1))
+ .build();
+ serverKeyStore.setKeyEntry(LOCALHOST_ALIAS, serverSigningKey, PASSWORD, new X509Certificate[]{serverCertificate});
+
+ File clientKeystoreFile = new File(KEYSTORES_DIR, clientKeystoreFilename);
+ try (FileOutputStream clientStream = new FileOutputStream(clientKeystoreFile)) {
+ clientKeyStore.store(clientStream, PASSWORD);
+ }
+
+ File serverKeystoreFile = new File(KEYSTORES_DIR, serverKeystoreFilename);
+ try (FileOutputStream serverStream = new FileOutputStream(serverKeystoreFile)) {
+ serverKeyStore.store(serverStream, PASSWORD);
+ }
+
+ // create truststores
+ KeyStore clientTrustStore = KeyStore.getInstance(KEYSTORE_TYPE);
+ clientTrustStore.load(null, null);
+
+ KeyStore serverTrustStore = KeyStore.getInstance(KEYSTORE_TYPE);
+ serverTrustStore.load(null, null);
+ clientTrustStore.setCertificateEntry(LOCALHOST_ALIAS, serverKeyStore.getCertificate(LOCALHOST_ALIAS));
+ serverTrustStore.setCertificateEntry(CLIENT_ALIAS, clientKeyStore.getCertificate(CLIENT_ALIAS) );
+
+ File clientTrustFile = new File(KEYSTORES_DIR, clientTruststoreFilename);
+ try (FileOutputStream clientStream = new FileOutputStream(clientTrustFile)) {
+ clientTrustStore.store(clientStream, PASSWORD);
+ }
+
+ File serverTrustFile = new File(KEYSTORES_DIR, serverTruststoreFilename);
+ try (FileOutputStream serverStream = new FileOutputStream(serverTrustFile)) {
+ serverTrustStore.store(serverStream, PASSWORD);
+ }
+ }
+
+ public static void deleteKeystores() {
+ new File(KEYSTORES_DIR, CLIENT1_KEYSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, CLIENT1_TRUSTSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, CLIENT2_KEYSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, CLIENT2_TRUSTSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, CLIENT3_KEYSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, CLIENT3_TRUSTSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, DEFAULT_CLIENT_KEYSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, DEFAULT_CLIENT_TRUSTSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, SERVER1_KEYSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, SERVER1_TRUSTSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, SERVER2_KEYSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, SERVER2_TRUSTSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, SERVER3_KEYSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, SERVER3_TRUSTSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, DEFAULT_SERVER_KEYSTORE_FILENAME).delete();
+ new File(KEYSTORES_DIR, DEFAULT_SERVER_TRUSTSTORE_FILENAME).delete();
+ KEYSTORES_DIR.delete();
+ }
+}
diff --git a/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/SSLServerSocketTestInstance.java b/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/SSLServerSocketTestInstance.java
new file mode 100644
index 0000000000..e404fd3e62
--- /dev/null
+++ b/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/SSLServerSocketTestInstance.java
@@ -0,0 +1,138 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2024 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.wildfly.security.dynamic.ssl;
+
+import okhttp3.TlsVersion;
+import org.junit.Assert;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSocket;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.InetSocketAddress;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Utility class for running SSLServerSocket instance for testing.
+ *
+ * @author Diana Krepinska (Vilkolakova)
+ */
+public class SSLServerSocketTestInstance {
+
+ private int port;
+ private String keystorePath;
+ private String truststorePath;
+ private String[] configuredEnabledCipherSuites;
+ private SSLServerSocket sslServerSocket;
+ private AtomicBoolean running = new AtomicBoolean(false);
+ private Thread serverThread;
+
+ public SSLServerSocketTestInstance(String pathToKeystore, String pathToTruststore, int port) {
+ this.keystorePath = pathToKeystore;
+ this.truststorePath = pathToTruststore;
+ this.port = port;
+ }
+
+ void setConfiguredEnabledCipherSuites(String[] configuredEnabledCipherSuite) {
+ this.configuredEnabledCipherSuites = configuredEnabledCipherSuite;
+ }
+
+ public void run() {
+ String password = "secret";
+ SSLContext sslContext = DynamicSSLTestUtils.createSSLContext(this.keystorePath, this.truststorePath, password);
+ try {
+ SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
+ sslServerSocket = (javax.net.ssl.SSLServerSocket) sslServerSocketFactory.createServerSocket();
+ sslServerSocket.setNeedClientAuth(true);
+ sslServerSocket.setUseClientMode(false);
+ sslServerSocket.setWantClientAuth(true);
+ sslServerSocket.setEnabledProtocols(new String[]{
+ TlsVersion.TLS_1_2.javaName(),
+ TlsVersion.TLS_1_3.javaName()
+ });
+ if (configuredEnabledCipherSuites != null) {
+ sslServerSocket.setEnabledCipherSuites(configuredEnabledCipherSuites);
+ }
+ sslServerSocket.bind(new InetSocketAddress("localhost", port));
+ serverThread = new Thread(() -> {
+ running.set(true);
+ while (running.get()) {
+ SSLSocket sslSocket;
+ try {
+ sslSocket = (SSLSocket) sslServerSocket.accept();
+ new Thread(new ServerThread(sslSocket)).start();
+ } catch (Exception e) {
+ Assert.fail();
+ }
+ }
+ });
+ serverThread.start();
+ } catch (Exception ex) {
+ Assert.fail();
+ } finally {
+ running.set(false);
+ }
+ }
+
+ public void stop() {
+ running.set(false);
+ }
+
+ // Thread handling the socket from client
+ public static class ServerThread implements Runnable {
+ public static final String STATUS_OK = "HTTP/1.1 200 OK";
+ private SSLSocket sslSocket;
+ AtomicBoolean running = new AtomicBoolean(false);
+
+ ServerThread(SSLSocket sslSocket) {
+ this.sslSocket = sslSocket;
+ }
+
+ public void run() {
+ try {
+ // wait for client's message first so that the first client message will trigger handshake.
+ // This way client can set its preferences in SSLParams after creation of bound createSocket(host,port) without server triggering handshake before.
+ running.set(true);
+ sslSocket.startHandshake();
+ InputStream inputStream = sslSocket.getInputStream();
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
+ while (running.get()) {
+ if ((bufferedReader.readLine()).equals("Client Hello")) {
+ break;
+ }
+ }
+ // if successful return 200
+ PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(sslSocket.getOutputStream()));
+ printWriter.println(STATUS_OK);
+ printWriter.flush();
+ sslSocket.close();
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ Assert.fail();
+ } finally {
+ running.set(false);
+ }
+ }
+ }
+}
diff --git a/dynamic-ssl/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test-without-default-sslcontext.xml b/dynamic-ssl/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test-without-default-sslcontext.xml
new file mode 100644
index 0000000000..07bfe16b24
--- /dev/null
+++ b/dynamic-ssl/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test-without-default-sslcontext.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dynamic-ssl/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test.xml b/dynamic-ssl/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test.xml
new file mode 100644
index 0000000000..5e01db7044
--- /dev/null
+++ b/dynamic-ssl/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test.xml
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
index 57221bdbfd..2848bc8b07 100644
--- a/pom.xml
+++ b/pom.xml
@@ -522,6 +522,11 @@
wildfly-elytron-digest
${project.version}
+
+ org.wildfly.security
+ wildfly-elytron-dynamic-ssl
+ ${project.version}
+
org.wildfly.security
wildfly-elytron-encryption
@@ -1382,6 +1387,7 @@
credential/source/impl
digest
encryption
+ dynamic-ssl
http/base
http/basic
http/bearer
diff --git a/tests/base/pom.xml b/tests/base/pom.xml
index d9ded87bb4..0966e15ee3 100644
--- a/tests/base/pom.xml
+++ b/tests/base/pom.xml
@@ -788,6 +788,11 @@
2.7.1
test
+
+ org.wildfly.security
+ wildfly-elytron-dynamic-ssl
+ test
+
diff --git a/wildfly-elytron/pom.xml b/wildfly-elytron/pom.xml
index 6973cb1cc0..87cf010d2f 100644
--- a/wildfly-elytron/pom.xml
+++ b/wildfly-elytron/pom.xml
@@ -293,6 +293,10 @@
org.wildfly.security
wildfly-elytron-digest
+
+ org.wildfly.security
+ wildfly-elytron-dynamic-ssl
+
org.wildfly.security
wildfly-elytron-encryption
@@ -633,6 +637,11 @@
wildfly-elytron-digest
${project.version}
+
+ org.wildfly.security
+ wildfly-elytron-dynamic-ssl
+ ${project.version}
+
org.wildfly.security
wildfly-elytron-http