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 682cb8a161..e677e1d9d5 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 @@ -103,7 +103,6 @@ import io.netty.util.ResourceLeakDetector; import io.netty.util.ResourceLeakDetector.Level; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GlobalEventExecutor; -import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration; import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.core.client.ActiveMQClientLogger; import org.apache.activemq.artemis.core.client.ActiveMQClientMessageBundle; @@ -123,6 +122,9 @@ import org.jboss.logging.Logger; import static org.apache.activemq.artemis.utils.Base64.encodeBytes; +import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration; +import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactoryProvider; + public class NettyConnector extends AbstractConnector { public static String NIO_CONNECTOR_TYPE = "NIO"; @@ -674,22 +676,10 @@ public class NettyConnector extends AbstractConnector { String truststoreProvider, String truststorePath, String truststorePassword) throws Exception { - SSLContext context; - if (useDefaultSslContext) { - context = SSLContext.getDefault(); - } else { - context = new SSLSupport() - .setKeystoreProvider(keystoreProvider) - .setKeystorePath(keystorePath) - .setKeystorePassword(keystorePassword) - .setTruststoreProvider(truststoreProvider) - .setTruststorePath(truststorePath) - .setTruststorePassword(truststorePassword) - .setTrustAll(trustAll) - .setCrlPath(crlPath) - .setTrustManagerFactoryPlugin(trustManagerFactoryPlugin) - .createContext(); - } + SSLContext context = SSLContextFactoryProvider.getSSLContextFactory().getSSLContext(configuration, + keystoreProvider, keystorePath, keystorePassword, + truststoreProvider, truststorePath, truststorePassword, + crlPath, trustManagerFactoryPlugin, trustAll); Subject subject = null; if (kerb5Config != null) { LoginContext loginContext = new LoginContext(kerb5Config); 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 d5b12ce100..7f304e1d91 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 @@ -29,6 +29,8 @@ public class TransportConstants { private static final Logger logger = Logger.getLogger(TransportConstants.class); + public static final String SSL_CONTEXT_PROP_NAME = "sslContext"; + public static final String SSL_ENABLED_PROP_NAME = "sslEnabled"; public static final String SSL_KRB5_CONFIG_PROP_NAME = "sslKrb5Config"; diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/ssl/CachingSSLContextFactory.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/ssl/CachingSSLContextFactory.java new file mode 100644 index 0000000000..8fce20b394 --- /dev/null +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/ssl/CachingSSLContextFactory.java @@ -0,0 +1,84 @@ +/* + * Copyright 2020 The Apache Software Foundation. + * + * 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.apache.activemq.artemis.core.remoting.impl.ssl; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.net.ssl.SSLContext; +import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; +import org.apache.activemq.artemis.utils.ConfigurationHelper; + +/** + * SSLContextFactory providing a cache of SSLContext. + * Since SSLContext should be reused instead of recreated and are thread safe. + * To activate it uou need to allow this Service to be discovered by having a + * META-INF/services/org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactory + * file with org.apache.activemq.artemis.core.remoting.impl.ssl.CachingSSLContextFactory + * as value. + */ +public class CachingSSLContextFactory extends DefaultSSLContextFactory { + + private static final Map SSL_CONTEXTS = Collections.synchronizedMap(new HashMap<>()); + + @Override + public void clearSSLContexts() { + SSL_CONTEXTS.clear(); + } + + @Override + public SSLContext getSSLContext(Map configuration, + String keystoreProvider, String keystorePath, String keystorePassword, + String truststoreProvider, String truststorePath, String truststorePassword, + String crlPath, String trustManagerFactoryPlugin, boolean trustAll) throws Exception { + String sslContextName = getSSLContextName(configuration, keystorePath, keystoreProvider, truststorePath, truststoreProvider); + if (!SSL_CONTEXTS.containsKey(sslContextName)) { + SSL_CONTEXTS.put(sslContextName, createSSLContext(configuration, + keystoreProvider, keystorePath, keystorePassword, + truststoreProvider, truststorePath, truststorePassword, + crlPath, trustManagerFactoryPlugin, trustAll)); + } + return SSL_CONTEXTS.get(sslContextName); + } + + /** + * Obtain the sslContextName : + * - if available the 'sslContext' from the configuration + * - otherwise if available the keyStorePath + '_' + keystoreProvider + * - otherwise the truststorePath + '_' + truststoreProvider. + * @param configuration + * @param keyStorePath + * @param keystoreProvider + * @param truststorePath + * @param truststoreProvider + * @return the ley associated to the SSLContext. + */ + protected String getSSLContextName(Map configuration, String keyStorePath, String keystoreProvider, String truststorePath, String truststoreProvider) { + String sslContextName = ConfigurationHelper.getStringProperty(TransportConstants.SSL_CONTEXT_PROP_NAME, null, configuration); + if (sslContextName == null) { + if (keyStorePath != null) { + return keyStorePath + '_' + keystoreProvider; + } + return truststorePath + '_' + truststoreProvider; + } + return sslContextName; + } + + @Override + public int getPriority() { + return 10; + } +} diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/ssl/DefaultSSLContextFactory.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/ssl/DefaultSSLContextFactory.java new file mode 100644 index 0000000000..133cc20159 --- /dev/null +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/ssl/DefaultSSLContextFactory.java @@ -0,0 +1,70 @@ +/* + * Copyright 2020 The Apache Software Foundation. + * + * 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.apache.activemq.artemis.core.remoting.impl.ssl; + +import java.util.Map; +import javax.net.ssl.SSLContext; +import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; +import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactory; +import org.apache.activemq.artemis.utils.ConfigurationHelper; + +/** + * Simple SSLContextFactory for use in NettyConnector and NettyAcceptor. + */ +public class DefaultSSLContextFactory implements SSLContextFactory { + + + @Override + public SSLContext getSSLContext(Map configuration, + String keystoreProvider, String keystorePath, String keystorePassword, + String truststoreProvider, String truststorePath, String truststorePassword, + String crlPath, String trustManagerFactoryPlugin, boolean trustAll) throws Exception { + return createSSLContext(configuration, + keystoreProvider, keystorePath, keystorePassword, + truststoreProvider, truststorePath, truststorePassword, + crlPath, trustManagerFactoryPlugin, trustAll); + } + + protected SSLContext createSSLContext(Map configuration, + String keystoreProvider, String keystorePath, String keystorePassword, + String truststoreProvider, String truststorePath, String truststorePassword, + String crlPath, String trustManagerFactoryPlugin, boolean trustAll) throws Exception { + if (log.isDebugEnabled()) { + final StringBuilder builder = new StringBuilder(); + configuration.forEach((k, v) -> builder.append("\r\n").append(k).append("=").append(v)); + log.debugf("Creating SSL context with configuration %s", builder.toString()); + } + boolean useDefaultSslContext = ConfigurationHelper.getBooleanProperty(TransportConstants.USE_DEFAULT_SSL_CONTEXT_PROP_NAME, TransportConstants.DEFAULT_USE_DEFAULT_SSL_CONTEXT, configuration); + if (useDefaultSslContext) { + return SSLContext.getDefault(); + } + return new SSLSupport() + .setKeystoreProvider(keystoreProvider) + .setKeystorePath(keystorePath) + .setKeystorePassword(keystorePassword) + .setTruststoreProvider(truststoreProvider) + .setTruststorePath(truststorePath) + .setTruststorePassword(truststorePassword) + .setTrustAll(trustAll) + .setCrlPath(crlPath) + .setTrustManagerFactoryPlugin(trustManagerFactoryPlugin) + .createContext(); + } + @Override + public int getPriority() { + return 5; + } +} diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ssl/SSLContextFactory.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ssl/SSLContextFactory.java new file mode 100644 index 0000000000..436092d84c --- /dev/null +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ssl/SSLContextFactory.java @@ -0,0 +1,67 @@ +/* + * Copyright 2020 The Apache Software Foundation. + * + * 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.apache.activemq.artemis.spi.core.remoting.ssl; + +import java.util.Map; +import javax.net.ssl.SSLContext; +import org.jboss.logging.Logger; + +/** + * Service interface to create a SSLContext for a configuration. + * This is NOT used by OpenSSL. + * To create and use your own implementation you need to create a file + * META-INF/services/org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactory + * in your jar and fill it with the full qualified name of your implementation. + */ +public interface SSLContextFactory extends Comparable { + Logger log = Logger.getLogger(SSLContextFactory.class); + + /** + * Obtain a SSLContext from the configuration. + * @param configuration + * @param keystoreProvider + * @param keystorePath + * @param keystorePassword + * @param truststoreProvider + * @param truststorePath + * @param truststorePassword + * @param crlPath + * @param trustManagerFactoryPlugin + * @param trustAll + * @return a SSLContext instance. + * @throws Exception + */ + SSLContext getSSLContext(Map configuration, + String keystoreProvider, String keystorePath, String keystorePassword, + String truststoreProvider, String truststorePath, String truststorePassword, + String crlPath, String trustManagerFactoryPlugin, boolean trustAll) throws Exception; + + default void clearSSLContexts() { + } + + /** + * The priority for the SSLContextFactory when resolving the service to get the implementation. + * This is used when selecting the implementation when several implementations are loaded. + * The highest priority implementation will be used. + * @return the priority. + */ + int getPriority(); + + @Override + default int compareTo(SSLContextFactory other) { + return this.getPriority() - other.getPriority(); + } +} diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ssl/SSLContextFactoryProvider.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ssl/SSLContextFactoryProvider.java new file mode 100644 index 0000000000..fb3f3bf271 --- /dev/null +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ssl/SSLContextFactoryProvider.java @@ -0,0 +1,42 @@ +/* + * Copyright 2020 The Apache Software Foundation. + * + * 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.apache.activemq.artemis.spi.core.remoting.ssl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.ServiceLoader; + +/** + * Provider that loads the SSLContextFactory services and return the one with the highest priority. + * This is only used to provide SSLContext, so it doesn't support OpenSSL. + */ +public class SSLContextFactoryProvider { + private static final SSLContextFactory factory; + static { + ServiceLoader loader = ServiceLoader.load(SSLContextFactory.class, Thread.currentThread().getContextClassLoader()); + final List factories = new ArrayList<>(); + loader.forEach(factories::add); + Collections.sort(factories); + factory = factories.get(factories.size() - 1); + } + /** + * @return the SSLContextFactory with the higher priority. + */ + public static SSLContextFactory getSSLContextFactory() { + return factory; + } +} diff --git a/artemis-core-client/src/main/resources/META-INF/services/org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactory b/artemis-core-client/src/main/resources/META-INF/services/org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactory new file mode 100644 index 0000000000..047f434e0b --- /dev/null +++ b/artemis-core-client/src/main/resources/META-INF/services/org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactory @@ -0,0 +1 @@ +org.apache.activemq.artemis.core.remoting.impl.ssl.DefaultSSLContextFactory \ No newline at end of file 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 d207fdf21f..716123a86f 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 @@ -92,6 +92,7 @@ import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager; import org.apache.activemq.artemis.spi.core.remoting.BufferHandler; import org.apache.activemq.artemis.spi.core.remoting.Connection; import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener; +import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactoryProvider; import org.apache.activemq.artemis.utils.ActiveMQThreadFactory; import org.apache.activemq.artemis.utils.ConfigurationHelper; import org.apache.activemq.artemis.utils.collections.TypedProperties; @@ -489,6 +490,7 @@ public class NettyAcceptor extends AbstractAcceptor { // only for testing purposes public void setKeyStorePath(String keyStorePath) { this.keyStorePath = keyStorePath; + this.configuration.put(TransportConstants.KEYSTORE_PATH_PROP_NAME, keyStorePath); } /** @@ -519,7 +521,7 @@ public class NettyAcceptor extends AbstractAcceptor { public synchronized SslHandler getSslHandler(ByteBufAllocator alloc, String peerHost, int peerPort) throws Exception { SSLEngine engine; - if (sslProvider.equals(TransportConstants.OPENSSL_PROVIDER)) { + if (TransportConstants.OPENSSL_PROVIDER.equals(sslProvider)) { engine = loadOpenSslEngine(alloc, peerHost, peerPort); } else { engine = loadJdkSslEngine(peerHost, peerPort); @@ -594,21 +596,13 @@ public class NettyAcceptor extends AbstractAcceptor { private SSLEngine loadJdkSslEngine(String peerHost, int peerPort) throws Exception { final SSLContext context; try { - if (kerb5Config == null && keyStorePath == null && TransportConstants.DEFAULT_TRUSTSTORE_PROVIDER.equals(keyStoreProvider)) - throw new IllegalArgumentException("If \"" + TransportConstants.SSL_ENABLED_PROP_NAME + "\" is true then \"" + TransportConstants.KEYSTORE_PATH_PROP_NAME + "\" must be non-null " + "unless an alternative \"" + TransportConstants.KEYSTORE_PROVIDER_PROP_NAME + "\" has been specified."); - context = new SSLSupport() - .setKeystoreProvider(keyStoreProvider) - .setKeystorePath(keyStorePath) - .setKeystorePassword(keyStorePassword) - .setTruststoreProvider(trustStoreProvider) - .setTruststorePath(trustStorePath) - .setTruststorePassword(trustStorePassword) - .setCrlPath(crlPath) - .setTrustManagerFactoryPlugin(trustManagerFactoryPlugin) - .createContext(); + checkSSLConfiguration(); + context = SSLContextFactoryProvider.getSSLContextFactory().getSSLContext(configuration, + keyStoreProvider, keyStorePath, keyStorePassword, + trustStoreProvider, trustStorePath, trustStorePassword, + crlPath, trustManagerFactoryPlugin, TransportConstants.DEFAULT_TRUST_ALL); } catch (Exception e) { - IllegalStateException ise = new IllegalStateException("Unable to create NettyAcceptor for " + host + ":" + port); - ise.initCause(e); + IllegalStateException ise = new IllegalStateException("Unable to create NettyAcceptor for " + host + ":" + port, e); throw ise; } Subject subject = null; @@ -631,11 +625,19 @@ public class NettyAcceptor extends AbstractAcceptor { return engine; } + private void checkSSLConfiguration() throws IllegalArgumentException { + if (configuration.containsKey(TransportConstants.SSL_CONTEXT_PROP_NAME)) { + return; + } + if (kerb5Config == null && keyStorePath == null && TransportConstants.DEFAULT_TRUSTSTORE_PROVIDER.equals(keyStoreProvider)) { + throw new IllegalArgumentException("If \"" + TransportConstants.SSL_ENABLED_PROP_NAME + "\" is true then \"" + TransportConstants.KEYSTORE_PATH_PROP_NAME + "\" must be non-null " + "unless an alternative \"" + TransportConstants.KEYSTORE_PROVIDER_PROP_NAME + "\" has been specified."); + } + } + private SSLEngine loadOpenSslEngine(ByteBufAllocator alloc, String peerHost, int peerPort) throws Exception { final SslContext context; try { - if (kerb5Config == null && keyStorePath == null && TransportConstants.DEFAULT_TRUSTSTORE_PROVIDER.equals(keyStoreProvider)) - throw new IllegalArgumentException("If \"" + TransportConstants.SSL_ENABLED_PROP_NAME + "\" is true then \"" + TransportConstants.KEYSTORE_PATH_PROP_NAME + "\" must be non-null " + "unless an alternative \"" + TransportConstants.KEYSTORE_PROVIDER_PROP_NAME + "\" has been specified."); + checkSSLConfiguration(); context = new SSLSupport() .setKeystoreProvider(keyStoreProvider) .setKeystorePath(keyStorePath) @@ -647,8 +649,7 @@ public class NettyAcceptor extends AbstractAcceptor { .setTrustManagerFactoryPlugin(trustManagerFactoryPlugin) .createNettyContext(); } catch (Exception e) { - IllegalStateException ise = new IllegalStateException("Unable to create NettyAcceptor for " + host + ":" + port); - ise.initCause(e); + IllegalStateException ise = new IllegalStateException("Unable to create NettyAcceptor for " + host + ":" + port, e); throw ise; } Subject subject = null; diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/server/impl/RemotingServiceImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/server/impl/RemotingServiceImpl.java index ca5093448f..d77f9a38d1 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/server/impl/RemotingServiceImpl.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/remoting/server/impl/RemotingServiceImpl.java @@ -70,6 +70,7 @@ import org.apache.activemq.artemis.spi.core.remoting.AcceptorFactory; import org.apache.activemq.artemis.spi.core.remoting.BufferHandler; import org.apache.activemq.artemis.spi.core.remoting.Connection; import org.apache.activemq.artemis.spi.core.remoting.ServerConnectionLifeCycleListener; +import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactoryProvider; import org.apache.activemq.artemis.utils.ActiveMQThreadFactory; import org.apache.activemq.artemis.utils.ConfigurationHelper; import org.apache.activemq.artemis.utils.ReusableLatch; @@ -380,6 +381,7 @@ public class RemotingServiceImpl implements RemotingService, ServerConnectionLif if (!started) { return; } + SSLContextFactoryProvider.getSSLContextFactory().clearSSLContexts(); failureCheckAndFlushThread.close(criticalError); diff --git a/docs/user-manual/en/configuring-transports.md b/docs/user-manual/en/configuring-transports.md index ce13166dce..40f844a5da 100644 --- a/docs/user-manual/en/configuring-transports.md +++ b/docs/user-manual/en/configuring-transports.md @@ -313,6 +313,12 @@ Please see the examples for a full working example of using Netty SSL. Netty SSL uses all the same properties as Netty TCP but adds the following additional properties: +- `sslContext` + +A key that can be used in conjunction with `org.apache.activemq.artemis.core.remoting.impl.ssl.CachingSSLContextFactory` +to cache created SSLContext and avoid recreating. Look [Configuring a SSLContextFactory](#Configuring a SSLContextFactory) +for more details. + - `sslEnabled` Must be `true` to enable SSL. Default is `false`. @@ -490,6 +496,24 @@ additional properties: [broker's classpath](using-server.md#adding-runtime-dependencies). +#### Configuring a SSLContextFactory + +If you have a `JDK` provider you can configure which SSLContextFactory to use. +Currently we provide two implementations: +- `org.apache.activemq.artemis.core.remoting.impl.ssl.DefaultSSLContextFactory` +- `org.apache.activemq.artemis.core.remoting.impl.ssl.CachingSSLContextFactory` +but you can also add your own implementation of `org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactory`. + +The implementations are loaded by a ServiceLoader, thus you need to declare your implementation in +a `META-INF/services/org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactory` file. +If several implementations are available, the one with the highest `priority` will be selected. +So for example, if you want to use `org.apache.activemq.artemis.core.remoting.impl.ssl.CachingSSLContextFactory` +you need to add a `META-INF/services/org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextFactory` file +to your classpath with the content `org.apache.activemq.artemis.core.remoting.impl.ssl.CachingSSLContextFactory`. + +**Note:** This mechanism doesn't work if you have selected `OPENSSL` as provider. + + ### Configuring Netty HTTP Netty HTTP tunnels packets over the HTTP protocol. It can be useful in