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