diff --git a/ELY_Messages.txt b/ELY_Messages.txt
index 908acc95f2d..3f5d52bf69f 100644
--- a/ELY_Messages.txt
+++ b/ELY_Messages.txt
@@ -113,7 +113,7 @@
16000 - 16999 wildfly-elytron-auth-server
17000 - 17999 wildfly-elytron-auth-util
18000 - 18999 wildfly-elytron-x500-cert (2)
-19000 - 19999
+19000 - 19999 wildfly-elytron-dynamic-ssl
20000 - 20999
21000 - 21999
22000 - 22999
diff --git a/auth/client/pom.xml b/auth/client/pom.xml
index 84954dd7135..f985a715876 100644
--- a/auth/client/pom.xml
+++ b/auth/client/pom.xml
@@ -76,7 +76,6 @@
org.wildfly.security
wildfly-elytron-x500-cert
-
org.jboss.logging
jboss-logging-annotations
@@ -142,11 +141,6 @@
${version.org.jboss.ws.jbossws-spi}
provided
-
- org.kohsuke.metainf-services
- metainf-services
- provided
-
@@ -160,5 +154,4 @@
test
-
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 fc2c6b086b8..13d58004014 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
@@ -354,6 +354,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 50cf8d87886..6aa5de9ee41 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,44 @@ private static AuthenticationConfiguration initializeConfiguration(final URI uri
return configuration;
}
+ /**
+ * Get all SSL contexts configured for this authentication context.
+ * This method is not part of the public API.
+ *
+ * @param authenticationContext the authentication context to examine (must not be {@code null})
+ * @return List of all configured SSL context 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.
+ * This method is not part of the public API.
+ *
+ * @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 SSL context which matches the given URI, 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 00000000000..f72a4091c66
--- /dev/null
+++ b/dynamic-ssl/pom.xml
@@ -0,0 +1,78 @@
+
+
+
+ org.wildfly.security
+ wildfly-elytron-parent
+ 1.14.3.CR1-SNAPSHOT
+ ../pom.xml
+
+
+ 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 00000000000..53862cb4b2a
--- /dev/null
+++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContext.java
@@ -0,0 +1,52 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2021 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.security.NoSuchAlgorithmException;
+
+/**
+ * SSLContext that resolves which SSLContext to use based on peer's host and port information.
+ */
+public final class DynamicSSLContext extends SSLContext {
+
+ 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 00000000000..aa058bebe1d
--- /dev/null
+++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextException.java
@@ -0,0 +1,39 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2021 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.
+ */
+public class DynamicSSLContextException extends Exception {
+
+ 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 00000000000..977f0b8c330
--- /dev/null
+++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextImpl.java
@@ -0,0 +1,88 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2021 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.common.Assert;
+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;
+
+/**
+ * Elytron client implementation of DynamicSSLContextSPI. It uses configuration from either provided instance of AuthenticationContext
+ * or from current AuthenticationContext if a configuration was not provided.
+ */
+@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 {
+ Assert.assertNotNull("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 00000000000..f392a4bc522
--- /dev/null
+++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextSPI.java
@@ -0,0 +1,50 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2021 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.
+ */
+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 00000000000..38c54b0c88d
--- /dev/null
+++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextSpiImpl.java
@@ -0,0 +1,143 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2021 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.
+ */
+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 00000000000..d23b3a04799
--- /dev/null
+++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLSocketFactory.java
@@ -0,0 +1,158 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2021 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.
+ */
+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 00000000000..9f48fda788a
--- /dev/null
+++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/ElytronMessages.java
@@ -0,0 +1,74 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2021 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.
+ */
+@MessageLogger(projectCode = "ELY", length = 5)
+@ValidIdRanges({
+ @ValidIdRange(min = 19000, max = 19999)
+})
+interface ElytronMessages extends BasicLogger {
+
+ ElytronMessages log = Logger.getMessageLogger(ElytronMessages.class, "org.wildfly.security");
+
+ @Message(id = 19000, value = "DynamicSSLContext creates loop")
+ IllegalStateException dynamicSSLContextCreatesLoop();
+
+ @Message(id = 19001, value = "Received SSLContext from DynamicSSLContextProvider was null")
+ IllegalStateException receivedSSLContextFromDynamicSSLContextProviderWasNull();
+
+ @Message(id = 19002, value = "Dynamic SSLContext does not support sessions")
+ UnsupportedOperationException dynamicSSLContextDoesNotSupportSessions();
+
+ @Message(id = 19003, value = "Provider for DynamicSSLContextSPI threw an exception when getting configured SSLContexts")
+ IllegalStateException unableToGetConfiguredSSLContexts();
+
+ @Message(id = 19004, value = "Provider for DynamicSSLContextSPI returned null configured SSLContexts")
+ IllegalStateException configuredSSLContextsAreNull();
+
+ @Message(id = 19005, value = "Cannot obtain default SSLContext from DynamicSSLContext implementation")
+ IllegalStateException cannotObtainConfiguredDefaultSSLContext();
+
+ @Message(id = 19006, value = "Could not create URI from host and port")
+ IllegalStateException couldNotCreateURI();
+
+ @Message(id = 19007, value = "Could not create dynamic ssl context engine")
+ IllegalStateException couldNotCreateDynamicSSLContextEngine();
+
+ @Message(id = 19008, value = "Provider for DynamicSSLContextSPI returned null SSLContext")
+ IllegalStateException configuredSSLContextIsNull();
+
+ @Message(id = 19009, value = "Obtaining of the default SSLContext from current authentication context resulted in exception.")
+ DynamicSSLContextException cannotObtainDefaultSSLContext(@Cause Throwable cause);
+
+ @Message(id = 19010, value = "Obtaining of all configured SSLContexts from current authentication context resulted in exception.")
+ DynamicSSLContextException cannotObtainConfiguredSSLContexts(@Cause Throwable cause);
+
+ @Message(id = 19011, 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 00000000000..89d0cc36c8a
--- /dev/null
+++ b/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextTest.java
@@ -0,0 +1,465 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2021 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.bouncycastle.jce.provider.BouncyCastleProvider;
+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.Security;
+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.
+ */
+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 {
+ BouncyCastleProvider bouncyCastleProvider = new BouncyCastleProvider();
+ Security.insertProviderAt(bouncyCastleProvider, 1);
+ 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) {
+ e.printStackTrace();
+ Assert.assertEquals("fine", e.getMessage());
+ } finally {
+ Security.removeProvider(bouncyCastleProvider.getName());
+ }
+ });
+ }
+
+ @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 {
+ BouncyCastleProvider bouncyCastleProvider = new BouncyCastleProvider();
+ Security.insertProviderAt(bouncyCastleProvider, 1);
+ 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());
+ } finally {
+ Security.removeProvider(bouncyCastleProvider.getName());
+ }
+ });
+ }
+
+ @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) {
+ e.printStackTrace();
+ 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) {
+ e.printStackTrace();
+ 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 00000000000..645a23ad0a0
--- /dev/null
+++ b/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLTestUtils.java
@@ -0,0 +1,212 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2021 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 DynamicSSLContextClientTest class.
+ */
+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;
+ }
+
+ public 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 00000000000..59861782fd3
--- /dev/null
+++ b/dynamic-ssl/src/test/java/org/wildfly/security/dynamic/ssl/SSLServerSocketTestInstance.java
@@ -0,0 +1,135 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2021 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.
+ */
+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) {
+ 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 00000000000..50cfad1d2c0
--- /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,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 00000000000..1e67b83b898
--- /dev/null
+++ b/dynamic-ssl/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
index 1f3cffe89a8..96551832a0a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -518,6 +518,11 @@
wildfly-elytron-digest
${project.version}
+
+ org.wildfly.security
+ wildfly-elytron-dynamic-ssl
+ ${project.version}
+
org.wildfly.security
wildfly-elytron-http
@@ -1298,6 +1303,7 @@
credential/source/deprecated
credential/source/impl
digest
+ dynamic-ssl
http/base
http/basic
http/bearer