From c60d17884a42b4bf4cb5e249f8020b19e0725eb6 Mon Sep 17 00:00:00 2001 From: Justin Bertram Date: Tue, 11 Sep 2018 11:06:01 -0500 Subject: [PATCH] ARTEMIS-1919 implement SNI properly --- .../remoting/impl/netty/NettyConnector.java | 20 ++- .../remoting/impl/netty/NettyAcceptor.java | 13 ++ docs/user-manual/en/configuring-transports.md | 12 ++ .../ssl/CoreClientOverOneWaySSLTest.java | 123 +++++++++++++++++- 4 files changed, 158 insertions(+), 10 deletions(-) diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyConnector.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyConnector.java index 2ef5fed2db..4a87770817 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyConnector.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyConnector.java @@ -16,6 +16,12 @@ */ package org.apache.activemq.artemis.core.remoting.impl.netty; +import javax.net.ssl.SNIHostName; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; import java.io.IOException; import java.net.ConnectException; import java.net.InetAddress; @@ -28,6 +34,7 @@ import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivilegedExceptionAction; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -42,12 +49,6 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLParameters; -import javax.security.auth.Subject; -import javax.security.auth.login.LoginContext; - import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -383,6 +384,7 @@ public class NettyConnector extends AbstractConnector { enabledProtocols = TransportConstants.DEFAULT_ENABLED_PROTOCOLS; verifyHost = TransportConstants.DEFAULT_VERIFY_HOST; trustAll = TransportConstants.DEFAULT_TRUST_ALL; + sniHost = TransportConstants.DEFAULT_SNIHOST_CONFIG; useDefaultSslContext = TransportConstants.DEFAULT_USE_DEFAULT_SSL_CONTEXT; } @@ -571,6 +573,12 @@ public class NettyConnector extends AbstractConnector { engine.setSSLParameters(sslParameters); } + if (sniHost != null) { + SSLParameters sslParameters = engine.getSSLParameters(); + sslParameters.setServerNames(Arrays.asList(new SNIHostName(sniHost))); + engine.setSSLParameters(sslParameters); + } + SslHandler handler = new SslHandler(engine); pipeline.addLast("ssl", handler); diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyAcceptor.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyAcceptor.java index fb46ff4401..47a66f5037 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyAcceptor.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/NettyAcceptor.java @@ -16,6 +16,7 @@ */ package org.apache.activemq.artemis.core.remoting.impl.netty; +import javax.net.ssl.SNIHostName; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLHandshakeException; @@ -28,6 +29,7 @@ import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -175,6 +177,8 @@ public class NettyAcceptor extends AbstractAcceptor { private final String kerb5Config; + private String sniHost; + private final boolean tcpNoDelay; private final int backlog; @@ -286,6 +290,8 @@ public class NettyAcceptor extends AbstractAcceptor { verifyHost = ConfigurationHelper.getBooleanProperty(TransportConstants.VERIFY_HOST_PROP_NAME, TransportConstants.DEFAULT_VERIFY_HOST, configuration); sslProvider = ConfigurationHelper.getStringProperty(TransportConstants.SSL_PROVIDER, TransportConstants.DEFAULT_SSL_PROVIDER, configuration); + + sniHost = ConfigurationHelper.getStringProperty(TransportConstants.SNIHOST_PROP_NAME, TransportConstants.DEFAULT_SNIHOST_CONFIG, configuration); } else { keyStoreProvider = TransportConstants.DEFAULT_KEYSTORE_PROVIDER; keyStorePath = TransportConstants.DEFAULT_KEYSTORE_PATH; @@ -300,6 +306,7 @@ public class NettyAcceptor extends AbstractAcceptor { wantClientAuth = TransportConstants.DEFAULT_WANT_CLIENT_AUTH; verifyHost = TransportConstants.DEFAULT_VERIFY_HOST; sslProvider = TransportConstants.DEFAULT_SSL_PROVIDER; + sniHost = TransportConstants.DEFAULT_SNIHOST_CONFIG; } tcpNoDelay = ConfigurationHelper.getBooleanProperty(TransportConstants.TCP_NODELAY_PROPNAME, TransportConstants.DEFAULT_TCP_NODELAY, configuration); @@ -534,6 +541,12 @@ public class NettyAcceptor extends AbstractAcceptor { engine.setSSLParameters(sslParameters); } + if (sniHost != null) { + SSLParameters sslParameters = engine.getSSLParameters(); + sslParameters.setSNIMatchers(Arrays.asList(SNIHostName.createSNIMatcher(sniHost))); + engine.setSSLParameters(sslParameters); + } + return new SslHandler(engine); } diff --git a/docs/user-manual/en/configuring-transports.md b/docs/user-manual/en/configuring-transports.md index ee3a0471c6..00df421bb4 100644 --- a/docs/user-manual/en/configuring-transports.md +++ b/docs/user-manual/en/configuring-transports.md @@ -460,6 +460,18 @@ additional properties: https://en.wikipedia.org/wiki/Comparison_of_TLS_implementations for more information's. +- `sniHost` + + When used on an `acceptor` the `sniHost` is a *regular expression* used to + match the [`server_name`](https://tools.ietf.org/html/rfc6066) extension on + incoming SSL connections. If the name doesn't match then the connection to + the acceptor will be rejected. A WARN message will be logged if this happens. + If the incoming connection doesn't include the `server_name` extension then + the connection will be accepted. + + When used on a `connector` the `sniHost` value is used for the `server_name` + extension on the SSL connection. + ### Configuring Netty HTTP Netty HTTP tunnels packets over the HTTP protocol. It can be useful in diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverOneWaySSLTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverOneWaySSLTest.java index 2bc321224c..c6457c6542 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverOneWaySSLTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverOneWaySSLTest.java @@ -153,6 +153,108 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase { Assert.assertEquals(text, m.getBodyBuffer().readString()); } + @Test + public void testOneWaySSLwithSNI() throws Exception { + createCustomSslServer("myhost\\.com"); + String text = RandomUtil.randomString(); + + tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true); + tc.getParams().put(TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME, storeType); + tc.getParams().put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, CLIENT_SIDE_TRUSTSTORE); + tc.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, PASSWORD); + tc.getParams().put(TransportConstants.SNIHOST_PROP_NAME, "myhost.com"); + + ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc)); + ClientSessionFactory sf = addSessionFactory(createSessionFactory(locator)); + ClientSession session = addClientSession(sf.createSession(false, true, true)); + session.createQueue(CoreClientOverOneWaySSLTest.QUEUE, CoreClientOverOneWaySSLTest.QUEUE, false); + ClientProducer producer = addClientProducer(session.createProducer(CoreClientOverOneWaySSLTest.QUEUE)); + + ClientMessage message = createTextMessage(session, text); + producer.send(message); + + ClientConsumer consumer = addClientConsumer(session.createConsumer(CoreClientOverOneWaySSLTest.QUEUE)); + session.start(); + + ClientMessage m = consumer.receive(1000); + Assert.assertNotNull(m); + Assert.assertEquals(text, m.getBodyBuffer().readString()); + } + + @Test + public void testOneWaySSLwithSNINegative() throws Exception { + createCustomSslServer("myhost\\.com"); + + tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true); + tc.getParams().put(TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME, storeType); + tc.getParams().put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, CLIENT_SIDE_TRUSTSTORE); + tc.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, PASSWORD); + tc.getParams().put(TransportConstants.SNIHOST_PROP_NAME, "badhost.com"); + + ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc)); + try { + ClientSessionFactory sf = addSessionFactory(createSessionFactory(locator)); + fail("Should have failed due to unrecognized SNI host name"); + } catch (Exception e) { + // ignore + } + } + + @Test + public void testOneWaySSLwithSNIOnlyOnTheClient() throws Exception { + createCustomSslServer(); + String text = RandomUtil.randomString(); + + tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true); + tc.getParams().put(TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME, storeType); + tc.getParams().put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, CLIENT_SIDE_TRUSTSTORE); + tc.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, PASSWORD); + tc.getParams().put(TransportConstants.SNIHOST_PROP_NAME, "myhost.com"); + + ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc)); + ClientSessionFactory sf = addSessionFactory(createSessionFactory(locator)); + ClientSession session = addClientSession(sf.createSession(false, true, true)); + session.createQueue(CoreClientOverOneWaySSLTest.QUEUE, CoreClientOverOneWaySSLTest.QUEUE, false); + ClientProducer producer = addClientProducer(session.createProducer(CoreClientOverOneWaySSLTest.QUEUE)); + + ClientMessage message = createTextMessage(session, text); + producer.send(message); + + ClientConsumer consumer = addClientConsumer(session.createConsumer(CoreClientOverOneWaySSLTest.QUEUE)); + session.start(); + + ClientMessage m = consumer.receive(1000); + Assert.assertNotNull(m); + Assert.assertEquals(text, m.getBodyBuffer().readString()); + } + + @Test + public void testOneWaySSLwithSNIOnlyOnTheBroker() throws Exception { + createCustomSslServer("myhost\\.com"); + String text = RandomUtil.randomString(); + + tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true); + tc.getParams().put(TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME, storeType); + tc.getParams().put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, CLIENT_SIDE_TRUSTSTORE); + tc.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, PASSWORD); + + ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc)); + ClientSessionFactory sf = addSessionFactory(createSessionFactory(locator)); + ClientSession session = addClientSession(sf.createSession(false, true, true)); + session.createQueue(CoreClientOverOneWaySSLTest.QUEUE, CoreClientOverOneWaySSLTest.QUEUE, false); + ClientProducer producer = addClientProducer(session.createProducer(CoreClientOverOneWaySSLTest.QUEUE)); + + ClientMessage message = createTextMessage(session, text); + producer.send(message); + + ClientConsumer consumer = addClientConsumer(session.createConsumer(CoreClientOverOneWaySSLTest.QUEUE)); + session.start(); + + ClientMessage m = consumer.receive(1000); + Assert.assertNotNull(m); + Assert.assertEquals(text, m.getBodyBuffer().readString()); + } + @Test public void testOneWaySSLwithURL() throws Exception { createCustomSslServer(); @@ -264,7 +366,7 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase { @Test public void testOneWaySSLVerifyHost() throws Exception { - createCustomSslServer(null, null, true); + createCustomSslServer(true); String text = RandomUtil.randomString(); tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true); @@ -292,7 +394,7 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase { @Test public void testOneWaySSLVerifyHostNegative() throws Exception { - createCustomSslServer(null, null, false); + createCustomSslServer(); String text = RandomUtil.randomString(); tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true); @@ -763,16 +865,29 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase { } private void createCustomSslServer(String cipherSuites, String protocols) throws Exception { - createCustomSslServer(cipherSuites, protocols, false); + createCustomSslServer(cipherSuites, protocols, false, null); + } + + private void createCustomSslServer(String sniHost) throws Exception { + createCustomSslServer(null, null, false, sniHost); + } + + private void createCustomSslServer(boolean useVerifiedKeystore) throws Exception { + createCustomSslServer(null, null, useVerifiedKeystore, null); } private void createCustomSslServer(String cipherSuites, String protocols, - boolean useVerifiedKeystore) throws Exception { + boolean useVerifiedKeystore, + String sniHost) throws Exception { Map params = new HashMap<>(); params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true); params.put(TransportConstants.KEYSTORE_PROVIDER_PROP_NAME, storeType); + if (sniHost != null) { + params.put(TransportConstants.SNIHOST_PROP_NAME, sniHost); + } + if (useVerifiedKeystore) { params.put(TransportConstants.KEYSTORE_PATH_PROP_NAME, "verified-" + SERVER_SIDE_KEYSTORE); } else {