diff --git a/ELY_Messages.txt b/ELY_Messages.txt index a0dbf434172..9bb73c1c23a 100644 --- a/ELY_Messages.txt +++ b/ELY_Messages.txt @@ -114,7 +114,7 @@ 18000 - 18999 wildfly-elytron-x500-cert (2) 19000 - 19999 wildfly-elytron-encryption 20000 - 20999 wildfly-elytron-credential-store -21000 - 21999 +21000 - 21999 wildfly-elytron-dynamic-ssl 22000 - 22499 wildfly-elytron-http-form 22500 - 22999 wildfly-elytron-http-spnego 23000 - 23999 diff --git a/auth/client/pom.xml b/auth/client/pom.xml index 794d0f07912..61f1d641d03 100644 --- a/auth/client/pom.xml +++ b/auth/client/pom.xml @@ -52,6 +52,10 @@ org.wildfly.security wildfly-elytron-credential-store + + org.wildfly.security + wildfly-elytron-dynamic-ssl + org.wildfly.security wildfly-elytron-keystore @@ -76,7 +80,6 @@ org.wildfly.security wildfly-elytron-x500-cert - org.jboss.logging jboss-logging-annotations @@ -160,5 +163,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..495c063278f 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,28 @@ private static AuthenticationConfiguration initializeConfiguration(final URI uri return configuration; } + List getConfiguredSSLContexts(AuthenticationContext authenticationContext) throws GeneralSecurityException { + List sslContexts = new ArrayList<>(); + RuleNode> node = authenticationContext.getSslRules(); + while (node != null) { + sslContexts.add(node.getConfiguration().create()); + node = node.getNext(); + } + return sslContexts; + } + + public SSLContext getDefaultSSLContext(AuthenticationContext authenticationContext) throws GeneralSecurityException { + 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/auth/client/src/main/java/org/wildfly/security/auth/client/DynamicSSLContextImpl.java b/auth/client/src/main/java/org/wildfly/security/auth/client/DynamicSSLContextImpl.java new file mode 100644 index 00000000000..2b5963f4ec3 --- /dev/null +++ b/auth/client/src/main/java/org/wildfly/security/auth/client/DynamicSSLContextImpl.java @@ -0,0 +1,91 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2020 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; + +import org.kohsuke.MetaInfServices; +import org.wildfly.common.Assert; +import org.wildfly.security.auth.client._private.ElytronMessages; +import org.wildfly.security.dynamic.ssl.DynamicSSLContextException; +import org.wildfly.security.dynamic.ssl.DynamicSSLContextSPI; + +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. + * + * @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 { + 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/auth/client/src/main/java/org/wildfly/security/auth/client/_private/ElytronMessages.java b/auth/client/src/main/java/org/wildfly/security/auth/client/_private/ElytronMessages.java index 16ef34ce9cc..7a19345bd14 100644 --- a/auth/client/src/main/java/org/wildfly/security/auth/client/_private/ElytronMessages.java +++ b/auth/client/src/main/java/org/wildfly/security/auth/client/_private/ElytronMessages.java @@ -40,6 +40,7 @@ import org.wildfly.client.config.ConfigXMLParseException; import org.wildfly.client.config.ConfigurationXMLStreamReader; import org.wildfly.client.config.XMLLocation; +import org.wildfly.security.dynamic.ssl.DynamicSSLContextException; /** * Log messages and exceptions for Elytron. @@ -201,4 +202,13 @@ ConfigXMLParseException xmlUnableToIdentifyProvider(@Param Location location, St @Message(id = 14004, value = "Password callback handling was unsuccessful") ConfigXMLParseException passwordCallbackHandlingWasUnsuccessful(); + + @Message(id = 14005, value = "Obtaining of the default SSLContext from current authentication context resulted in exception.") + DynamicSSLContextException cannotObtainDefaultSSLContext(@Cause Throwable cause); + + @Message(id = 14006, value = "Obtaining of all configured SSLContexts from current authentication context resulted in exception.") + DynamicSSLContextException cannotObtainConfiguredSSLContexts(@Cause Throwable cause); + + @Message(id = 14007, 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/pom.xml b/dynamic-ssl/pom.xml new file mode 100644 index 00000000000..98deaec4949 --- /dev/null +++ b/dynamic-ssl/pom.xml @@ -0,0 +1,39 @@ + + + + org.wildfly.security + wildfly-elytron-parent + 1.16.0.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 + + + 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..3bf035f3734 --- /dev/null +++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContext.java @@ -0,0 +1,54 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2020 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. + * + * @author Diana Krepinska (Vilkolakova) + */ +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..39e30197d0b --- /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 2020 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 (Vilkolakova) + */ +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/DynamicSSLContextSPI.java b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextSPI.java new file mode 100644 index 00000000000..ed0b88f1a9f --- /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 2020 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 (Vilkolakova) + */ +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..d1544d13c85 --- /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 2020 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 (Vilkolakova) + */ +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..581c359d458 --- /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 2020 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 (Vilkolakova) + */ +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..9a206a8d008 --- /dev/null +++ b/dynamic-ssl/src/main/java/org/wildfly/security/dynamic/ssl/ElytronMessages.java @@ -0,0 +1,67 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2020 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.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(); +} diff --git a/pom.xml b/pom.xml index 3cadfc0dc3f..b146823a397 100644 --- a/pom.xml +++ b/pom.xml @@ -519,6 +519,11 @@ wildfly-elytron-digest ${project.version} + + org.wildfly.security + wildfly-elytron-dynamic-ssl + ${project.version} + org.wildfly.security wildfly-elytron-encryption @@ -1305,6 +1310,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 c7cabbd07e1..b4dad0f3191 100644 --- a/tests/base/pom.xml +++ b/tests/base/pom.xml @@ -759,6 +759,11 @@ 2.3.1 test + + org.wildfly.security + wildfly-elytron-dynamic-ssl + test + diff --git a/tests/base/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextTest.java b/tests/base/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextTest.java new file mode 100644 index 00000000000..3b29071aaf0 --- /dev/null +++ b/tests/base/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLContextTest.java @@ -0,0 +1,460 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2020 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.DynamicSSLContextImpl; +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 SSLServerSocketTestInstance sslServerSocketTestInstancePort10001; + private static SSLServerSocketTestInstance sslServerSocketTestInstancePort10002; + private static SSLServerSocketTestInstance sslServerSocketTestInstancePort10003; + private static 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(); + 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("127.0.0.1", 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.fail(); + } + }); + } + + @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("127.0.0.1", 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.fail(); + } + } + + @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.fail(); + } + }); + } + + @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.fail(); + } + }); + } + + @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("127.0.0.1"), 12555); + clientSslSocket.setReuseAddress(true); + clientSslSocket.setReuseAddress(true); + checkOutputIsOK(clientSslSocket); + clientSslSocket.close(); + } catch (Exception e) { + Assert.fail(); + } + }); + } + + @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.fail(); + } + }); + } + + @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.fail(); + } + }); + } + + @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 + SSLSocketFactory dynamicSSLSocketFactory = dynamicSSLContext.getSocketFactory(); + SSLSocket clientSocketWithDynamicDefaultSSLContext = (SSLSocket) dynamicSSLSocketFactory.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.fail(); + } + } + ); + + } catch (Exception e) { + e.printStackTrace(); + Assert.fail(); + } + } + + @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) { + e.printStackTrace(); + Assert.fail(); + } + }); + } + + @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) { + e.printStackTrace(); + Assert.fail(); + } + }); + } + + @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.fail(); + } + }); + + 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.fail(); + } + }); + + 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.fail(); + } + }); + } + + @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.fail(); + } finally { + SSLContext.setDefault(previousDefaultSSLContext); + } + }); + } + + 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.fail(); + throw new InvalidAuthenticationConfigurationException(e); + } + }); + } +} diff --git a/tests/base/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLTestUtils.java b/tests/base/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLTestUtils.java new file mode 100644 index 00000000000..cba0fe3522c --- /dev/null +++ b/tests/base/src/test/java/org/wildfly/security/dynamic/ssl/DynamicSSLTestUtils.java @@ -0,0 +1,215 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2020 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) + */ +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) { + ex.printStackTrace(); + 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); + } + } + + 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/tests/base/src/test/java/org/wildfly/security/dynamic/ssl/SSLServerSocketTestInstance.java b/tests/base/src/test/java/org/wildfly/security/dynamic/ssl/SSLServerSocketTestInstance.java new file mode 100644 index 00000000000..113dbed3b1b --- /dev/null +++ b/tests/base/src/test/java/org/wildfly/security/dynamic/ssl/SSLServerSocketTestInstance.java @@ -0,0 +1,141 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2020 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; + + 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)); + System.out.println("SSL server socket started on port: " + 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) { + e.printStackTrace(); + Assert.fail(); + } + } + }); + serverThread.start(); + } catch (Exception ex) { + ex.printStackTrace(); + Assert.fail(); + } finally { + running.set(false); + } + } + + public void stop() { + running.set(false); + } + + // Thread handling the socket from client + 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/tests/base/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test-without-default-sslcontext.xml b/tests/base/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test-without-default-sslcontext.xml new file mode 100644 index 00000000000..382342c3c89 --- /dev/null +++ b/tests/base/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test-without-default-sslcontext.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/base/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test.xml b/tests/base/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test.xml new file mode 100644 index 00000000000..39978c0b5a1 --- /dev/null +++ b/tests/base/src/test/resources/org/wildfly/security/dynamic/ssl/wildfly-config-dynamic-ssl-test.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wildfly-elytron/pom.xml b/wildfly-elytron/pom.xml index db9172c0493..a99dc2642ec 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 @@ -630,6 +634,11 @@ wildfly-elytron-digest ${project.version} + + org.wildfly.security + wildfly-elytron-dynamic-ssl + ${project.version} + org.wildfly.security wildfly-elytron-http