ARTEMIS-656 support host verification for SSL cert

This commit is contained in:
jbertram 2016-08-11 14:32:56 -05:00 committed by Clebert Suconic
parent 113c2a3477
commit cfbe06f3bc
14 changed files with 212 additions and 6 deletions

View File

@ -18,6 +18,7 @@ package org.apache.activemq.artemis.core.remoting.impl.netty;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import java.io.IOException;
import java.net.ConnectException;
import java.net.Inet6Address;
@ -197,6 +198,8 @@ public class NettyConnector extends AbstractConnector {
private String enabledProtocols;
private boolean verifyHost;
private boolean tcpNoDelay;
private int tcpSendBufferSize;
@ -306,6 +309,8 @@ public class NettyConnector extends AbstractConnector {
enabledCipherSuites = ConfigurationHelper.getStringProperty(TransportConstants.ENABLED_CIPHER_SUITES_PROP_NAME, TransportConstants.DEFAULT_ENABLED_CIPHER_SUITES, configuration);
enabledProtocols = ConfigurationHelper.getStringProperty(TransportConstants.ENABLED_PROTOCOLS_PROP_NAME, TransportConstants.DEFAULT_ENABLED_PROTOCOLS, configuration);
verifyHost = ConfigurationHelper.getBooleanProperty(TransportConstants.VERIFY_HOST_PROP_NAME, TransportConstants.DEFAULT_VERIFY_HOST, configuration);
}
else {
keyStoreProvider = TransportConstants.DEFAULT_KEYSTORE_PROVIDER;
@ -316,6 +321,7 @@ public class NettyConnector extends AbstractConnector {
trustStorePassword = TransportConstants.DEFAULT_TRUSTSTORE_PASSWORD;
enabledCipherSuites = TransportConstants.DEFAULT_ENABLED_CIPHER_SUITES;
enabledProtocols = TransportConstants.DEFAULT_ENABLED_PROTOCOLS;
verifyHost = TransportConstants.DEFAULT_VERIFY_HOST;
}
tcpNoDelay = ConfigurationHelper.getBooleanProperty(TransportConstants.TCP_NODELAY_PROPNAME, TransportConstants.DEFAULT_TCP_NODELAY, configuration);
@ -462,7 +468,13 @@ public class NettyConnector extends AbstractConnector {
public void initChannel(Channel channel) throws Exception {
final ChannelPipeline pipeline = channel.pipeline();
if (sslEnabled && !useServlet) {
SSLEngine engine = context.createSSLEngine();
SSLEngine engine;
if (verifyHost) {
engine = context.createSSLEngine(host, port);
}
else {
engine = context.createSSLEngine();
}
engine.setUseClientMode(true);
@ -496,6 +508,12 @@ public class NettyConnector extends AbstractConnector {
engine.setEnabledProtocols(originalProtocols);
}
if (verifyHost) {
SSLParameters sslParameters = engine.getSSLParameters();
sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
engine.setSSLParameters(sslParameters);
}
SslHandler handler = new SslHandler(engine);
pipeline.addLast(handler);

View File

@ -87,6 +87,8 @@ public class TransportConstants {
public static final String NEED_CLIENT_AUTH_PROP_NAME = "needClientAuth";
public static final String VERIFY_HOST_PROP_NAME = "verifyHost";
public static final String BACKLOG_PROP_NAME = "backlog";
public static final String NETTY_VERSION;
@ -155,6 +157,8 @@ public class TransportConstants {
public static final boolean DEFAULT_NEED_CLIENT_AUTH = false;
public static final boolean DEFAULT_VERIFY_HOST = false;
public static final boolean DEFAULT_TCP_NODELAY = true;
public static final int DEFAULT_TCP_SENDBUFFER_SIZE = 32768;
@ -226,6 +230,7 @@ public class TransportConstants {
allowableAcceptorKeys.add(TransportConstants.ENABLED_CIPHER_SUITES_PROP_NAME);
allowableAcceptorKeys.add(TransportConstants.ENABLED_PROTOCOLS_PROP_NAME);
allowableAcceptorKeys.add(TransportConstants.NEED_CLIENT_AUTH_PROP_NAME);
allowableAcceptorKeys.add(TransportConstants.VERIFY_HOST_PROP_NAME);
allowableAcceptorKeys.add(TransportConstants.TCP_NODELAY_PROPNAME);
allowableAcceptorKeys.add(TransportConstants.TCP_SENDBUFFER_SIZE_PROPNAME);
allowableAcceptorKeys.add(TransportConstants.TCP_RECEIVEBUFFER_SIZE_PROPNAME);
@ -270,6 +275,7 @@ public class TransportConstants {
allowableConnectorKeys.add(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME);
allowableConnectorKeys.add(TransportConstants.ENABLED_CIPHER_SUITES_PROP_NAME);
allowableConnectorKeys.add(TransportConstants.ENABLED_PROTOCOLS_PROP_NAME);
allowableConnectorKeys.add(TransportConstants.VERIFY_HOST_PROP_NAME);
allowableConnectorKeys.add(TransportConstants.TCP_NODELAY_PROPNAME);
allowableConnectorKeys.add(TransportConstants.TCP_SENDBUFFER_SIZE_PROPNAME);
allowableConnectorKeys.add(TransportConstants.TCP_RECEIVEBUFFER_SIZE_PROPNAME);

View File

@ -19,12 +19,15 @@ package org.apache.activemq.artemis.core.remoting.impl.netty;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLParameters;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@ -138,6 +141,8 @@ public class NettyAcceptor extends AbstractAcceptor {
private final boolean needClientAuth;
private final boolean verifyHost;
private final boolean tcpNoDelay;
private final int backlog;
@ -224,6 +229,8 @@ public class NettyAcceptor extends AbstractAcceptor {
enabledProtocols = ConfigurationHelper.getStringProperty(TransportConstants.ENABLED_PROTOCOLS_PROP_NAME, TransportConstants.DEFAULT_ENABLED_PROTOCOLS, configuration);
needClientAuth = ConfigurationHelper.getBooleanProperty(TransportConstants.NEED_CLIENT_AUTH_PROP_NAME, TransportConstants.DEFAULT_NEED_CLIENT_AUTH, configuration);
verifyHost = ConfigurationHelper.getBooleanProperty(TransportConstants.VERIFY_HOST_PROP_NAME, TransportConstants.DEFAULT_VERIFY_HOST, configuration);
}
else {
keyStoreProvider = TransportConstants.DEFAULT_KEYSTORE_PROVIDER;
@ -235,6 +242,7 @@ public class NettyAcceptor extends AbstractAcceptor {
enabledCipherSuites = TransportConstants.DEFAULT_ENABLED_CIPHER_SUITES;
enabledProtocols = TransportConstants.DEFAULT_ENABLED_PROTOCOLS;
needClientAuth = TransportConstants.DEFAULT_NEED_CLIENT_AUTH;
verifyHost = TransportConstants.DEFAULT_VERIFY_HOST;
}
tcpNoDelay = ConfigurationHelper.getBooleanProperty(TransportConstants.TCP_NODELAY_PROPNAME, TransportConstants.DEFAULT_TCP_NODELAY, configuration);
@ -391,7 +399,13 @@ public class NettyAcceptor extends AbstractAcceptor {
ise.initCause(e);
throw ise;
}
SSLEngine engine = context.createSSLEngine();
SSLEngine engine;
if (verifyHost) {
engine = context.createSSLEngine(host, port);
}
else {
engine = context.createSSLEngine();
}
engine.setUseClientMode(false);
@ -439,6 +453,13 @@ public class NettyAcceptor extends AbstractAcceptor {
}
engine.setEnabledProtocols(set.toArray(new String[set.size()]));
if (verifyHost) {
SSLParameters sslParameters = engine.getSSLParameters();
sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
engine.setSSLParameters(sslParameters);
}
return new SslHandler(engine);
}

View File

@ -395,6 +395,18 @@ following additional properties:
connecting to this acceptor that 2-way SSL is required. Valid values
are `true` or `false`. Default is `false`.
- `verifyHost`
When used on an `acceptor` the `CN` of the connecting client's SSL certificate
will be compared to its hostname to verify they match. This is useful
only for 2-way SSL.
When used on a `connector` the `CN` of the server's SSL certificate will be
compared to its hostname to verify they match. This is useful for both 1-way
and 2-way SSL.
Valid values are `true` or `false`. Default is `false`.
## Configuring Netty HTTP
Netty HTTP tunnels packets over the HTTP protocol. It can be useful in

View File

@ -55,7 +55,10 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase {
@Parameterized.Parameters(name = "storeType={0}")
public static Collection getParameters() {
return Arrays.asList(new Object[][]{{"JCEKS"}, {"JKS"}});
return Arrays.asList(new Object[][]{
{"JCEKS"},
{"JKS"}
});
}
public CoreClientOverOneWaySSLTest(String storeType) {
@ -78,6 +81,10 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase {
* keytool -export -keystore other-server-side-keystore.jks -file activemq-jks.cer -storepass secureexample
* keytool -import -keystore other-client-side-truststore.jks -file activemq-jks.cer -storepass secureexample -keypass secureexample -noprompt
*
* keytool -genkey -keystore verified-server-side-keystore.jks -storepass secureexample -keypass secureexample -dname "CN=localhost, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ" -keyalg RSA
* keytool -export -keystore verified-server-side-keystore.jks -file activemq-jks.cer -storepass secureexample
* keytool -import -keystore verified-client-side-truststore.jks -file activemq-jks.cer -storepass secureexample -keypass secureexample -noprompt
*
* Commands to create the JCEKS artifacts:
* keytool -genkey -keystore server-side-keystore.jceks -storetype JCEKS -storepass secureexample -keypass secureexample -dname "CN=ActiveMQ Artemis Server, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ"
* keytool -export -keystore server-side-keystore.jceks -file activemq-jceks.cer -storetype jceks -storepass secureexample
@ -86,6 +93,10 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase {
* keytool -genkey -keystore other-server-side-keystore.jceks -storetype JCEKS -storepass secureexample -keypass secureexample -dname "CN=Other ActiveMQ Artemis Server, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ"
* keytool -export -keystore other-server-side-keystore.jceks -file activemq-jceks.cer -storetype jceks -storepass secureexample
* keytool -import -keystore other-client-side-truststore.jceks -storetype JCEKS -file activemq-jceks.cer -storepass secureexample -keypass secureexample -noprompt
*
* keytool -genkey -keystore verified-server-side-keystore.jceks -storetype JCEKS -storepass secureexample -keypass secureexample -dname "CN=localhost, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ"
* keytool -export -keystore verified-server-side-keystore.jceks -file activemq-jceks.cer -storetype jceks -storepass secureexample
* keytool -import -keystore verified-client-side-truststore.jceks -storetype JCEKS -file activemq-jceks.cer -storepass secureexample -keypass secureexample -noprompt
*/
private String storeType;
private String SERVER_SIDE_KEYSTORE;
@ -123,6 +134,56 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase {
Assert.assertEquals(text, m.getBodyBuffer().readString());
}
@Test
public void testOneWaySSLVerifyHost() throws Exception {
createCustomSslServer(null, null, true);
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, "verified-" + CLIENT_SIDE_TRUSTSTORE);
tc.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, PASSWORD);
tc.getParams().put(TransportConstants.VERIFY_HOST_PROP_NAME, true);
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();
Message m = consumer.receive(1000);
Assert.assertNotNull(m);
Assert.assertEquals(text, m.getBodyBuffer().readString());
}
@Test
public void testOneWaySSLVerifyHostNegative() throws Exception {
createCustomSslServer(null, null, false);
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.VERIFY_HOST_PROP_NAME, true);
ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc));
try {
ClientSessionFactory sf = addSessionFactory(createSessionFactory(locator));
fail("Creating a session here should fail due to a certificate with a CN that doesn't match the host name.");
}
catch (Exception e) {
// ignore
}
}
@Test
public void testOneWaySSLReloaded() throws Exception {
createCustomSslServer();
@ -589,11 +650,22 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase {
}
private void createCustomSslServer(String cipherSuites, String protocols) throws Exception {
createCustomSslServer(cipherSuites, protocols, false);
}
private void createCustomSslServer(String cipherSuites, String protocols, boolean useVerifiedKeystore) throws Exception {
Map<String, Object> params = new HashMap<>();
params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
params.put(TransportConstants.KEYSTORE_PROVIDER_PROP_NAME, storeType);
params.put(TransportConstants.KEYSTORE_PATH_PROP_NAME, SERVER_SIDE_KEYSTORE);
if (useVerifiedKeystore) {
params.put(TransportConstants.KEYSTORE_PATH_PROP_NAME, "verified-" + SERVER_SIDE_KEYSTORE);
}
else {
params.put(TransportConstants.KEYSTORE_PATH_PROP_NAME, SERVER_SIDE_KEYSTORE);
}
params.put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, PASSWORD);
params.put(TransportConstants.HOST_PROP_NAME, "localhost");
if (cipherSuites != null) {
params.put(TransportConstants.ENABLED_CIPHER_SUITES_PROP_NAME, cipherSuites);

View File

@ -36,6 +36,7 @@ import org.apache.activemq.artemis.api.core.client.ClientSession;
import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
import org.apache.activemq.artemis.api.core.client.ServerLocator;
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptor;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.apache.activemq.artemis.utils.RandomUtil;
import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl;
@ -56,7 +57,9 @@ public class CoreClientOverTwoWaySSLTest extends ActiveMQTestBase {
@Parameterized.Parameters(name = "storeType={0}")
public static Collection getParameters() {
return Arrays.asList(new Object[][]{{"JCEKS"}, {"JKS"}});
return Arrays.asList(new Object[][]{
{"JCEKS"},
{"JKS"}});
}
public CoreClientOverTwoWaySSLTest(String storeType) {
@ -77,10 +80,18 @@ public class CoreClientOverTwoWaySSLTest extends ActiveMQTestBase {
* keytool -export -keystore client-side-keystore.jks -file activemq-jks.cer -storepass secureexample
* keytool -import -keystore server-side-truststore.jks -file activemq-jks.cer -storepass secureexample -keypass secureexample -noprompt
*
* keytool -genkey -keystore verified-client-side-keystore.jks -storepass secureexample -keypass secureexample -dname "CN=localhost, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ" -keyalg RSA
* keytool -export -keystore verified-client-side-keystore.jks -file activemq-jks.cer -storepass secureexample
* keytool -import -keystore verified-server-side-truststore.jks -file activemq-jks.cer -storepass secureexample -keypass secureexample -noprompt
*
* Commands to create the JCEKS artifacts:
* keytool -genkey -keystore client-side-keystore.jceks -storetype JCEKS -storepass secureexample -keypass secureexample -dname "CN=ActiveMQ Artemis Client, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ" -keyalg RSA
* keytool -export -keystore client-side-keystore.jceks -file activemq-jceks.cer -storetype jceks -storepass secureexample
* keytool -import -keystore server-side-truststore.jceks -storetype JCEKS -file activemq-jceks.cer -storepass secureexample -keypass secureexample -noprompt
*
* keytool -genkey -keystore verified-client-side-keystore.jceks -storetype JCEKS -storepass secureexample -keypass secureexample -dname "CN=localhost, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ" -keyalg RSA
* keytool -export -keystore verified-client-side-keystore.jceks -file activemq-jceks.cer -storetype jceks -storepass secureexample
* keytool -import -keystore verified-server-side-truststore.jceks -storetype JCEKS -file activemq-jceks.cer -storepass secureexample -keypass secureexample -noprompt
*/
private String storeType;
@ -148,6 +159,72 @@ public class CoreClientOverTwoWaySSLTest extends ActiveMQTestBase {
Assert.assertEquals(text, m.getBodyBuffer().readString());
}
@Test
public void testTwoWaySSLVerifyClientHost() throws Exception {
NettyAcceptor acceptor = (NettyAcceptor) server.getRemotingService().getAcceptor("nettySSL");
acceptor.getConfiguration().put(TransportConstants.VERIFY_HOST_PROP_NAME, true);
acceptor.getConfiguration().put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, "verified-" + SERVER_SIDE_TRUSTSTORE);
server.getRemotingService().stop(false);
server.getRemotingService().start();
server.getRemotingService().startAcceptors();
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.KEYSTORE_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.KEYSTORE_PATH_PROP_NAME, "verified-" + CLIENT_SIDE_KEYSTORE);
tc.getParams().put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, PASSWORD);
server.getRemotingService().addIncomingInterceptor(new MyInterceptor());
ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc));
ClientSessionFactory sf = createSessionFactory(locator);
ClientSession session = sf.createSession(false, true, true);
session.createQueue(CoreClientOverTwoWaySSLTest.QUEUE, CoreClientOverTwoWaySSLTest.QUEUE, false);
ClientProducer producer = session.createProducer(CoreClientOverTwoWaySSLTest.QUEUE);
ClientMessage message = createTextMessage(session, text);
producer.send(message);
ClientConsumer consumer = session.createConsumer(CoreClientOverTwoWaySSLTest.QUEUE);
session.start();
Message m = consumer.receive(1000);
Assert.assertNotNull(m);
Assert.assertEquals(text, m.getBodyBuffer().readString());
}
@Test
public void testTwoWaySSLVerifyClientHostNegative() throws Exception {
NettyAcceptor acceptor = (NettyAcceptor) server.getRemotingService().getAcceptor("nettySSL");
acceptor.getConfiguration().put(TransportConstants.VERIFY_HOST_PROP_NAME, true);
server.getRemotingService().stop(false);
server.getRemotingService().start();
server.getRemotingService().startAcceptors();
tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
tc.getParams().put(TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME, storeType);
tc.getParams().put(TransportConstants.KEYSTORE_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.KEYSTORE_PATH_PROP_NAME, CLIENT_SIDE_KEYSTORE);
tc.getParams().put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, PASSWORD);
server.getRemotingService().addIncomingInterceptor(new MyInterceptor());
ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc));
try {
ClientSessionFactory sf = createSessionFactory(locator);
fail("Creating a session here should fail due to a certificate with a CN that doesn't match the host name.");
}
catch (Exception e) {
// ignore
}
}
@Test
public void testTwoWaySSLWithoutClientKeyStore() throws Exception {
tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
@ -183,7 +260,7 @@ public class CoreClientOverTwoWaySSLTest extends ActiveMQTestBase {
params.put(TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME, storeType);
params.put(TransportConstants.KEYSTORE_PROVIDER_PROP_NAME, storeType);
params.put(TransportConstants.NEED_CLIENT_AUTH_PROP_NAME, true);
ConfigurationImpl config = createBasicConfig().addAcceptorConfiguration(new TransportConfiguration(NETTY_ACCEPTOR_FACTORY, params));
ConfigurationImpl config = createBasicConfig().addAcceptorConfiguration(new TransportConfiguration(NETTY_ACCEPTOR_FACTORY, params, "nettySSL"));
server = createServer(false, config);
server.start();
waitForServerToStart(server);