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 e7c1c7108df..ce5007d0a08 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 898dbb9ae71..30071582d85 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 NettyConnector(final Map configuration, useDefaultSslContext = ConfigurationHelper.getBooleanProperty(TransportConstants.USE_DEFAULT_SSL_CONTEXT_PROP_NAME, TransportConstants.DEFAULT_USE_DEFAULT_SSL_CONTEXT, configuration); trustManagerFactoryPlugin = ConfigurationHelper.getStringProperty(TransportConstants.TRUST_MANAGER_FACTORY_PLUGIN_PROP_NAME, TransportConstants.DEFAULT_TRUST_MANAGER_FACTORY_PLUGIN, configuration); + + 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 @@ private String getHttpUpgradeInfo() { 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 void initChannel(Channel channel) throws Exception { .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 641d1448e91..2456590795e 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 @@ private static int parseDefaultVariable(String variableName, int defaultValue) { 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 @@ private static int parseDefaultVariable(String variableName, int defaultValue) { 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 3de362cda50..975ee89b605 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 @@ 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.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 SSLSupport(final SSLContextConfig config) { trustAll = config.isTrustAll(); trustManagerFactoryPlugin = config.getTrustManagerFactoryPlugin(); keystoreAlias = config.getKeystoreAlias(); + crcOptions = config.getCrcOptions(); + ocspResponderURL = config.getOcspResponderURL(); } public String getKeystoreProvider() { @@ -217,6 +229,24 @@ public SSLSupport setTrustManagerFactoryPlugin(String trustManagerFactoryPlugin) 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 @@ private TrustManagerFactory loadTrustManagerFactory() throws Exception { } 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 crlList = loadCRL(); @@ -299,17 +330,109 @@ private TrustManagerFactory loadTrustManagerFactory() throws Exception { 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 getSupportedExtensions() { + return revocationChecker.getSupportedExtensions(); + } + + @Override + public void check(Certificate cert, Collection unresolvedCritExts) throws CertPathValidatorException { + List 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 loadRevocationOptions() { + String[] revocationOptionNames = crcOptions.split(","); + + Set 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 fc5808d9e9d..e4367446b3c 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 static final class Builder { 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 Builder from(final SSLContextConfig config) { truststoreProvider = config.getTruststoreProvider(); trustAll = config.trustAll; keystoreAlias = config.keystoreAlias; + crcOptions = config.crcOptions; + ocspResponderURL = config.ocspResponderURL; return this; } @@ -67,7 +71,7 @@ public SSLContextConfig build() { 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 Builder trustManagerFactoryPlugin(final String trustManagerFactoryPlugin) 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 static Builder builder() { 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 @@ private SSLContextConfig(final String keystoreProvider, 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 @@ private SSLContextConfig(final String keystoreProvider, 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 boolean equals(final Object obj) { 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 String getKeystoreAlias() { return keystoreAlias; } + public String getCrcOptions() { + return crcOptions; + } + + public String getOcspResponderURL() { + return ocspResponderURL; + } + @Override public String toString() { return "SSLSupport [" + @@ -270,6 +300,8 @@ public String toString() { ", 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 00000000000..8443a3c5542 --- /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 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 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 94a373723a3..c15406f52bc 100644 --- a/artemis-pom/pom.xml +++ b/artemis-pom/pom.xml @@ -65,6 +65,41 @@ test + + org.mock-server + mockserver-netty + ${mockserver.version} + test + + + net.minidev + json-smart + + + + + org.mock-server + mockserver-core + ${mockserver.version} + test + + + net.minidev + json-smart + + + javax.servlet + javax.servlet-api + + + + + org.mock-server + mockserver-client-java + ${mockserver.version} + test + + org.eclipse.paho diff --git a/artemis-server/pom.xml b/artemis-server/pom.xml index ea11c361805..86a88606799 100644 --- a/artemis-server/pom.xml +++ b/artemis-server/pom.xml @@ -274,35 +274,16 @@ org.mock-server mockserver-netty - ${mockserver.version} test - - - net.minidev - json-smart - - org.mock-server mockserver-core - ${mockserver.version} test - - - net.minidev - json-smart - - - javax.servlet - javax.servlet-api - - org.mock-server mockserver-client-java - ${mockserver.version} test 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 c1e209d1d7f..7770a0ac22b 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 NettyAcceptor(final String name, 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 NettyAcceptor(final String name, .truststorePassword(trustStorePassword) .trustManagerFactoryPlugin(trustManagerFactoryPlugin) .crlPath(crlPath) + .crcOptions(crcOptions) + .ocspResponderURL(ocspResponderURL) .build(); providerAgnosticSslContext = loadSSLContext(); } else { @@ -381,6 +391,8 @@ public NettyAcceptor(final String name, 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 int getTcpReceiveBufferSize() { 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 ed3014553b9..dc4b3404d5c 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 @@ protected SSLContextConfig getSSLContextConfig() { 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 681cccb48b9..d782cbcd95e 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.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.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 void testLDAPLoginSSLSocketFactoryWithCustomPasswordCodecAndTruststore() testLDAPSConnectionWithLDAPLoginSSLSocketFactory(extraOptions, true); } + @Test + public void testLDAPLoginSSLSocketFactoryWithNoFallbackRevocationCheckerAndTruststore() throws Exception { + Map 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 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 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 0772a700924..d8c0dd599bf 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 void testGetSSLContextConfigWithAllOptions() { 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 void testGetSSLContextConfigWithAllOptions() { 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 056eed7a46c..9a364be7244 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 4b345aa3402..b08eb271d7f 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 ba19f368ff3..9b72aaf0516 100644 --- a/tests/integration-tests/pom.xml +++ b/tests/integration-tests/pom.xml @@ -416,6 +416,11 @@ mockito-core test + + org.mock-server + mockserver-core + test + 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 9a6b7a33b23..dd382ccef96 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.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 void testPlainConnectionToSSLEndpoint() throws Exception { } } + @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 84fcdd236c9..3201375a214 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 @@ 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.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.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 void testTwoWaySSLWithoutClientKeyStore() throws Exception { } } + @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 3f63901fde4..c2e8faf3c60 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.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 void testValidSSLConfig2() { 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 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 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 37d1fa12b9c..8e36b986dc9 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.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 void testChannelHandlerRemovedWhileCreatingConnection() throws Exception scheduledThreadPool.shutdownNow(); } } + + @Test + public void testCrcOptionsConfig() throws Exception { + Map 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 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 00000000000..63a8b31f898 --- /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 d44f02ff289..8e458896e64 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 void testContextWithTrustAll() throws Exception { .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(); + } }