This is an automated email from the ASF dual-hosted git repository.
jbertram pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/artemis.git
The following commit(s) were added to refs/heads/main by this push:
new fa1da6e630 ARTEMIS-5778 Improve OCSP revocation checker support
fa1da6e630 is described below
commit fa1da6e6301fd89f7ec6dcdb98fd4366597082fa
Author: Domenico Francesco Bruscino <[email protected]>
AuthorDate: Mon Dec 1 21:45:39 2025 +0100
ARTEMIS-5778 Improve OCSP revocation checker support
Add the parameters crcOptions and ocspResponderURL to customize the
options and responder URL used by the OCSP revocation checker.
---
.../artemis/core/client/ActiveMQClientLogger.java | 3 +
.../core/remoting/impl/netty/NettyConnector.java | 20 +++
.../remoting/impl/netty/TransportConstants.java | 12 ++
.../artemis/core/remoting/impl/ssl/SSLSupport.java | 135 ++++++++++++++-
.../spi/core/remoting/ssl/SSLContextConfig.java | 40 ++++-
.../core/remoting/impl/ssl/SSLSupportTest.java | 104 ++++++++++++
artemis-pom/pom.xml | 35 ++++
artemis-server/pom.xml | 19 ---
.../core/remoting/impl/netty/NettyAcceptor.java | 16 ++
.../security/jaas/LDAPLoginSSLSocketFactory.java | 8 +
.../core/security/jaas/LDAPLoginModuleTest.java | 35 ++++
.../jaas/LDAPLoginSSLSocketFactoryTest.java | 4 +
docs/user-manual/configuring-transports.adoc | 21 ++-
docs/user-manual/security.adoc | 14 +-
tests/integration-tests/pom.xml | 5 +
.../ssl/CoreClientOverOneWaySSLTest.java | 60 +++++++
.../ssl/CoreClientOverTwoWaySSLTest.java | 64 +++++++
.../remoting/impl/netty/NettyAcceptorTest.java | 23 +++
.../remoting/impl/netty/NettyConnectorTest.java | 29 ++++
.../remoting/impl/ssl/SSLContextConfigTest.java | 187 +++++++++++++++++++++
.../core/remoting/impl/ssl/SSLSupportTest.java | 64 +++++++
21 files changed, 867 insertions(+), 31 deletions(-)
diff --git
a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientLogger.java
b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientLogger.java
index e7c1c7108d..ce5007d0a0 100644
---
a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientLogger.java
+++
b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientLogger.java
@@ -260,6 +260,9 @@ public interface ActiveMQClientLogger {
@LogMessage(id = 212080, value = "Using legacy SSL store provider value:
{}. Please use either 'keyStoreType' or 'trustStoreType' instead as
appropriate.", level = LogMessage.Level.WARN)
void oldStoreProvider(String value);
+ @LogMessage(id = 212081, value = "Soft failure checking the certificate
[{}]: {}", level = LogMessage.Level.WARN)
+ void softFailException(String certSubject, Exception e);
+
@LogMessage(id = 214000, value = "Failed to call onMessage", level =
LogMessage.Level.ERROR)
void onMessageError(Throwable e);
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 898dbb9ae7..30071582d8 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
@@ -257,6 +257,10 @@ public class NettyConnector extends AbstractConnector {
private String crlPath;
+ private String crcOptions;
+
+ private String ocspResponderURL;
+
private String enabledCipherSuites;
private String enabledProtocols;
@@ -451,12 +455,18 @@ public class NettyConnector extends AbstractConnector {
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);
+
+ crcOptions =
ConfigurationHelper.getStringProperty(TransportConstants.CRC_OPTIONS_PROP_NAME,
TransportConstants.DEFAULT_CRC_OPTIONS, configuration);
+
+ ocspResponderURL =
ConfigurationHelper.getStringProperty(TransportConstants.OCSP_RESPONDER_URL_PROP_NAME,
TransportConstants.DEFAULT_OCSP_RESPONDER_URL, configuration);
} else {
keyStoreProvider = TransportConstants.DEFAULT_KEYSTORE_PROVIDER;
keyStoreType = TransportConstants.DEFAULT_KEYSTORE_TYPE;
keyStorePath = TransportConstants.DEFAULT_KEYSTORE_PATH;
keyStorePassword = TransportConstants.DEFAULT_KEYSTORE_PASSWORD;
keyStoreAlias = TransportConstants.DEFAULT_KEYSTORE_ALIAS;
+ crcOptions = TransportConstants.DEFAULT_CRC_OPTIONS;
+ ocspResponderURL = TransportConstants.DEFAULT_OCSP_RESPONDER_URL;
passwordCodecClass = TransportConstants.DEFAULT_PASSWORD_CODEC_CLASS;
trustStoreProvider = TransportConstants.DEFAULT_TRUSTSTORE_PROVIDER;
trustStoreType = TransportConstants.DEFAULT_TRUSTSTORE_TYPE;
@@ -522,6 +532,14 @@ public class NettyConnector extends AbstractConnector {
return ", activemqServerName=" + serverName + ", httpUpgradeEndpoint=" +
acceptor;
}
+ public String getCrcOptions() {
+ return crcOptions;
+ }
+
+ public String getOcspResponderURL() {
+ return ocspResponderURL;
+ }
+
@Override
public synchronized void start() {
if (channelClazz != null) {
@@ -688,6 +706,8 @@ public class NettyConnector extends AbstractConnector {
.trustManagerFactoryPlugin(trustManagerFactoryPlugin)
.crlPath(crlPath)
.trustAll(trustAll)
+ .crcOptions(crcOptions)
+ .ocspResponderURL(ocspResponderURL)
.build();
final SSLEngine engine;
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 641d1448e9..2456590795 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
@@ -202,6 +202,10 @@ public class TransportConstants {
public static final String SOCKS_REMOTE_DNS_PROP_NAME = "socksRemoteDNS";
+ public static final String CRC_OPTIONS_PROP_NAME = "crcOptions";
+
+ public static final String OCSP_RESPONDER_URL_PROP_NAME =
"ocspResponderURL";
+
public static final String AUTO_START = "autoStart";
public static final boolean DEFAULT_AUTO_START = true;
@@ -403,6 +407,10 @@ public class TransportConstants {
public static final String DEFAULT_ROUTER = null;
+ public static final String DEFAULT_CRC_OPTIONS = null;
+
+ public static final String DEFAULT_OCSP_RESPONDER_URL = null;
+
private static int parseDefaultVariable(String variableName, int
defaultValue) {
try {
String variable =
System.getProperty(TransportConstants.class.getName() + "." + variableName);
@@ -484,6 +492,8 @@ public class TransportConstants {
allowableAcceptorKeys.add(TransportConstants.AUTO_START);
allowableAcceptorKeys.add(TransportConstants.ROUTER);
allowableAcceptorKeys.add(TransportConstants.PROXY_PROTOCOL_ENABLED_PROP_NAME);
+ allowableAcceptorKeys.add(TransportConstants.CRC_OPTIONS_PROP_NAME);
+
allowableAcceptorKeys.add(TransportConstants.OCSP_RESPONDER_URL_PROP_NAME);
ALLOWABLE_ACCEPTOR_KEYS =
Collections.unmodifiableSet(allowableAcceptorKeys);
@@ -545,6 +555,8 @@ public class TransportConstants {
allowableConnectorKeys.add(TransportConstants.TRUST_MANAGER_FACTORY_PLUGIN_PROP_NAME);
allowableConnectorKeys.add(TransportConstants.HANDSHAKE_TIMEOUT);
allowableConnectorKeys.add(TransportConstants.CRL_PATH_PROP_NAME);
+ allowableConnectorKeys.add(TransportConstants.CRC_OPTIONS_PROP_NAME);
+
allowableConnectorKeys.add(TransportConstants.OCSP_RESPONDER_URL_PROP_NAME);
ALLOWABLE_CONNECTOR_KEYS =
Collections.unmodifiableSet(allowableConnectorKeys);
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 3de362cda5..975ee89b60 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
@@ -19,6 +19,7 @@ package org.apache.activemq.artemis.core.remoting.impl.ssl;
import javax.net.ssl.CertPathTrustManagerParameters;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.ManagerFactoryParameters;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
@@ -37,14 +38,21 @@ import java.security.SecureRandom;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.CRL;
+import java.security.cert.CertPathBuilder;
+import java.security.cert.CertPathValidatorException;
import java.security.cert.CertStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.PKIXBuilderParameters;
+import java.security.cert.PKIXCertPathChecker;
+import java.security.cert.PKIXRevocationChecker;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
@@ -81,6 +89,8 @@ public class SSLSupport {
private boolean trustAll = TransportConstants.DEFAULT_TRUST_ALL;
private String trustManagerFactoryPlugin =
TransportConstants.DEFAULT_TRUST_MANAGER_FACTORY_PLUGIN;
private String keystoreAlias = TransportConstants.DEFAULT_KEYSTORE_ALIAS;
+ private String crcOptions = TransportConstants.DEFAULT_CRC_OPTIONS;
+ private String ocspResponderURL =
TransportConstants.DEFAULT_OCSP_RESPONDER_URL;
public SSLSupport() {
}
@@ -98,6 +108,8 @@ public class SSLSupport {
trustAll = config.isTrustAll();
trustManagerFactoryPlugin = config.getTrustManagerFactoryPlugin();
keystoreAlias = config.getKeystoreAlias();
+ crcOptions = config.getCrcOptions();
+ ocspResponderURL = config.getOcspResponderURL();
}
public String getKeystoreProvider() {
@@ -217,6 +229,24 @@ public class SSLSupport {
return this;
}
+ public String getCrcOptions() {
+ return crcOptions;
+ }
+
+ public SSLSupport setCrcOptions(String crcOptions) {
+ this.crcOptions = crcOptions;
+ return this;
+ }
+
+ public String getOcspResponderURL() {
+ return ocspResponderURL;
+ }
+
+ public SSLSupport setOcspResponderURL(String ocspResponderURL) {
+ this.ocspResponderURL = ocspResponderURL;
+ return this;
+ }
+
public SSLContext createContext() throws Exception {
SSLContext context = SSLContext.getInstance("TLS");
KeyManager[] keyManagers = loadKeyManagers();
@@ -287,11 +317,12 @@ public class SSLSupport {
} else {
TrustManagerFactory trustMgrFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore trustStore = SSLSupport.loadKeystore(truststoreProvider,
truststoreType, truststorePath, truststorePassword);
- boolean ocsp = Boolean.valueOf(Security.getProperty("ocsp.enable"));
- boolean initialized = false;
- if ((ocsp || crlPath != null) &&
TrustManagerFactory.getDefaultAlgorithm().equalsIgnoreCase("PKIX")) {
+ ManagerFactoryParameters managerFactoryParameters = null;
+ boolean ocsp =
Boolean.parseBoolean(Security.getProperty("ocsp.enable"));
+ if ((ocsp || crlPath != null || crcOptions != null ||
ocspResponderURL != null) && checkPKIXTrustManagerFactory(trustMgrFactory)) {
PKIXBuilderParameters pkixParams = new
PKIXBuilderParameters(trustStore, new X509CertSelector());
+
if (crlPath != null) {
pkixParams.setRevocationEnabled(true);
Collection<? extends CRL> crlList = loadCRL();
@@ -299,17 +330,109 @@ public class SSLSupport {
pkixParams.addCertStore(CertStore.getInstance("Collection",
new CollectionCertStoreParameters(crlList)));
}
}
- trustMgrFactory.init(new
CertPathTrustManagerParameters(pkixParams));
- initialized = true;
+
+ if (crcOptions != null || ocspResponderURL != null) {
+ addCertPathCheckers(pkixParams);
+ }
+
+ managerFactoryParameters = new
CertPathTrustManagerParameters(pkixParams);
}
- if (!initialized) {
+ if (managerFactoryParameters != null) {
+ trustMgrFactory.init(managerFactoryParameters);
+ } else {
trustMgrFactory.init(trustStore);
}
+
return trustMgrFactory;
}
}
+ private boolean checkPKIXTrustManagerFactory(TrustManagerFactory
trustMgrFactory) {
+ if (trustMgrFactory.getAlgorithm().equalsIgnoreCase("PKIX")) {
+ return true;
+ }
+
+ if (crlPath != null) {
+ throw new IllegalStateException("The crlPath parameter is not
supported with the algorithm "
+ + trustMgrFactory.getAlgorithm());
+ }
+
+ if (crcOptions != null) {
+ throw new IllegalStateException("The crcOptions parameter is not
supported with the algorithm "
+ + trustMgrFactory.getAlgorithm());
+ }
+
+ if (ocspResponderURL != null) {
+ throw new IllegalStateException("The ocspResponderURL parameter is
not supported with the algorithm "
+ + trustMgrFactory.getAlgorithm());
+ }
+
+ return false;
+ }
+
+ protected void addCertPathCheckers(PKIXBuilderParameters pkixParams) throws
Exception {
+ CertPathBuilder certPathBuilder = CertPathBuilder.getInstance("PKIX");
+ PKIXRevocationChecker revocationChecker = (PKIXRevocationChecker)
certPathBuilder.getRevocationChecker();
+ if (crcOptions != null) {
+ revocationChecker.setOptions(loadRevocationOptions());
+ }
+ if (ocspResponderURL != null) {
+ revocationChecker.setOcspResponder(new
java.net.URI(ocspResponderURL));
+ }
+ pkixParams.addCertPathChecker(revocationChecker);
+
+
+ // Add a certPathChecker to log soft fail exceptions caught by the
revocation checker.
+ pkixParams.addCertPathChecker(new PKIXCertPathChecker() {
+ @Override
+ public void init(boolean forward) throws CertPathValidatorException {
+ }
+
+ @Override
+ public boolean isForwardCheckingSupported() {
+ return revocationChecker.isForwardCheckingSupported();
+ }
+
+ @Override
+ public Set<String> getSupportedExtensions() {
+ return revocationChecker.getSupportedExtensions();
+ }
+
+ @Override
+ public void check(Certificate cert, Collection<String>
unresolvedCritExts) throws CertPathValidatorException {
+ List<CertPathValidatorException> softFailExceptions =
revocationChecker.getSoftFailExceptions();
+
+ if (softFailExceptions != null) {
+ for (CertPathValidatorException e : softFailExceptions) {
+ // Filter soft failure exceptions related to cert.
+ // The check method may be invoked for all certificates in
the path and the list of
+ // the soft failure exceptions is cleared only before the
first certificate in the path.
+ if (e.getIndex() >= 0 &&
e.getCertPath().getCertificates().get(e.getIndex()).equals(cert)) {
+ String certSubject = null;
+ if (cert instanceof X509Certificate) {
+ certSubject = ((X509Certificate)
cert).getSubjectX500Principal().getName();
+ }
+
+
ActiveMQClientLogger.LOGGER.softFailException(certSubject, e);
+ }
+ }
+ }
+ }
+ });
+ }
+
+ protected Set<PKIXRevocationChecker.Option> loadRevocationOptions() {
+ String[] revocationOptionNames = crcOptions.split(",");
+
+ Set<PKIXRevocationChecker.Option> revocationOptions = new HashSet<>();
+ for (String revocationOptionName : revocationOptionNames) {
+
revocationOptions.add(PKIXRevocationChecker.Option.valueOf(revocationOptionName));
+ }
+
+ return revocationOptions;
+ }
+
private TrustManager[] loadTrustManagers() throws Exception {
TrustManagerFactory trustManagerFactory = loadTrustManagerFactory();
if (trustManagerFactory == null) {
diff --git
a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ssl/SSLContextConfig.java
b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ssl/SSLContextConfig.java
index fc5808d9e9..e4367446b3 100644
---
a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ssl/SSLContextConfig.java
+++
b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ssl/SSLContextConfig.java
@@ -41,6 +41,8 @@ public final class SSLContextConfig {
private String trustManagerFactoryPlugin =
TransportConstants.DEFAULT_TRUST_MANAGER_FACTORY_PLUGIN;
private boolean trustAll = TransportConstants.DEFAULT_TRUST_ALL;
private String keystoreAlias = TransportConstants.DEFAULT_KEYSTORE_ALIAS;
+ private String crcOptions = TransportConstants.DEFAULT_CRC_OPTIONS;
+ private String ocspResponderURL =
TransportConstants.DEFAULT_OCSP_RESPONDER_URL;
private Builder() {
}
@@ -60,6 +62,8 @@ public final class SSLContextConfig {
truststoreProvider = config.getTruststoreProvider();
trustAll = config.trustAll;
keystoreAlias = config.keystoreAlias;
+ crcOptions = config.crcOptions;
+ ocspResponderURL = config.ocspResponderURL;
return this;
}
@@ -67,7 +71,7 @@ public final class SSLContextConfig {
return new SSLContextConfig(
keystoreProvider, keystorePath, keystoreType, keystorePassword,
truststoreProvider, truststorePath, truststoreType,
truststorePassword,
- crlPath, trustManagerFactoryPlugin, trustAll, keystoreAlias
+ crlPath, trustManagerFactoryPlugin, trustAll, keystoreAlias,
crcOptions, ocspResponderURL
);
}
@@ -130,6 +134,16 @@ public final class SSLContextConfig {
this.trustManagerFactoryPlugin = trustManagerFactoryPlugin;
return this;
}
+
+ public Builder crcOptions(final String crcOptions) {
+ this.crcOptions = crcOptions;
+ return this;
+ }
+
+ public Builder ocspResponderURL(final String ocspResponderURL) {
+ this.ocspResponderURL = ocspResponderURL;
+ return this;
+ }
}
public static Builder builder() {
@@ -148,6 +162,8 @@ public final class SSLContextConfig {
private final String crlPath;
private final boolean trustAll;
private final String keystoreAlias;
+ private final String crcOptions;
+ private final String ocspResponderURL;
private final int hashCode;
private SSLContextConfig(final String keystoreProvider,
@@ -161,7 +177,9 @@ public final class SSLContextConfig {
final String crlPath,
final String trustManagerFactoryPlugin,
final boolean trustAll,
- final String keystoreAlias) {
+ final String keystoreAlias,
+ final String crcOptions,
+ final String ocspResponderURL) {
this.keystorePath = keystorePath;
this.keystoreType = keystoreType;
this.keystoreProvider = keystoreProvider;
@@ -174,10 +192,12 @@ public final class SSLContextConfig {
this.crlPath = crlPath;
this.trustAll = trustAll;
this.keystoreAlias = keystoreAlias;
+ this.crcOptions = crcOptions;
+ this.ocspResponderURL = ocspResponderURL;
hashCode = Objects.hash(
keystorePath, keystoreType, keystoreProvider,
truststorePath, truststoreType, truststoreProvider,
- crlPath, trustManagerFactoryPlugin, trustAll, keystoreAlias
+ crlPath, trustManagerFactoryPlugin, trustAll, keystoreAlias,
crcOptions, ocspResponderURL
);
}
@@ -199,7 +219,9 @@ public final class SSLContextConfig {
Objects.equals(crlPath, other.crlPath) &&
Objects.equals(trustManagerFactoryPlugin,
other.trustManagerFactoryPlugin) &&
trustAll == other.trustAll &&
- Objects.equals(keystoreAlias, other.keystoreAlias);
+ Objects.equals(keystoreAlias, other.keystoreAlias) &&
+ Objects.equals(crcOptions, other.crcOptions) &&
+ Objects.equals(ocspResponderURL, other.ocspResponderURL);
}
public String getCrlPath() {
@@ -255,6 +277,14 @@ public final class SSLContextConfig {
return keystoreAlias;
}
+ public String getCrcOptions() {
+ return crcOptions;
+ }
+
+ public String getOcspResponderURL() {
+ return ocspResponderURL;
+ }
+
@Override
public String toString() {
return "SSLSupport [" +
@@ -270,6 +300,8 @@ public final class SSLContextConfig {
", trustAll=" + trustAll +
", trustManagerFactoryPlugin=" + trustManagerFactoryPlugin +
", keystoreAlias=" + keystoreAlias +
+ ", crcOptions=" + crcOptions +
+ ", ocspResponderURL=" + ocspResponderURL +
"]";
}
}
diff --git
a/artemis-core-client/src/test/java/org/apache/activemq/artemis/core/remoting/impl/ssl/SSLSupportTest.java
b/artemis-core-client/src/test/java/org/apache/activemq/artemis/core/remoting/impl/ssl/SSLSupportTest.java
new file mode 100644
index 0000000000..8443a3c554
--- /dev/null
+++
b/artemis-core-client/src/test/java/org/apache/activemq/artemis/core/remoting/impl/ssl/SSLSupportTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.core.remoting.impl.ssl;
+
+import org.junit.jupiter.api.Test;
+
+import java.security.cert.PKIXRevocationChecker;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class SSLSupportTest {
+ @Test
+ public void testLoadRevocationOptionsWithSingleOption() throws Exception {
+ SSLSupport sslSupport = new SSLSupport();
+ sslSupport.setCrcOptions("SOFT_FAIL");
+
+ Set<PKIXRevocationChecker.Option> options =
sslSupport.loadRevocationOptions();
+
+ assertEquals(1, options.size());
+ assertTrue(options.contains(PKIXRevocationChecker.Option.SOFT_FAIL));
+ }
+
+ @Test
+ public void testLoadRevocationOptionsWithMultipleOptions() throws Exception
{
+ SSLSupport sslSupport = new SSLSupport();
+ sslSupport.setCrcOptions("SOFT_FAIL,PREFER_CRLS,NO_FALLBACK");
+
+ Set<PKIXRevocationChecker.Option> options =
sslSupport.loadRevocationOptions();
+
+ assertEquals(3, options.size());
+ assertTrue(options.contains(PKIXRevocationChecker.Option.SOFT_FAIL));
+ assertTrue(options.contains(PKIXRevocationChecker.Option.PREFER_CRLS));
+ assertTrue(options.contains(PKIXRevocationChecker.Option.NO_FALLBACK));
+ }
+
+ @Test
+ public void testLoadRevocationOptionsWithInvalidOption() {
+ SSLSupport sslSupport = new SSLSupport();
+ sslSupport.setCrcOptions("INVALID_OPTION");
+
+ try {
+ sslSupport.loadRevocationOptions();
+ fail("Expected IllegalArgumentException for invalid CRC option");
+ } catch (IllegalArgumentException e) {
+ // Expected exception
+ }
+ }
+
+ @Test
+ public void testLoadRevocationOptionsWithMixedValidInvalid() {
+ SSLSupport sslSupport = new SSLSupport();
+ sslSupport.setCrcOptions("SOFT_FAIL,INVALID_OPTION");
+
+ try {
+ sslSupport.loadRevocationOptions();
+ fail("Expected IllegalArgumentException for invalid CRC option");
+ } catch (IllegalArgumentException e) {
+ // Expected exception
+ }
+ }
+
+ @Test
+ public void testLoadRevocationOptionsWithWhitespace() throws Exception {
+ SSLSupport sslSupport = new SSLSupport();
+ sslSupport.setCrcOptions(" SOFT_FAIL , PREFER_CRLS ");
+
+ try {
+ sslSupport.loadRevocationOptions();
+ fail("Expected IllegalArgumentException for CRC options with
whitespaces");
+ } catch (IllegalArgumentException e) {
+ // Expected exception
+ }
+ }
+
+ @Test
+ public void testEmptyCrcOptions() {
+ SSLSupport sslSupport = new SSLSupport();
+ sslSupport.setCrcOptions("");
+
+ try {
+ sslSupport.loadRevocationOptions();
+ fail("Expected IllegalArgumentException for empty CRC options");
+ } catch (IllegalArgumentException e) {
+ // Expected exception
+ }
+ }
+}
diff --git a/artemis-pom/pom.xml b/artemis-pom/pom.xml
index 94a373723a..c15406f52b 100644
--- a/artemis-pom/pom.xml
+++ b/artemis-pom/pom.xml
@@ -65,6 +65,41 @@
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.mock-server</groupId>
+ <artifactId>mockserver-netty</artifactId>
+ <version>${mockserver.version}</version>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>net.minidev</groupId>
+ <artifactId>json-smart</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.mock-server</groupId>
+ <artifactId>mockserver-core</artifactId>
+ <version>${mockserver.version}</version>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>net.minidev</groupId>
+ <artifactId>json-smart</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.mock-server</groupId>
+ <artifactId>mockserver-client-java</artifactId>
+ <version>${mockserver.version}</version>
+ <scope>test</scope>
+ </dependency>
+
<!-- ### For MQTT Tests && Examples -->
<dependency>
<groupId>org.eclipse.paho</groupId>
diff --git a/artemis-server/pom.xml b/artemis-server/pom.xml
index ea11c36180..86a8860679 100644
--- a/artemis-server/pom.xml
+++ b/artemis-server/pom.xml
@@ -274,35 +274,16 @@
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-netty</artifactId>
- <version>${mockserver.version}</version>
<scope>test</scope>
- <exclusions>
- <exclusion>
- <groupId>net.minidev</groupId>
- <artifactId>json-smart</artifactId>
- </exclusion>
- </exclusions>
</dependency>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-core</artifactId>
- <version>${mockserver.version}</version>
<scope>test</scope>
- <exclusions>
- <exclusion>
- <groupId>net.minidev</groupId>
- <artifactId>json-smart</artifactId>
- </exclusion>
- <exclusion>
- <groupId>javax.servlet</groupId>
- <artifactId>javax.servlet-api</artifactId>
- </exclusion>
- </exclusions>
</dependency>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-client-java</artifactId>
- <version>${mockserver.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
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 c1e209d1d7..7770a0ac22 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
@@ -182,6 +182,10 @@ public class NettyAcceptor extends AbstractAcceptor {
private final String crlPath;
+ private final String crcOptions;
+
+ private final String ocspResponderURL;
+
private SSLContextConfig sslContextConfig;
private final String enabledCipherSuites;
@@ -356,6 +360,10 @@ public class NettyAcceptor extends AbstractAcceptor {
keystoreAlias =
ConfigurationHelper.getStringProperty(TransportConstants.KEYSTORE_ALIAS_PROP_NAME,
TransportConstants.DEFAULT_KEYSTORE_ALIAS, configuration);
+ crcOptions =
ConfigurationHelper.getStringProperty(TransportConstants.CRC_OPTIONS_PROP_NAME,
TransportConstants.DEFAULT_CRC_OPTIONS, configuration);
+
+ ocspResponderURL =
ConfigurationHelper.getStringProperty(TransportConstants.OCSP_RESPONDER_URL_PROP_NAME,
TransportConstants.DEFAULT_OCSP_RESPONDER_URL, configuration);
+
sslContextConfig = SSLContextConfig.builder()
.keystoreProvider(keyStoreProvider)
.keystorePath(keyStorePath)
@@ -368,6 +376,8 @@ public class NettyAcceptor extends AbstractAcceptor {
.truststorePassword(trustStorePassword)
.trustManagerFactoryPlugin(trustManagerFactoryPlugin)
.crlPath(crlPath)
+ .crcOptions(crcOptions)
+ .ocspResponderURL(ocspResponderURL)
.build();
providerAgnosticSslContext = loadSSLContext();
} else {
@@ -381,6 +391,8 @@ public class NettyAcceptor extends AbstractAcceptor {
trustStorePath = TransportConstants.DEFAULT_TRUSTSTORE_PATH;
trustStorePassword = TransportConstants.DEFAULT_TRUSTSTORE_PASSWORD;
crlPath = TransportConstants.DEFAULT_CRL_PATH;
+ crcOptions = TransportConstants.DEFAULT_CRC_OPTIONS;
+ ocspResponderURL = TransportConstants.DEFAULT_OCSP_RESPONDER_URL;
enabledCipherSuites =
TransportConstants.DEFAULT_ENABLED_CIPHER_SUITES;
enabledProtocols = TransportConstants.DEFAULT_ENABLED_PROTOCOLS;
needClientAuth = TransportConstants.DEFAULT_NEED_CLIENT_AUTH;
@@ -433,6 +445,10 @@ public class NettyAcceptor extends AbstractAcceptor {
return tcpReceiveBufferSize;
}
+ public SSLContextConfig getSSLContextConfig() {
+ return sslContextConfig;
+ }
+
@Override
public synchronized void start() throws Exception {
if (channelClazz != null) {
diff --git
a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginSSLSocketFactory.java
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginSSLSocketFactory.java
index ed3014553b..dc4b3404d5 100644
---
a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginSSLSocketFactory.java
+++
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginSSLSocketFactory.java
@@ -46,6 +46,8 @@ public class LDAPLoginSSLSocketFactory extends SocketFactory {
private static final String CRL_PATH = "crlPath";
private static final String TRUST_ALL = "trustAll";
private static final String TRUST_MANAGER_FACTORY_PLUGIN =
"trustManagerFactoryPlugin";
+ private static final String CRC_OPTIONS = "crcOptions";
+ private static final String OCSP_RESPONDER_URL = "ocspResponderURL";
private static final SSLContextFactory sslContextFactory = new
CachingSSLContextFactory();
@@ -129,6 +131,12 @@ public class LDAPLoginSSLSocketFactory extends
SocketFactory {
if (environment.containsKey(TRUST_MANAGER_FACTORY_PLUGIN)) {
sslContextConfigBuilder.trustManagerFactoryPlugin(environment.get(TRUST_MANAGER_FACTORY_PLUGIN));
}
+ if (environment.containsKey(CRC_OPTIONS)) {
+ sslContextConfigBuilder.crcOptions(environment.get(CRC_OPTIONS));
+ }
+ if (environment.containsKey(OCSP_RESPONDER_URL)) {
+
sslContextConfigBuilder.ocspResponderURL(environment.get(OCSP_RESPONDER_URL));
+ }
return sslContextConfigBuilder.build();
}
diff --git
a/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginModuleTest.java
b/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginModuleTest.java
index 681cccb48b..d782cbcd95 100644
---
a/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginModuleTest.java
+++
b/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginModuleTest.java
@@ -42,6 +42,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
+import org.apache.activemq.artemis.logs.AssertionLoggerHandler;
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
import org.apache.activemq.artemis.utils.SensitiveDataCodec;
import org.apache.directory.server.core.annotations.ApplyLdifFiles;
@@ -55,6 +56,7 @@ import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockserver.socket.PortFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -493,6 +495,39 @@ public class LDAPLoginModuleTest extends
AbstractLdapTestUnit {
testLDAPSConnectionWithLDAPLoginSSLSocketFactory(extraOptions, true);
}
+ @Test
+ public void
testLDAPLoginSSLSocketFactoryWithNoFallbackRevocationCheckerAndTruststore()
throws Exception {
+ Map<String, Object> extraOptions = new HashMap<>();
+ extraOptions.put("crcOptions", "NO_FALLBACK");
+ extraOptions.put("truststorePath",
Objects.requireNonNull(this.getClass().
+ getClassLoader().getResource("server-ca-truststore.jks")).getFile());
+ extraOptions.put("truststorePassword", "securepass");
+
+ try {
+ testLDAPSConnectionWithLDAPLoginSSLSocketFactory(extraOptions, true);
+ fail("Should have thrown CommunicationException");
+ } catch (Exception e) {
+ assertEquals(CommunicationException.class, e.getClass());
+ }
+ }
+
+ @Test
+ public void
testLDAPLoginSSLSocketFactoryWithSoftFailRevocationCheckerAndTruststore()
throws Exception {
+ Map<String, Object> extraOptions = new HashMap<>();
+ extraOptions.put("crcOptions", "SOFT_FAIL");
+ extraOptions.put("ocspResponderURL", "http://localhost:" +
PortFactory.findFreePort());
+ extraOptions.put("truststorePath",
Objects.requireNonNull(this.getClass().
+ getClassLoader().getResource("server-ca-truststore.jks")).getFile());
+ extraOptions.put("truststorePassword", "securepass");
+
+ try (AssertionLoggerHandler loggerHandler = new
AssertionLoggerHandler(true)) {
+ testLDAPSConnectionWithLDAPLoginSSLSocketFactory(extraOptions, true);
+
+ assertTrue(loggerHandler.findText("AMQ212081",
+ "[CN=ActiveMQ Artemis
Server,OU=Artemis,O=ActiveMQ,L=AMQ,ST=AMQ,C=AMQ]", "Exception"));
+ }
+ }
+
@Test
public void testLDAPLoginSSLSocketFactoryWithInvalidTruststore() throws
Exception {
Map<String, Object> extraOptions = new HashMap<>();
diff --git
a/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginSSLSocketFactoryTest.java
b/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginSSLSocketFactoryTest.java
index 0772a70092..d8c0dd599b 100644
---
a/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginSSLSocketFactoryTest.java
+++
b/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginSSLSocketFactoryTest.java
@@ -103,6 +103,8 @@ public class LDAPLoginSSLSocketFactoryTest {
environment.put("crlPath", "/path/to/crl");
environment.put("trustAll", "false");
environment.put("trustManagerFactoryPlugin",
"trust.manager.factory.Plugin");
+ environment.put("crcOptions", "ONLY_END_ENTITY,SOFT_FAIL");
+ environment.put("ocspResponderURL", "http://localhost:8080");
LDAPLoginSSLSocketFactory factory = new
LDAPLoginSSLSocketFactory(environment);
SSLContextConfig config = factory.getSSLContextConfig();
@@ -120,6 +122,8 @@ public class LDAPLoginSSLSocketFactoryTest {
assertEquals("/path/to/crl", config.getCrlPath());
assertFalse(config.isTrustAll());
assertEquals("trust.manager.factory.Plugin",
config.getTrustManagerFactoryPlugin());
+ assertEquals("ONLY_END_ENTITY,SOFT_FAIL", config.getCrcOptions());
+ assertEquals("http://localhost:8080", config.getOcspResponderURL());
}
@Test
diff --git a/docs/user-manual/configuring-transports.adoc
b/docs/user-manual/configuring-transports.adoc
index 056eed7a46..9a364be724 100644
--- a/docs/user-manual/configuring-transports.adoc
+++ b/docs/user-manual/configuring-transports.adoc
@@ -440,6 +440,25 @@ If the incoming connection doesn't include the
`server_name` extension then the
+
When used on a `connector` the `sniHost` value is used for the `server_name`
extension on the SSL connection.
+crlPath::
+This is valid on either an `acceptor` or `connector`.
+It specifies the path to a Certificate Revocation List (CRL) file for
additional certificate validation when using PKIX trust manager.
+The CRL file contains a list of certificates that have been revoked and should
no longer be trusted.
+Default is `null`.
+
+crcOptions::
+This is valid on either an `acceptor` or `connector`.
+It specifies a comma-separated list of PKIXRevocationChecker options to
configure certificate revocation checking behavior when using PKIX trust
manager.
+Available options include: `ONLY_END_ENTITY`, `PREFER_CRLS`, `NO_FALLBACK`,
`SOFT_FAIL`.
+For further details about these options, see
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/security/cert/PKIXRevocationChecker.Option.html[PKIXRevocationChecker.Option
JavaDoc].
+Default is `null`.
+
+ocspResponderURL::
+This is valid on either an `acceptor` or `connector`.
+It specifies the URL of the OCSP (Online Certificate Status Protocol)
responder to use for certificate revocation checking when using PKIX trust
manager.
+This overrides the default OCSP responder specified in the certificate's
Authority Information Access (AIA) extension.
+Default is `null`.
+
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`.
@@ -447,7 +466,7 @@ This is a simple interface with a single method which
returns a `javax.net.ssl.T
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`).
+This value takes precedence of all other SSL parameters which apply to the
trust manager (i.e. `trustAll`, `truststoreProvider`, `truststorePath`,
`truststorePassword`, `crlPath`, `crcOptions`, `ocspResponderURL`).
+
Any plugin specified will need to be placed on the
xref:using-server.adoc#adding-runtime-dependencies[broker's classpath].
diff --git a/docs/user-manual/security.adoc b/docs/user-manual/security.adoc
index 4b345aa340..b08eb271d7 100644
--- a/docs/user-manual/security.adoc
+++ b/docs/user-manual/security.adoc
@@ -889,7 +889,19 @@ Supports
xref:masking-passwords.adoc#masking-passwords[password masking].
Default is `null`.
crlPath::
-The path to the Certificate Revocation List (CRL) file for additional
certificate validation.
+The path to a Certificate Revocation List (CRL) file for additional
certificate validation when using PKIX trust manager.
+Default is `null`.
+
+crcOptions::
+Comma-separated list of PKIXRevocationChecker options to configure certificate
revocation checking behavior when using PKIX trust manager.
+Available options include: `ONLY_END_ENTITY`, `PREFER_CRLS`, `NO_FALLBACK`,
`SOFT_FAIL`.
+For further details about these options, see
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/security/cert/PKIXRevocationChecker.Option.html[PKIXRevocationChecker.Option
JavaDoc].
+Default is `null`.
+
+ocspResponderURL::
+This is valid on either an `acceptor` or `connector`.
+The URL of the OCSP (Online Certificate Status Protocol) responder to use for
certificate revocation checking when using PKIX trust manager.
+This overrides the default OCSP responder specified in the certificate's
Authority Information Access (AIA) extension.
Default is `null`.
trustAll::
diff --git a/tests/integration-tests/pom.xml b/tests/integration-tests/pom.xml
index ba19f368ff..9b72aaf051 100644
--- a/tests/integration-tests/pom.xml
+++ b/tests/integration-tests/pom.xml
@@ -416,6 +416,11 @@
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.mock-server</groupId>
+ <artifactId>mockserver-core</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
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 9a6b7a33b2..dd382ccef9 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
@@ -61,6 +61,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockserver.socket.PortFactory;
/**
* See the tests/security-resources/build.sh script for details on the
security resources used.
@@ -955,6 +956,65 @@ public class CoreClientOverOneWaySSLTest extends
ActiveMQTestBase {
}
}
+ @TestTemplate
+ public void testOneWaySSLWithNoFallbackRevocationChecker() throws Exception
{
+ createCustomSslServer();
+ String text = RandomUtil.randomUUIDString();
+
+ tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
+ tc.getParams().put(TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME,
storeProvider);
+ tc.getParams().put(TransportConstants.TRUSTSTORE_TYPE_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.CRC_OPTIONS_PROP_NAME,
"NO_FALLBACK");
+
+ ServerLocator locator =
addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc)).setCallTimeout(2000);
+ try {
+ createSessionFactory(locator);
+ fail("expecting exception");
+ } catch (Exception e) {
+ assertTrue(ActiveMQNotConnectedException.class.equals(e.getClass()) ||
+ ActiveMQConnectionTimedOutException.class.equals(e.getClass()));
+ }
+ }
+
+ @TestTemplate
+ public void testOneWaySSLWithSoftFailRevocationChecker() throws Exception {
+ createCustomSslServer();
+ String text = RandomUtil.randomUUIDString();
+
+ tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
+ tc.getParams().put(TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME,
storeProvider);
+ tc.getParams().put(TransportConstants.TRUSTSTORE_TYPE_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.CRC_OPTIONS_PROP_NAME,
"SOFT_FAIL");
+ tc.getParams().put(TransportConstants.OCSP_RESPONDER_URL_PROP_NAME,
"http://localhost:" + PortFactory.findFreePort());
+
+ ServerLocator locator =
addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc));
+ ClientSessionFactory sf;
+ try (AssertionLoggerHandler loggerHandler = new
AssertionLoggerHandler(true)) {
+ sf = addSessionFactory(createSessionFactory(locator));
+
+ assertTrue(loggerHandler.findText("AMQ212081",
+ "[CN=ActiveMQ Artemis
Server,OU=Artemis,O=ActiveMQ,L=AMQ,ST=AMQ,C=AMQ]", "Exception"));
+ }
+
+ ClientSession session = addClientSession(sf.createSession(false, true,
true));
+
session.createQueue(QueueConfiguration.of(CoreClientOverOneWaySSLTest.QUEUE).setDurable(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);
+ assertNotNull(m);
+ assertEquals(text, m.getBodyBuffer().readString());
+ }
+
private void createCustomSslServer() throws Exception {
createCustomSslServer(null, null);
}
diff --git
a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverTwoWaySSLTest.java
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverTwoWaySSLTest.java
index 84fcdd236c..3201375a21 100644
---
a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverTwoWaySSLTest.java
+++
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverTwoWaySSLTest.java
@@ -18,6 +18,7 @@ package org.apache.activemq.artemis.tests.integration.ssl;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.util.Arrays;
@@ -48,6 +49,7 @@ import
org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptor;
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnection;
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
+import org.apache.activemq.artemis.logs.AssertionLoggerHandler;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import
org.apache.activemq.artemis.tests.extensions.parameterized.ParameterizedTestExtension;
import org.apache.activemq.artemis.tests.extensions.parameterized.Parameters;
@@ -57,6 +59,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith;
import io.netty.handler.ssl.SslHandler;
+import org.mockserver.socket.PortFactory;
/**
* See the tests/security-resources/build.sh script for details on the
security resources used.
@@ -430,6 +433,67 @@ public class CoreClientOverTwoWaySSLTest extends
ActiveMQTestBase {
}
}
+ @TestTemplate
+ public void testTwoWaySSLWithNoFallbackRevocationChecker() throws Exception
{
+ NettyAcceptor acceptor = (NettyAcceptor)
server.getRemotingService().getAcceptor("nettySSL");
+
acceptor.getConfiguration().put(TransportConstants.NEED_CLIENT_AUTH_PROP_NAME,
true);
+
acceptor.getConfiguration().put(TransportConstants.CRC_OPTIONS_PROP_NAME,
"NO_FALLBACK");
+ server.getRemotingService().stop(false);
+ server.getRemotingService().start();
+ server.getRemotingService().startAcceptors();
+
+ //Trust all defaults to false so this should fail with no trust store set
+ tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
+ tc.getParams().put(TransportConstants.SSL_PROVIDER, clientSSLProvider);
+
+ tc.getParams().put(TransportConstants.KEYSTORE_PROVIDER_PROP_NAME,
storeProvider);
+ tc.getParams().put(TransportConstants.KEYSTORE_TYPE_PROP_NAME,
storeType);
+ 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 no fallback
revocation checker");
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+
+ @TestTemplate
+ public void testTwoWaySSLWithSoftFailRevocationChecker() throws Exception {
+ NettyAcceptor acceptor = (NettyAcceptor)
server.getRemotingService().getAcceptor("nettySSL");
+
acceptor.getConfiguration().put(TransportConstants.NEED_CLIENT_AUTH_PROP_NAME,
true);
+
acceptor.getConfiguration().put(TransportConstants.CRC_OPTIONS_PROP_NAME,
"SOFT_FAIL");
+
acceptor.getConfiguration().put(TransportConstants.OCSP_RESPONDER_URL_PROP_NAME,
"http://localhost:" + PortFactory.findFreePort());
+ server.getRemotingService().stop(false);
+ server.getRemotingService().start();
+ server.getRemotingService().startAcceptors();
+
+ //Set trust all so this should work even with no trust store set
+ tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
+ tc.getParams().put(TransportConstants.SSL_PROVIDER, clientSSLProvider);
+ tc.getParams().put(TransportConstants.TRUST_ALL_PROP_NAME, true);
+
+ tc.getParams().put(TransportConstants.KEYSTORE_PROVIDER_PROP_NAME,
storeProvider);
+ tc.getParams().put(TransportConstants.KEYSTORE_TYPE_PROP_NAME,
storeType);
+ 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 (AssertionLoggerHandler loggerHandler = new
AssertionLoggerHandler(true)) {
+ ClientSessionFactory sf = createSessionFactory(locator);
+ sf.close();
+
+ assertTrue(loggerHandler.findText("AMQ212081",
+ "[CN=ActiveMQ Artemis
Client,OU=Artemis,O=ActiveMQ,L=AMQ,ST=AMQ,C=AMQ]", "Exception"));
+ }
+ }
+
@Override
@BeforeEach
public void setUp() throws Exception {
diff --git
a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/netty/NettyAcceptorTest.java
b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/netty/NettyAcceptorTest.java
index 3f63901fde..c2e8faf3c6 100644
---
a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/netty/NettyAcceptorTest.java
+++
b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/netty/NettyAcceptorTest.java
@@ -41,6 +41,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@@ -171,4 +172,26 @@ public class NettyAcceptorTest extends ActiveMQTestBase {
params.put(TransportConstants.SSL_CONTEXT_PROP_NAME,
RandomUtil.randomUUIDString());
new NettyAcceptor("netty", null, params, null, null, null, null,
Map.of(), null, null);
}
+
+ @Test
+ public void testValidSSLConfigWithCrcOptions() {
+ Map<String, Object> params = new HashMap<>();
+ params.put(TransportConstants.SSL_ENABLED_PROP_NAME, "true");
+ params.put(TransportConstants.KEYSTORE_PROVIDER_PROP_NAME,
RandomUtil.randomUUIDString());
+ params.put(TransportConstants.CRC_OPTIONS_PROP_NAME, "SOFT_FAIL");
+ NettyAcceptor acceptor = new NettyAcceptor("netty", null, params, null,
null, null, null, Map.of(), null, null);
+
+ assertEquals("SOFT_FAIL",
acceptor.getSSLContextConfig().getCrcOptions());
+ }
+
+ @Test
+ public void testValidSSLConfigWithOcspResponderURL() {
+ Map<String, Object> params = new HashMap<>();
+ params.put(TransportConstants.SSL_ENABLED_PROP_NAME, "true");
+ params.put(TransportConstants.KEYSTORE_PROVIDER_PROP_NAME,
RandomUtil.randomUUIDString());
+ params.put(TransportConstants.OCSP_RESPONDER_URL_PROP_NAME,
"http://localhost:8080");
+ NettyAcceptor acceptor = new NettyAcceptor("netty", null, params, null,
null, null, null, Map.of(), null, null);
+
+ assertEquals("http://localhost:8080",
acceptor.getSSLContextConfig().getOcspResponderURL());
+ }
}
diff --git
a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/netty/NettyConnectorTest.java
b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/netty/NettyConnectorTest.java
index 37d1fa12b9..8e36b986dc 100644
---
a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/netty/NettyConnectorTest.java
+++
b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/netty/NettyConnectorTest.java
@@ -44,6 +44,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -606,4 +607,32 @@ public class NettyConnectorTest extends ActiveMQTestBase {
scheduledThreadPool.shutdownNow();
}
}
+
+ @Test
+ public void testCrcOptionsConfig() throws Exception {
+ Map<String, Object> params = new HashMap<>();
+ params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
+ params.put(TransportConstants.CRC_OPTIONS_PROP_NAME,
"SOFT_FAIL,PREFER_CRLS");
+
+ NettyConnector connector = new NettyConnector(params, (connectionID,
buffer) -> { }, listener, null, null, null);
+ try {
+ assertEquals("SOFT_FAIL,PREFER_CRLS", connector.getCrcOptions());
+ } finally {
+ connector.close();
+ }
+ }
+
+ @Test
+ public void testOcspResponderURL() throws Exception {
+ Map<String, Object> params = new HashMap<>();
+ params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
+ params.put(TransportConstants.OCSP_RESPONDER_URL_PROP_NAME,
"http://localhost:8080");
+
+ NettyConnector connector = new NettyConnector(params, (connectionID,
buffer) -> { }, listener, null, null, null);
+ try {
+ assertEquals("http://localhost:8080",
connector.getOcspResponderURL());
+ } finally {
+ connector.close();
+ }
+ }
}
diff --git
a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/ssl/SSLContextConfigTest.java
b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/ssl/SSLContextConfigTest.java
new file mode 100644
index 0000000000..63a8b31f89
--- /dev/null
+++
b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/ssl/SSLContextConfigTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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.unit.core.remoting.impl.ssl;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextConfig;
+import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for SSLContextConfig to ensure proper handling of crcOptions
+ */
+public class SSLContextConfigTest extends ActiveMQTestBase {
+
+ @Test
+ public void testCrcOptionsInBuilder() {
+ final String crcOptions = "SOFT_FAIL,PREFER_CRLS";
+
+ SSLContextConfig config = SSLContextConfig.builder()
+ .crcOptions(crcOptions)
+ .build();
+
+ assertEquals(crcOptions, config.getCrcOptions());
+ }
+
+ @Test
+ public void testDefaultCrcOptions() {
+ SSLContextConfig config = SSLContextConfig.builder()
+ .build();
+
+ assertNull(config.getCrcOptions());
+ }
+
+ @Test
+ public void testEqualsWithCrcOptions() {
+ SSLContextConfig config1 = SSLContextConfig.builder()
+ .crcOptions("SOFT_FAIL")
+ .build();
+
+ SSLContextConfig config2 = SSLContextConfig.builder()
+ .crcOptions("SOFT_FAIL")
+ .build();
+
+ SSLContextConfig config3 = SSLContextConfig.builder()
+ .crcOptions("PREFER_CRLS")
+ .build();
+
+ assertEquals(config1, config2);
+ assertNotEquals(config1, config3);
+ }
+
+ @Test
+ public void testHashCodeWithCrcOptions() {
+ SSLContextConfig config1 = SSLContextConfig.builder()
+ .crcOptions("SOFT_FAIL")
+ .build();
+
+ SSLContextConfig config2 = SSLContextConfig.builder()
+ .crcOptions("SOFT_FAIL")
+ .build();
+
+ assertEquals(config1.hashCode(), config2.hashCode());
+ }
+
+ @Test
+ public void testToStringWithCrcOptions() {
+ final String crcOptions = "SOFT_FAIL,PREFER_CRLS";
+
+ SSLContextConfig config = SSLContextConfig.builder()
+ .crcOptions(crcOptions)
+ .build();
+
+ String toString = config.toString();
+ assert toString.contains(crcOptions);
+ }
+
+ @Test
+ public void testBuilderFromExistingConfigWithCrcOptions() {
+ final String crcOptions = "SOFT_FAIL,PREFER_CRLS";
+
+ SSLContextConfig originalConfig = SSLContextConfig.builder()
+ .crcOptions(crcOptions)
+ .build();
+
+ SSLContextConfig copiedConfig = SSLContextConfig.builder()
+ .from(originalConfig)
+ .build();
+
+ assertEquals(originalConfig.getCrcOptions(),
copiedConfig.getCrcOptions());
+ assertEquals(crcOptions, copiedConfig.getCrcOptions());
+ }
+
+ @Test
+ public void testOcspResponderURLInBuilder() {
+ final String ocspURL = "http://ocsp.example.com:8080";
+
+ SSLContextConfig config = SSLContextConfig.builder()
+ .ocspResponderURL(ocspURL)
+ .build();
+
+ assertEquals(ocspURL, config.getOcspResponderURL());
+ }
+
+ @Test
+ public void testDefaultOcspResponderURL() {
+ SSLContextConfig config = SSLContextConfig.builder()
+ .build();
+
+ assertNull(config.getOcspResponderURL());
+ }
+
+ @Test
+ public void testEqualsWithOcspResponderURL() {
+ SSLContextConfig config1 = SSLContextConfig.builder()
+ .ocspResponderURL("http://ocsp1.example.com")
+ .build();
+
+ SSLContextConfig config2 = SSLContextConfig.builder()
+ .ocspResponderURL("http://ocsp1.example.com")
+ .build();
+
+ SSLContextConfig config3 = SSLContextConfig.builder()
+ .ocspResponderURL("http://ocsp2.example.com")
+ .build();
+
+ assertEquals(config1, config2);
+ assertNotEquals(config1, config3);
+ }
+
+ @Test
+ public void testHashCodeWithOcspResponderURL() {
+ SSLContextConfig config1 = SSLContextConfig.builder()
+ .ocspResponderURL("http://ocsp.example.com")
+ .build();
+
+ SSLContextConfig config2 = SSLContextConfig.builder()
+ .ocspResponderURL("http://ocsp.example.com")
+ .build();
+
+ assertEquals(config1.hashCode(), config2.hashCode());
+ }
+
+ @Test
+ public void testToStringWithOcspResponderURL() {
+ final String ocspURL = "http://ocsp.example.com:8080";
+
+ SSLContextConfig config = SSLContextConfig.builder()
+ .ocspResponderURL(ocspURL)
+ .build();
+
+ String toString = config.toString();
+ assert toString.contains(ocspURL);
+ }
+
+ @Test
+ public void testBuilderFromExistingConfigWithOcspResponderURL() {
+ final String ocspURL = "http://ocsp.example.com:8080";
+
+ SSLContextConfig originalConfig = SSLContextConfig.builder()
+ .ocspResponderURL(ocspURL)
+ .build();
+
+ SSLContextConfig copiedConfig = SSLContextConfig.builder()
+ .from(originalConfig)
+ .build();
+
+ assertEquals(originalConfig.getOcspResponderURL(),
copiedConfig.getOcspResponderURL());
+ assertEquals(ocspURL, copiedConfig.getOcspResponderURL());
+ }
+}
\ No newline at end of file
diff --git
a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/ssl/SSLSupportTest.java
b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/ssl/SSLSupportTest.java
index d44f02ff28..8e458896e6 100644
---
a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/ssl/SSLSupportTest.java
+++
b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/ssl/SSLSupportTest.java
@@ -287,4 +287,68 @@ public class SSLSupportTest extends ActiveMQTestBase {
.setTrustAll(true)
.createContext();
}
+
+ @TestTemplate
+ public void testContextWithCrcOptions() throws Exception {
+ new SSLSupport()
+ .setKeystoreProvider(storeProvider)
+ .setKeystoreType(storeType)
+ .setKeystorePath(keyStorePath)
+ .setKeystorePassword(keyStorePassword)
+ .setTruststoreProvider(storeProvider)
+ .setTruststoreType(storeType)
+ .setTruststorePath(trustStorePath)
+ .setTruststorePassword(trustStorePassword)
+ .setCrcOptions("SOFT_FAIL")
+ .createContext();
+ }
+
+ @TestTemplate
+ public void testContextWithMultipleCrcOptions() throws Exception {
+ new SSLSupport()
+ .setKeystoreProvider(storeProvider)
+ .setKeystoreType(storeType)
+ .setKeystorePath(keyStorePath)
+ .setKeystorePassword(keyStorePassword)
+ .setTruststoreProvider(storeProvider)
+ .setTruststoreType(storeType)
+ .setTruststorePath(trustStorePath)
+ .setTruststorePassword(trustStorePassword)
+ .setCrcOptions("SOFT_FAIL,PREFER_CRLS,NO_FALLBACK")
+ .createContext();
+ }
+
+ @TestTemplate
+ public void testContextWithInvalidCrcOptions() throws Exception {
+ try {
+ new SSLSupport()
+ .setKeystoreProvider(storeProvider)
+ .setKeystoreType(storeType)
+ .setKeystorePath(keyStorePath)
+ .setKeystorePassword(keyStorePassword)
+ .setTruststoreProvider(storeProvider)
+ .setTruststoreType(storeType)
+ .setTruststorePath(trustStorePath)
+ .setTruststorePassword(trustStorePassword)
+ .setCrcOptions("INVALID_OPTION")
+ .createContext();
+ } catch (IllegalArgumentException e) {
+ // Expected exception
+ }
+ }
+
+ @TestTemplate
+ public void testContextWithOcspResponderURL() throws Exception {
+ new SSLSupport()
+ .setKeystoreProvider(storeProvider)
+ .setKeystoreType(storeType)
+ .setKeystorePath(keyStorePath)
+ .setKeystorePassword(keyStorePassword)
+ .setTruststoreProvider(storeProvider)
+ .setTruststoreType(storeType)
+ .setTruststorePath(trustStorePath)
+ .setTruststorePassword(trustStorePassword)
+ .setOcspResponderURL("http://localhost:8080")
+ .createContext();
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]