This closes #2315 ARTEMIS-1919 implement SNI properly

This commit is contained in:
andytaylor 2018-09-18 08:00:05 +01:00
commit 4cbc387782
4 changed files with 158 additions and 10 deletions

View File

@ -16,6 +16,12 @@
*/ */
package org.apache.activemq.artemis.core.remoting.impl.netty; 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.io.IOException;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.InetAddress; import java.net.InetAddress;
@ -28,6 +34,7 @@ import java.nio.charset.StandardCharsets;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedExceptionAction; import java.security.PrivilegedExceptionAction;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -42,12 +49,6 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Stream; 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.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufAllocator;
@ -383,6 +384,7 @@ public class NettyConnector extends AbstractConnector {
enabledProtocols = TransportConstants.DEFAULT_ENABLED_PROTOCOLS; enabledProtocols = TransportConstants.DEFAULT_ENABLED_PROTOCOLS;
verifyHost = TransportConstants.DEFAULT_VERIFY_HOST; verifyHost = TransportConstants.DEFAULT_VERIFY_HOST;
trustAll = TransportConstants.DEFAULT_TRUST_ALL; trustAll = TransportConstants.DEFAULT_TRUST_ALL;
sniHost = TransportConstants.DEFAULT_SNIHOST_CONFIG;
useDefaultSslContext = TransportConstants.DEFAULT_USE_DEFAULT_SSL_CONTEXT; useDefaultSslContext = TransportConstants.DEFAULT_USE_DEFAULT_SSL_CONTEXT;
} }
@ -571,6 +573,12 @@ public class NettyConnector extends AbstractConnector {
engine.setSSLParameters(sslParameters); 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); SslHandler handler = new SslHandler(engine);
pipeline.addLast("ssl", handler); pipeline.addLast("ssl", handler);

View File

@ -16,6 +16,7 @@
*/ */
package org.apache.activemq.artemis.core.remoting.impl.netty; package org.apache.activemq.artemis.core.remoting.impl.netty;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLHandshakeException;
@ -28,6 +29,7 @@ import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction; import java.security.PrivilegedExceptionAction;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -175,6 +177,8 @@ public class NettyAcceptor extends AbstractAcceptor {
private final String kerb5Config; private final String kerb5Config;
private String sniHost;
private final boolean tcpNoDelay; private final boolean tcpNoDelay;
private final int backlog; 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); verifyHost = ConfigurationHelper.getBooleanProperty(TransportConstants.VERIFY_HOST_PROP_NAME, TransportConstants.DEFAULT_VERIFY_HOST, configuration);
sslProvider = ConfigurationHelper.getStringProperty(TransportConstants.SSL_PROVIDER, TransportConstants.DEFAULT_SSL_PROVIDER, 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 { } else {
keyStoreProvider = TransportConstants.DEFAULT_KEYSTORE_PROVIDER; keyStoreProvider = TransportConstants.DEFAULT_KEYSTORE_PROVIDER;
keyStorePath = TransportConstants.DEFAULT_KEYSTORE_PATH; keyStorePath = TransportConstants.DEFAULT_KEYSTORE_PATH;
@ -300,6 +306,7 @@ public class NettyAcceptor extends AbstractAcceptor {
wantClientAuth = TransportConstants.DEFAULT_WANT_CLIENT_AUTH; wantClientAuth = TransportConstants.DEFAULT_WANT_CLIENT_AUTH;
verifyHost = TransportConstants.DEFAULT_VERIFY_HOST; verifyHost = TransportConstants.DEFAULT_VERIFY_HOST;
sslProvider = TransportConstants.DEFAULT_SSL_PROVIDER; sslProvider = TransportConstants.DEFAULT_SSL_PROVIDER;
sniHost = TransportConstants.DEFAULT_SNIHOST_CONFIG;
} }
tcpNoDelay = ConfigurationHelper.getBooleanProperty(TransportConstants.TCP_NODELAY_PROPNAME, TransportConstants.DEFAULT_TCP_NODELAY, configuration); tcpNoDelay = ConfigurationHelper.getBooleanProperty(TransportConstants.TCP_NODELAY_PROPNAME, TransportConstants.DEFAULT_TCP_NODELAY, configuration);
@ -534,6 +541,12 @@ public class NettyAcceptor extends AbstractAcceptor {
engine.setSSLParameters(sslParameters); engine.setSSLParameters(sslParameters);
} }
if (sniHost != null) {
SSLParameters sslParameters = engine.getSSLParameters();
sslParameters.setSNIMatchers(Arrays.asList(SNIHostName.createSNIMatcher(sniHost)));
engine.setSSLParameters(sslParameters);
}
return new SslHandler(engine); return new SslHandler(engine);
} }

View File

@ -460,6 +460,18 @@ additional properties:
https://en.wikipedia.org/wiki/Comparison_of_TLS_implementations for more https://en.wikipedia.org/wiki/Comparison_of_TLS_implementations for more
information's. 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 ### Configuring Netty HTTP
Netty HTTP tunnels packets over the HTTP protocol. It can be useful in Netty HTTP tunnels packets over the HTTP protocol. It can be useful in

View File

@ -153,6 +153,108 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase {
Assert.assertEquals(text, m.getBodyBuffer().readString()); 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 @Test
public void testOneWaySSLwithURL() throws Exception { public void testOneWaySSLwithURL() throws Exception {
createCustomSslServer(); createCustomSslServer();
@ -264,7 +366,7 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase {
@Test @Test
public void testOneWaySSLVerifyHost() throws Exception { public void testOneWaySSLVerifyHost() throws Exception {
createCustomSslServer(null, null, true); createCustomSslServer(true);
String text = RandomUtil.randomString(); String text = RandomUtil.randomString();
tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true); tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
@ -292,7 +394,7 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase {
@Test @Test
public void testOneWaySSLVerifyHostNegative() throws Exception { public void testOneWaySSLVerifyHostNegative() throws Exception {
createCustomSslServer(null, null, false); createCustomSslServer();
String text = RandomUtil.randomString(); String text = RandomUtil.randomString();
tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true); 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 { 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, private void createCustomSslServer(String cipherSuites,
String protocols, String protocols,
boolean useVerifiedKeystore) throws Exception { boolean useVerifiedKeystore,
String sniHost) throws Exception {
Map<String, Object> params = new HashMap<>(); Map<String, Object> params = new HashMap<>();
params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true); params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
params.put(TransportConstants.KEYSTORE_PROVIDER_PROP_NAME, storeType); params.put(TransportConstants.KEYSTORE_PROVIDER_PROP_NAME, storeType);
if (sniHost != null) {
params.put(TransportConstants.SNIHOST_PROP_NAME, sniHost);
}
if (useVerifiedKeystore) { if (useVerifiedKeystore) {
params.put(TransportConstants.KEYSTORE_PATH_PROP_NAME, "verified-" + SERVER_SIDE_KEYSTORE); params.put(TransportConstants.KEYSTORE_PATH_PROP_NAME, "verified-" + SERVER_SIDE_KEYSTORE);
} else { } else {