From 8d8eaebb65b4a3be7f27fb004301b5b0e76a421c Mon Sep 17 00:00:00 2001 From: Justin Bertram Date: Thu, 19 Dec 2019 09:03:57 -0600 Subject: [PATCH] ARTEMIS-2580 support pluggable SSL TrustManagerFactory --- .../api/core/TrustManagerFactoryPlugin.java | 28 +++++++++++++ .../remoting/impl/netty/NettyConnector.java | 7 ++++ .../impl/netty/TransportConstants.java | 6 +++ .../core/remoting/impl/ssl/SSLSupport.java | 15 ++++++- .../remoting/impl/netty/NettyAcceptor.java | 7 ++++ docs/user-manual/en/configuring-transports.md | 17 ++++++++ .../ssl/CoreClientOverOneWaySSLTest.java | 42 +++++++++++++++++++ .../ssl/TestTrustManagerFactoryPlugin.java | 36 ++++++++++++++++ 8 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/TrustManagerFactoryPlugin.java create mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/TestTrustManagerFactoryPlugin.java diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/TrustManagerFactoryPlugin.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/TrustManagerFactoryPlugin.java new file mode 100644 index 0000000000..707c698f19 --- /dev/null +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/TrustManagerFactoryPlugin.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.activemq.artemis.api.core; + +import javax.net.ssl.TrustManagerFactory; + +public interface TrustManagerFactoryPlugin { + + /** + * @return the TrustManagerFactory used when invoking javax.net.ssl.TrustManagerFactory#getTrustManagers() to initialize the SSLContext + */ + TrustManagerFactory getTrustManagerFactory(); +} 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 29cc9cdf0a..ecccf001ea 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 @@ -222,6 +222,8 @@ public class NettyConnector extends AbstractConnector { private String sslProvider; + private String trustManagerFactoryPlugin; + private boolean verifyHost; private boolean trustAll; @@ -371,6 +373,8 @@ public class NettyConnector extends AbstractConnector { kerb5Config = ConfigurationHelper.getStringProperty(TransportConstants.SSL_KRB5_CONFIG_PROP_NAME, TransportConstants.DEFAULT_SSL_KRB5_CONFIG, configuration); useDefaultSslContext = ConfigurationHelper.getBooleanProperty(TransportConstants.USE_DEFAULT_SSL_CONTEXT_PROP_NAME, TransportConstants.DEFAULT_USE_DEFAULT_SSL_CONTEXT, configuration); + + trustManagerFactoryPlugin = ConfigurationHelper.getStringProperty(TransportConstants.TRUST_MANAGER_FACTORY_PLUGIN_PROP_NAME, TransportConstants.DEFAULT_TRUST_MANAGER_FACTORY_PLUGIN, configuration); } else { keyStoreProvider = TransportConstants.DEFAULT_KEYSTORE_PROVIDER; keyStorePath = TransportConstants.DEFAULT_KEYSTORE_PATH; @@ -385,6 +389,7 @@ public class NettyConnector extends AbstractConnector { trustAll = TransportConstants.DEFAULT_TRUST_ALL; sniHost = TransportConstants.DEFAULT_SNIHOST_CONFIG; useDefaultSslContext = TransportConstants.DEFAULT_USE_DEFAULT_SSL_CONTEXT; + trustManagerFactoryPlugin = TransportConstants.DEFAULT_TRUST_MANAGER_FACTORY_PLUGIN; } tcpNoDelay = ConfigurationHelper.getBooleanProperty(TransportConstants.TCP_NODELAY_PROPNAME, TransportConstants.DEFAULT_TCP_NODELAY, configuration); @@ -634,6 +639,7 @@ public class NettyConnector extends AbstractConnector { .setTruststorePassword(truststorePassword) .setTrustAll(trustAll) .setCrlPath(crlPath) + .setTrustManagerFactoryPlugin(trustManagerFactoryPlugin) .createContext(); } Subject subject = null; @@ -675,6 +681,7 @@ public class NettyConnector extends AbstractConnector { .setTruststorePassword(truststorePassword) .setSslProvider(sslProvider) .setTrustAll(trustAll) + .setTrustManagerFactoryPlugin(trustManagerFactoryPlugin) .createNettyClientContext(); Subject subject = null; diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/TransportConstants.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/TransportConstants.java index 5b06415269..d9d2d12299 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/TransportConstants.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/netty/TransportConstants.java @@ -122,6 +122,8 @@ public class TransportConstants { public static final String SSL_PROVIDER = "sslProvider"; + public static final String TRUST_MANAGER_FACTORY_PLUGIN_PROP_NAME = "trustManagerFactoryPlugin"; + public static final String NETTY_VERSION; /** @@ -218,6 +220,8 @@ public class TransportConstants { public static final boolean DEFAULT_TRUST_ALL = false; + public static final String DEFAULT_TRUST_MANAGER_FACTORY_PLUGIN = null; + public static final boolean DEFAULT_FORCE_SSL_PARAMETERS = false; public static final boolean DEFAULT_USE_DEFAULT_SSL_CONTEXT = false; @@ -364,6 +368,7 @@ public class TransportConstants { allowableAcceptorKeys.add(TransportConstants.CRL_PATH_PROP_NAME); allowableAcceptorKeys.add(TransportConstants.HANDSHAKE_TIMEOUT); allowableAcceptorKeys.add(TransportConstants.SSL_PROVIDER); + allowableAcceptorKeys.add(TransportConstants.TRUST_MANAGER_FACTORY_PLUGIN_PROP_NAME); allowableAcceptorKeys.add(TransportConstants.SHUTDOWN_TIMEOUT); allowableAcceptorKeys.add(TransportConstants.QUIET_PERIOD); @@ -413,6 +418,7 @@ public class TransportConstants { allowableConnectorKeys.add(TransportConstants.NETTY_CONNECT_TIMEOUT); allowableConnectorKeys.add(TransportConstants.USE_DEFAULT_SSL_CONTEXT_PROP_NAME); allowableConnectorKeys.add(TransportConstants.SSL_PROVIDER); + allowableConnectorKeys.add(TransportConstants.TRUST_MANAGER_FACTORY_PLUGIN_PROP_NAME); allowableConnectorKeys.add(TransportConstants.HANDSHAKE_TIMEOUT); allowableConnectorKeys.add(TransportConstants.CRL_PATH_PROP_NAME); diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/ssl/SSLSupport.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/ssl/SSLSupport.java index 507f8eaefc..9e2d3192d9 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/ssl/SSLSupport.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/ssl/SSLSupport.java @@ -44,6 +44,7 @@ import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import org.apache.activemq.artemis.api.core.TrustManagerFactoryPlugin; import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; import org.apache.activemq.artemis.utils.ClassloadingUtil; @@ -63,6 +64,7 @@ public class SSLSupport { private String crlPath = TransportConstants.DEFAULT_CRL_PATH; private String sslProvider = TransportConstants.DEFAULT_SSL_PROVIDER; private boolean trustAll = TransportConstants.DEFAULT_TRUST_ALL; + private String trustManagerFactoryPlugin = TransportConstants.DEFAULT_TRUST_MANAGER_FACTORY_PLUGIN; public String getKeystoreProvider() { return keystoreProvider; @@ -145,6 +147,15 @@ public class SSLSupport { return this; } + public String getTrustManagerFactoryPlugin() { + return trustManagerFactoryPlugin; + } + + public SSLSupport setTrustManagerFactoryPlugin(String trustManagerFactoryPlugin) { + this.trustManagerFactoryPlugin = trustManagerFactoryPlugin; + return this; + } + public SSLContext createContext() throws Exception { SSLContext context = SSLContext.getInstance("TLS"); KeyManager[] keyManagers = loadKeyManagers(); @@ -190,7 +201,9 @@ public class SSLSupport { // Private ------------------------------------------------------- private TrustManagerFactory loadTrustManagerFactory() throws Exception { - if (trustAll) { + if (trustManagerFactoryPlugin != null) { + return AccessController.doPrivileged((PrivilegedAction) () -> ((TrustManagerFactoryPlugin) ClassloadingUtil.newInstanceFromClassLoader(SSLSupport.class, trustManagerFactoryPlugin)).getTrustManagerFactory()); + } else if (trustAll) { //This is useful for testing but not should be used outside of that purpose return InsecureTrustManagerFactory.INSTANCE; } else if (truststorePath == null && (truststoreProvider == null || !"PKCS11".equals(truststoreProvider.toUpperCase()))) { 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 0bc99c6db5..a528099a9a 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 @@ -177,6 +177,8 @@ public class NettyAcceptor extends AbstractAcceptor { private final boolean verifyHost; + private final String trustManagerFactoryPlugin; + private final String kerb5Config; private String sniHost; @@ -303,6 +305,8 @@ public class NettyAcceptor extends AbstractAcceptor { sslProvider = ConfigurationHelper.getStringProperty(TransportConstants.SSL_PROVIDER, TransportConstants.DEFAULT_SSL_PROVIDER, configuration); sniHost = ConfigurationHelper.getStringProperty(TransportConstants.SNIHOST_PROP_NAME, TransportConstants.DEFAULT_SNIHOST_CONFIG, configuration); + + trustManagerFactoryPlugin = ConfigurationHelper.getStringProperty(TransportConstants.TRUST_MANAGER_FACTORY_PLUGIN_PROP_NAME, TransportConstants.DEFAULT_TRUST_MANAGER_FACTORY_PLUGIN, configuration); } else { keyStoreProvider = TransportConstants.DEFAULT_KEYSTORE_PROVIDER; keyStorePath = TransportConstants.DEFAULT_KEYSTORE_PATH; @@ -318,6 +322,7 @@ public class NettyAcceptor extends AbstractAcceptor { verifyHost = TransportConstants.DEFAULT_VERIFY_HOST; sslProvider = TransportConstants.DEFAULT_SSL_PROVIDER; sniHost = TransportConstants.DEFAULT_SNIHOST_CONFIG; + trustManagerFactoryPlugin = TransportConstants.DEFAULT_TRUST_MANAGER_FACTORY_PLUGIN; } tcpNoDelay = ConfigurationHelper.getBooleanProperty(TransportConstants.TCP_NODELAY_PROPNAME, TransportConstants.DEFAULT_TCP_NODELAY, configuration); @@ -580,6 +585,7 @@ public class NettyAcceptor extends AbstractAcceptor { .setTruststorePath(trustStorePath) .setTruststorePassword(trustStorePassword) .setCrlPath(crlPath) + .setTrustManagerFactoryPlugin(trustManagerFactoryPlugin) .createContext(); } catch (Exception e) { IllegalStateException ise = new IllegalStateException("Unable to create NettyAcceptor for " + host + ":" + port); @@ -619,6 +625,7 @@ public class NettyAcceptor extends AbstractAcceptor { .setTruststorePath(trustStorePath) .setTruststorePassword(trustStorePassword) .setSslProvider(sslProvider) + .setTrustManagerFactoryPlugin(trustManagerFactoryPlugin) .createNettyContext(); } catch (Exception e) { IllegalStateException ise = new IllegalStateException("Unable to create NettyAcceptor for " + host + ":" + port); diff --git a/docs/user-manual/en/configuring-transports.md b/docs/user-manual/en/configuring-transports.md index 00df421bb4..c941735f80 100644 --- a/docs/user-manual/en/configuring-transports.md +++ b/docs/user-manual/en/configuring-transports.md @@ -472,6 +472,23 @@ additional properties: When used on a `connector` the `sniHost` value is used for the `server_name` extension on the SSL connection. +- `trustManagerFactoryPlugin` + + This is valid on either an `acceptor` or `connector`. It defines the name + of the class which implements `org.apache.activemq.artemis.api.core.TrustManagerFactoryPlugin`. + This is a simple interface with a single method which returns a + `javax.net.ssl.TrustManagerFactory`. The `TrustManagerFactory` will be used + when the underlying `javax.net.ssl.SSLContext` is initialized. This allows + fine-grained customization of who/what the broker & client trusts. + + This value takes precedence of all other SSL parameters which apply to the + trust manager (i.e. `trustAll`, `truststoreProvider`, `truststorePath`, + `truststorePassword`, `crlPath`). + + Any plugin specified will need to be placed on the + [broker's classpath](using-server.md#adding-runtime-dependencies). + + ### 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 c6457c6542..26c84e2a70 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 @@ -255,6 +255,36 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase { Assert.assertEquals(text, m.getBodyBuffer().readString()); } + @Test + public void testOneWaySSLwithTrustManagerPlugin() throws Exception { + createCustomSslServer(null, null, false, null, TestTrustManagerFactoryPlugin.class.getName()); + 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)); + + assertTrue(TestTrustManagerFactoryPlugin.triggered.get()); + + 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(); @@ -880,6 +910,14 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase { String protocols, boolean useVerifiedKeystore, String sniHost) throws Exception { + createCustomSslServer(null, null, useVerifiedKeystore, null, null); + } + + private void createCustomSslServer(String cipherSuites, + String protocols, + boolean useVerifiedKeystore, + String sniHost, + String trustManagerFactoryPlugin) throws Exception { Map params = new HashMap<>(); params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true); params.put(TransportConstants.KEYSTORE_PROVIDER_PROP_NAME, storeType); @@ -904,6 +942,10 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase { params.put(TransportConstants.ENABLED_PROTOCOLS_PROP_NAME, protocols); } + if (trustManagerFactoryPlugin != null) { + params.put(TransportConstants.TRUST_MANAGER_FACTORY_PLUGIN_PROP_NAME, trustManagerFactoryPlugin); + } + ConfigurationImpl config = createBasicConfig().addAcceptorConfiguration(new TransportConfiguration(NETTY_ACCEPTOR_FACTORY, params, "nettySSL")); server = createServer(false, config); server.start(); diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/TestTrustManagerFactoryPlugin.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/TestTrustManagerFactoryPlugin.java new file mode 100644 index 0000000000..b5391ba011 --- /dev/null +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/TestTrustManagerFactoryPlugin.java @@ -0,0 +1,36 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.activemq.artemis.tests.integration.ssl; + +import javax.net.ssl.TrustManagerFactory; + +import java.util.concurrent.atomic.AtomicBoolean; + +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import org.apache.activemq.artemis.api.core.TrustManagerFactoryPlugin; + +public class TestTrustManagerFactoryPlugin implements TrustManagerFactoryPlugin { + + public static AtomicBoolean triggered = new AtomicBoolean(false); + + @Override + public TrustManagerFactory getTrustManagerFactory() { + triggered.set(true); + return InsecureTrustManagerFactory.INSTANCE; + } +}