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 8faa22dbc6..5d3b82d7b5 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 @@ -206,6 +206,8 @@ public class NettyConnector extends AbstractConnector { private String trustStorePassword; + private String crlPath; + private String enabledCipherSuites; private String enabledProtocols; @@ -338,6 +340,8 @@ public class NettyConnector extends AbstractConnector { trustStorePassword = ConfigurationHelper.getPasswordProperty(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, TransportConstants.DEFAULT_TRUSTSTORE_PASSWORD, configuration, ActiveMQDefaultConfiguration.getPropMaskPassword(), ActiveMQDefaultConfiguration.getPropPasswordCodec()); + crlPath = ConfigurationHelper.getStringProperty(TransportConstants.CRL_PATH_PROP_NAME, TransportConstants.DEFAULT_CRL_PATH, configuration); + enabledCipherSuites = ConfigurationHelper.getStringProperty(TransportConstants.ENABLED_CIPHER_SUITES_PROP_NAME, TransportConstants.DEFAULT_ENABLED_CIPHER_SUITES, configuration); enabledProtocols = ConfigurationHelper.getStringProperty(TransportConstants.ENABLED_PROTOCOLS_PROP_NAME, TransportConstants.DEFAULT_ENABLED_PROTOCOLS, configuration); @@ -358,6 +362,7 @@ public class NettyConnector extends AbstractConnector { trustStoreProvider = TransportConstants.DEFAULT_TRUSTSTORE_PROVIDER; trustStorePath = TransportConstants.DEFAULT_TRUSTSTORE_PATH; trustStorePassword = TransportConstants.DEFAULT_TRUSTSTORE_PASSWORD; + crlPath = TransportConstants.DEFAULT_CRL_PATH; enabledCipherSuites = TransportConstants.DEFAULT_ENABLED_CIPHER_SUITES; enabledProtocols = TransportConstants.DEFAULT_ENABLED_PROTOCOLS; verifyHost = TransportConstants.DEFAULT_VERIFY_HOST; @@ -519,7 +524,7 @@ public class NettyConnector extends AbstractConnector { if (System.getProperty(ACTIVEMQ_TRUSTSTORE_PASSWORD_PROP_NAME) != null) { realTrustStorePassword = System.getProperty(ACTIVEMQ_TRUSTSTORE_PASSWORD_PROP_NAME); } - context = SSLSupport.createContext(realKeyStoreProvider, realKeyStorePath, realKeyStorePassword, realTrustStoreProvider, realTrustStorePath, realTrustStorePassword, trustAll); + context = SSLSupport.createContext(realKeyStoreProvider, realKeyStorePath, realKeyStorePassword, realTrustStoreProvider, realTrustStorePath, realTrustStorePassword, trustAll, crlPath); } } catch (Exception e) { close(); 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 890b508058..efc5eb0748 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 @@ -95,6 +95,8 @@ public class TransportConstants { public static final String TRUSTSTORE_PASSWORD_PROP_NAME = "trustStorePassword"; + public static final String CRL_PATH_PROP_NAME = "crlPath"; + public static final String ENABLED_CIPHER_SUITES_PROP_NAME = "enabledCipherSuites"; public static final String ENABLED_PROTOCOLS_PROP_NAME = "enabledProtocols"; @@ -189,6 +191,8 @@ public class TransportConstants { public static final String DEFAULT_TRUSTSTORE_PASSWORD = null; + public static final String DEFAULT_CRL_PATH = null; + public static final String DEFAULT_ENABLED_CIPHER_SUITES = null; public static final String DEFAULT_ENABLED_PROTOCOLS = null; @@ -310,6 +314,7 @@ public class TransportConstants { allowableAcceptorKeys.add(ActiveMQDefaultConfiguration.getPropMaskPassword()); allowableAcceptorKeys.add(ActiveMQDefaultConfiguration.getPropPasswordCodec()); allowableAcceptorKeys.add(TransportConstants.BACKLOG_PROP_NAME); + allowableAcceptorKeys.add(TransportConstants.CRL_PATH_PROP_NAME); ALLOWABLE_ACCEPTOR_KEYS = Collections.unmodifiableSet(allowableAcceptorKeys); @@ -356,6 +361,7 @@ public class TransportConstants { allowableConnectorKeys.add(TransportConstants.NETTY_CONNECT_TIMEOUT); allowableConnectorKeys.add(TransportConstants.USE_DEFAULT_SSL_CONTEXT_PROP_NAME); allowableConnectorKeys.add(TransportConstants.HANDSHAKE_TIMEOUT); + allowableConnectorKeys.add(TransportConstants.CRL_PATH_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 b4d9dbfee3..03b6e08217 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 @@ -16,6 +16,15 @@ */ package org.apache.activemq.artemis.core.remoting.impl.ssl; +import java.security.Security; +import java.security.cert.CRL; +import java.security.cert.CertStore; +import java.security.cert.CertificateFactory; +import java.security.cert.CollectionCertStoreParameters; +import java.security.cert.PKIXBuilderParameters; +import java.security.cert.X509CertSelector; +import java.util.Collection; +import javax.net.ssl.CertPathTrustManagerParameters; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; @@ -30,11 +39,11 @@ import java.security.AccessController; import java.security.KeyStore; import java.security.PrivilegedAction; import java.security.SecureRandom; - import org.apache.activemq.artemis.utils.ClassloadingUtil; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; + /** * Please note, this class supports PKCS#11 keystores, but there are no specific tests in the ActiveMQ Artemis test-suite to * validate/verify this works because this requires a functioning PKCS#11 provider which is not available by default @@ -51,7 +60,18 @@ public class SSLSupport { final String trustStorePath, final String trustStorePassword) throws Exception { - return SSLSupport.createContext(keystoreProvider, keystorePath, keystorePassword, trustStoreProvider, trustStorePath, trustStorePassword, false); + return SSLSupport.createContext(keystoreProvider, keystorePath, keystorePassword, trustStoreProvider, trustStorePath, trustStorePassword, false, null); + } + + public static SSLContext createContext(final String keystoreProvider, + final String keystorePath, + final String keystorePassword, + final String trustStoreProvider, + final String trustStorePath, + final String trustStorePassword, + final String crlPath) throws Exception { + + return SSLSupport.createContext(keystoreProvider, keystorePath, keystorePassword, trustStoreProvider, trustStorePath, trustStorePassword, false, crlPath); } public static SSLContext createContext(final String keystoreProvider, @@ -61,9 +81,20 @@ public class SSLSupport { final String trustStorePath, final String trustStorePassword, final boolean trustAll) throws Exception { + return SSLSupport.createContext(keystoreProvider, keystorePath, keystorePassword, trustStoreProvider, trustStorePath, trustStorePassword, trustAll, null); + } + + public static SSLContext createContext(final String keystoreProvider, + final String keystorePath, + final String keystorePassword, + final String trustStoreProvider, + final String trustStorePath, + final String trustStorePassword, + final boolean trustAll, + final String crlPath) throws Exception { SSLContext context = SSLContext.getInstance("TLS"); KeyManager[] keyManagers = SSLSupport.loadKeyManagers(keystoreProvider, keystorePath, keystorePassword); - TrustManager[] trustManagers = SSLSupport.loadTrustManager(trustStoreProvider, trustStorePath, trustStorePassword, trustAll); + TrustManager[] trustManagers = SSLSupport.loadTrustManager(trustStoreProvider, trustStorePath, trustStorePassword, trustAll, crlPath); context.init(keyManagers, trustManagers, new SecureRandom()); return context; } @@ -93,18 +124,50 @@ public class SSLSupport { private static TrustManager[] loadTrustManager(final String trustStoreProvider, final String trustStorePath, final String trustStorePassword, - final boolean trustAll) throws Exception { + final boolean trustAll, + final String crlPath) throws Exception { if (trustAll) { //This is useful for testing but not should be used outside of that purpose return InsecureTrustManagerFactory.INSTANCE.getTrustManagers(); } else if (trustStorePath == null && (trustStoreProvider == null || !"PKCS11".equals(trustStoreProvider.toUpperCase()))) { return null; } else { - TrustManagerFactory trustMgrFactory; + TrustManagerFactory trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); KeyStore trustStore = SSLSupport.loadKeystore(trustStoreProvider, trustStorePath, trustStorePassword); - trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustMgrFactory.init(trustStore); + boolean ocsp = Boolean.valueOf(Security.getProperty("ocsp.enable")); + + boolean initialized = false; + if ((ocsp || crlPath != null) && TrustManagerFactory.getDefaultAlgorithm().equalsIgnoreCase("PKIX")) { + PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(trustStore, new X509CertSelector()); + if (crlPath != null) { + pkixParams.setRevocationEnabled(true); + Collection crlList = loadCRL(crlPath); + if (crlList != null) { + pkixParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(crlList))); + } + } + trustMgrFactory.init(new CertPathTrustManagerParameters(pkixParams)); + initialized = true; + } + + if (!initialized) { + trustMgrFactory.init(trustStore); + } + return trustMgrFactory.getTrustManagers(); + + } + } + + private static Collection loadCRL(String crlPath) throws Exception { + if (crlPath == null) { + return null; + } + + URL resource = SSLSupport.validateStoreURL(crlPath); + + try (InputStream is = resource.openStream()) { + return CertificateFactory.getInstance("X.509").generateCRLs(is); } } 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 6141d6cda9..52c5b7ea46 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 @@ -156,6 +156,8 @@ public class NettyAcceptor extends AbstractAcceptor { private final String trustStorePassword; + private final String crlPath; + private final String enabledCipherSuites; private final String enabledProtocols; @@ -259,6 +261,8 @@ public class NettyAcceptor extends AbstractAcceptor { trustStorePassword = ConfigurationHelper.getPasswordProperty(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, TransportConstants.DEFAULT_TRUSTSTORE_PASSWORD, configuration, ActiveMQDefaultConfiguration.getPropMaskPassword(), ActiveMQDefaultConfiguration.getPropPasswordCodec()); + crlPath = ConfigurationHelper.getStringProperty(TransportConstants.CRL_PATH_PROP_NAME, TransportConstants.DEFAULT_CRL_PATH, configuration); + enabledCipherSuites = ConfigurationHelper.getStringProperty(TransportConstants.ENABLED_CIPHER_SUITES_PROP_NAME, TransportConstants.DEFAULT_ENABLED_CIPHER_SUITES, configuration); enabledProtocols = ConfigurationHelper.getStringProperty(TransportConstants.ENABLED_PROTOCOLS_PROP_NAME, TransportConstants.DEFAULT_ENABLED_PROTOCOLS, configuration); @@ -273,6 +277,7 @@ public class NettyAcceptor extends AbstractAcceptor { trustStoreProvider = TransportConstants.DEFAULT_TRUSTSTORE_PROVIDER; trustStorePath = TransportConstants.DEFAULT_TRUSTSTORE_PATH; trustStorePassword = TransportConstants.DEFAULT_TRUSTSTORE_PASSWORD; + crlPath = TransportConstants.DEFAULT_CRL_PATH; enabledCipherSuites = TransportConstants.DEFAULT_ENABLED_CIPHER_SUITES; enabledProtocols = TransportConstants.DEFAULT_ENABLED_PROTOCOLS; needClientAuth = TransportConstants.DEFAULT_NEED_CLIENT_AUTH; @@ -453,7 +458,7 @@ public class NettyAcceptor extends AbstractAcceptor { throw new IllegalArgumentException("If \"" + TransportConstants.SSL_ENABLED_PROP_NAME + "\" is true then \"" + TransportConstants.KEYSTORE_PATH_PROP_NAME + "\" must be non-null " + "unless an alternative \"" + TransportConstants.KEYSTORE_PROVIDER_PROP_NAME + "\" has been specified."); - context = SSLSupport.createContext(keyStoreProvider, keyStorePath, keyStorePassword, trustStoreProvider, trustStorePath, trustStorePassword); + context = SSLSupport.createContext(keyStoreProvider, keyStorePath, keyStorePassword, trustStoreProvider, trustStorePath, trustStorePassword, crlPath); } catch (Exception e) { IllegalStateException ise = new IllegalStateException("Unable to create NettyAcceptor for " + host + ":" + port); ise.initCause(e); diff --git a/examples/features/standard/pom.xml b/examples/features/standard/pom.xml index d254992703..0d77a4e7d5 100644 --- a/examples/features/standard/pom.xml +++ b/examples/features/standard/pom.xml @@ -102,6 +102,7 @@ under the License. xa-heuristic xa-receive xa-send + ssl-enabled-crl-mqtt @@ -173,6 +174,7 @@ under the License. xa-heuristic xa-receive xa-send + ssl-enabled-crl-mqtt diff --git a/examples/features/standard/ssl-enabled-crl-mqtt/pom.xml b/examples/features/standard/ssl-enabled-crl-mqtt/pom.xml new file mode 100644 index 0000000000..1c26f0dd12 --- /dev/null +++ b/examples/features/standard/ssl-enabled-crl-mqtt/pom.xml @@ -0,0 +1,115 @@ + + + + + 4.0.0 + + + org.apache.activemq.examples.broker + jms-examples + 2.5.0-SNAPSHOT + + + ssl-enabled-crl-mqtt + jar + ActiveMQ Artemis Mqtt CRL Example + + + ${project.basedir}/../../../.. + + + + + org.apache.activemq + artemis-jms-client-all + ${project.version} + + + org.fusesource.mqtt-client + mqtt-client + + + + + + + org.apache.activemq + artemis-maven-plugin + + + create + + create + + + ${noServer} + + + + start + + cli + + + ${noServer} + true + tcp://localhost:61616 + consumer + activemq + + run + + + + + runClient + + runClient + + + org.apache.activemq.artemis.jms.example.MqttCrlEnabledExample + + + + stop + + cli + + + ${noServer} + + stop + + + + + + + org.apache.activemq.examples.broker + ssl-enabled-crl-mqtt + ${project.version} + + + + + + + diff --git a/examples/features/standard/ssl-enabled-crl-mqtt/readme.md b/examples/features/standard/ssl-enabled-crl-mqtt/readme.md new file mode 100644 index 0000000000..56be3ceeeb --- /dev/null +++ b/examples/features/standard/ssl-enabled-crl-mqtt/readme.md @@ -0,0 +1,98 @@ +# ActiveMQ Artemis MQTT CRL Example + +To run the example, simply type **mvn verify** from this directory, or **mvn -PnoServer verify** if you want to start and create the server manually. + +This example shows you how to configure 2-way SSL with CRL along with 2 different connections, one with a valid certificate and another with a revoked certificate. + +To configure 2-way SSL with CRL you need to configure the acceptor as follows: + +``` +tcp://0.0.0.0:1883?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;protocols=MQTT;useEpoll=true;sslEnabled=true;keyStorePath=${data.dir}/../etc/keystore1.jks;keyStorePassword=changeit;trustStorePath=${data.dir}/../etc/truststore.jks;keyStorePassword=changeit;crlPath=${data.dir}/../etc/root.crl.pem;needClientAuth=true` +``` + +In the server-side URL, the `keystore1.jks` is the key store file holding the server's key certificate. The `truststore.jks` is the file holding the certificates which the server trusts. The `root.crl.pem` is the file holding the revoked certificates. Notice also the `sslEnabled` and `needClientAuth` parameters which enable SSL and require clients to present their own certificate respectively. + +The various keystore files are generated using the following commands. Keep in mind that each common name should be different and the passwords should be `changeit`. + +``` +openssl genrsa -out ca.key 2048 +openssl req -new -x509 -days 1826 -key ca.key -out ca.crt +touch certindex +echo 01 > certserial +echo 01 > crlnumber +``` + +## Create the ca.conf file: + +``` +[ ca ] +default_ca = myca + +[ crl_ext ] +# issuerAltName=issuer:copy #this would copy the issuer name to altname +authorityKeyIdentifier=keyid:always + +[ myca ] +dir = ./ +new_certs_dir = $dir +unique_subject = no +certificate = $dir/ca.crt +database = $dir/certindex +private_key = $dir/ca.key +serial = $dir/certserial +default_days = 730 +default_md = sha1 +policy = myca_policy +x509_extensions = myca_extensions +crlnumber = $dir/crlnumber +default_crl_days = 730 + +[ myca_policy ] +commonName = supplied +stateOrProvinceName = supplied +countryName = optional +emailAddress = optional +organizationName = supplied +organizationalUnitName = optional + +[ myca_extensions ] +basicConstraints = CA:false +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always +keyUsage = digitalSignature,keyEncipherment +extendedKeyUsage = serverAuth, clientAuth +crlDistributionPoints = URI:http://example.com/root.crl +subjectAltName = @alt_names + +[alt_names] +DNS.1 = example.com +DNS.2 = *.example.com` +``` + +## Continue with the following commands: + +``` +openssl genrsa -out keystore1.key 2048 +openssl req -new -key keystore1.key -out keystore1.csr +openssl ca -batch -config ca.conf -notext -in keystore1.csr -out keystore1.crt +openssl genrsa -out client_revoked.key 2048 +openssl req -new -key client_revoked.key -out client_revoked.csr +openssl ca -batch -config ca.conf -notext -in client_revoked.csr -out client_revoked.crt +openssl genrsa -out client_not_revoked.key 2048 +openssl req -new -key client_not_revoked.key -out client_not_revoked.csr +openssl ca -batch -config ca.conf -notext -in client_not_revoked.csr -out client_not_revoked.crt +openssl ca -config ca.conf -gencrl -keyfile ca.key -cert ca.crt -out root.crl.pem +openssl ca -config ca.conf -revoke client_revoked.crt -keyfile ca.key -cert ca.crt +openssl ca -config ca.conf -gencrl -keyfile ca.key -cert ca.crt -out root.crl.pem + +openssl pkcs12 -export -name client_revoked -in client_revoked.crt -inkey client_revoked.key -out client_revoked.p12 +keytool -importkeystore -destkeystore client_revoked.jks -srckeystore client_revoked.p12 -srcstoretype pkcs12 -alias client_revoked + +openssl pkcs12 -export -name client_not_revoked -in client_not_revoked.crt -inkey client_not_revoked.key -out client_not_revoked.p12 +keytool -importkeystore -destkeystore client_not_revoked.jks -srckeystore client_not_revoked.p12 -srcstoretype pkcs12 -alias client_not_revoked + +openssl pkcs12 -export -name keystore1 -in keystore1.crt -inkey keystore1.key -out keystore1.p12 +keytool -importkeystore -destkeystore keystore1.jks -srckeystore keystore1.p12 -srcstoretype pkcs12 -alias keystore1 + +keytool -import -trustcacerts -alias trust_key -file ca.crt -keystore truststore.jks +``` \ No newline at end of file diff --git a/examples/features/standard/ssl-enabled-crl-mqtt/src/main/java/org/apache/activemq/artemis/jms/example/MqttCrlEnabledExample.java b/examples/features/standard/ssl-enabled-crl-mqtt/src/main/java/org/apache/activemq/artemis/jms/example/MqttCrlEnabledExample.java new file mode 100644 index 0000000000..a4ddf6a6dc --- /dev/null +++ b/examples/features/standard/ssl-enabled-crl-mqtt/src/main/java/org/apache/activemq/artemis/jms/example/MqttCrlEnabledExample.java @@ -0,0 +1,83 @@ +/* + * 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.jms.example; + +import javax.net.ssl.SSLException; +import java.util.concurrent.TimeUnit; + +import org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport; +import org.fusesource.mqtt.client.BlockingConnection; +import org.fusesource.mqtt.client.MQTT; +import org.fusesource.mqtt.client.Message; +import org.fusesource.mqtt.client.QoS; +import org.fusesource.mqtt.client.Topic; + +public class MqttCrlEnabledExample { + + public static void main(final String[] args) throws Exception { + boolean exception = false; + try { + callBroker("truststore.jks", "changeit", "client_revoked.jks", "changeit"); + } catch (SSLException e) { + exception = true; + } + if (!exception) { + throw new RuntimeException("The connection should be revoked"); + } + callBroker("truststore.jks", "changeit", "client_not_revoked.jks", "changeit"); + } + + private static void callBroker(String truststorePath, String truststorePass, String keystorePath, String keystorePass) throws Exception { + BlockingConnection connection = null; + + try { + connection = retrieveMQTTConnection("ssl://localhost:1883", truststorePath, truststorePass, keystorePath, keystorePass); + // Subscribe to topics + Topic[] topics = {new Topic("test/+/some/#", QoS.AT_MOST_ONCE)}; + connection.subscribe(topics); + + // Publish Messages + String payload = "This is message 1"; + + connection.publish("test/1/some/la", payload.getBytes(), QoS.AT_LEAST_ONCE, false); + + Message message = connection.receive(5, TimeUnit.SECONDS); + System.out.println("Message received: " + new String(message.getPayload())); + + } catch (Exception e) { + throw e; + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } + + private static BlockingConnection retrieveMQTTConnection(String host, String truststorePath, String truststorePass, String keystorePath, String keystorePass) throws Exception { + MQTT mqtt = new MQTT(); + mqtt.setConnectAttemptsMax(0); + mqtt.setReconnectAttemptsMax(0); + mqtt.setHost(host); + mqtt.setSslContext(SSLSupport.createContext("JKS", keystorePath, keystorePass, "JKS", truststorePath, truststorePass)); + mqtt.setCleanSession(true); + + BlockingConnection connection = mqtt.blockingConnection(); + connection.connect(); + return connection; + } + +} \ No newline at end of file diff --git a/examples/features/standard/ssl-enabled-crl-mqtt/src/main/resources/activemq/server0/broker.xml b/examples/features/standard/ssl-enabled-crl-mqtt/src/main/resources/activemq/server0/broker.xml new file mode 100644 index 0000000000..9877bd57fa --- /dev/null +++ b/examples/features/standard/ssl-enabled-crl-mqtt/src/main/resources/activemq/server0/broker.xml @@ -0,0 +1,36 @@ + + + + + + false + + + tcp://localhost:61616 + tcp://0.0.0.0:1883?protocols=MQTT;sslEnabled=true;keyStorePath=keystore1.jks;keyStorePassword=changeit;trustStorePath=truststore.jks;keyStorePassword=changeit;crlPath=root.crl.pem;needClientAuth=true + + + + true + / + # + + + + + + \ No newline at end of file diff --git a/examples/features/standard/ssl-enabled-crl-mqtt/src/main/resources/activemq/server0/keystore1.jks b/examples/features/standard/ssl-enabled-crl-mqtt/src/main/resources/activemq/server0/keystore1.jks new file mode 100644 index 0000000000..f1d8857a11 Binary files /dev/null and b/examples/features/standard/ssl-enabled-crl-mqtt/src/main/resources/activemq/server0/keystore1.jks differ diff --git a/examples/features/standard/ssl-enabled-crl-mqtt/src/main/resources/activemq/server0/root.crl.pem b/examples/features/standard/ssl-enabled-crl-mqtt/src/main/resources/activemq/server0/root.crl.pem new file mode 100644 index 0000000000..8938392af6 --- /dev/null +++ b/examples/features/standard/ssl-enabled-crl-mqtt/src/main/resources/activemq/server0/root.crl.pem @@ -0,0 +1,12 @@ +-----BEGIN X509 CRL----- +MIIB2DCBwQIBATANBgkqhkiG9w0BAQUFADBpMQswCQYDVQQGEwJBVTETMBEGA1UE +CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk +MRAwDgYDVQQDDAdhc2ZnZGZnMRAwDgYJKoZIhvcNAQkBFgFhFw0xNzEyMTQxODAw +NDVaFw0xOTEyMTQxODAwNDVaMBQwEgIBAhcNMTcxMjE0MTgwMDM2WqAOMAwwCgYD +VR0UBAMCAQIwDQYJKoZIhvcNAQEFBQADggEBACNiLQvZayn+ULeeSTnxcOOPaIku +1E5AGG3M6uUBalECEpstzmXQELdiZvQb2BMRb1hpm1pNJ8uITjrjeT6bf1+KGgeN +6lRMg36AwyQm8LGiE6ry9jF1OCHqERuImQUrRKWRUbL4hT79Fmji1xm9T9CA3RmE +hjN5oHXM5avF+pm6aU2L2bZ03DhU4Ur0rOd1DCXcGWiZc7VJEQicSrG2R8dagFO/ +w0OUFiTahbdxSguNNU5kIuSltm4kfMM7GcFMb9/kMTTz/U+nUarm7ZzZozn7p/Sb +9FjJ39JzFwq0jTT2bK+3WEWagQs9eNWAPjb5F3ofBSUleZ1f3rdhWWCSS+A= +-----END X509 CRL----- diff --git a/examples/features/standard/ssl-enabled-crl-mqtt/src/main/resources/activemq/server0/truststore.jks b/examples/features/standard/ssl-enabled-crl-mqtt/src/main/resources/activemq/server0/truststore.jks new file mode 100644 index 0000000000..5a8d79fc61 Binary files /dev/null and b/examples/features/standard/ssl-enabled-crl-mqtt/src/main/resources/activemq/server0/truststore.jks differ diff --git a/examples/features/standard/ssl-enabled-crl-mqtt/src/main/resources/client_not_revoked.jks b/examples/features/standard/ssl-enabled-crl-mqtt/src/main/resources/client_not_revoked.jks new file mode 100644 index 0000000000..7e47443a17 Binary files /dev/null and b/examples/features/standard/ssl-enabled-crl-mqtt/src/main/resources/client_not_revoked.jks differ diff --git a/examples/features/standard/ssl-enabled-crl-mqtt/src/main/resources/client_revoked.jks b/examples/features/standard/ssl-enabled-crl-mqtt/src/main/resources/client_revoked.jks new file mode 100644 index 0000000000..8d6f480702 Binary files /dev/null and b/examples/features/standard/ssl-enabled-crl-mqtt/src/main/resources/client_revoked.jks differ diff --git a/examples/features/standard/ssl-enabled-crl-mqtt/src/main/resources/truststore.jks b/examples/features/standard/ssl-enabled-crl-mqtt/src/main/resources/truststore.jks new file mode 100644 index 0000000000..5a8d79fc61 Binary files /dev/null and b/examples/features/standard/ssl-enabled-crl-mqtt/src/main/resources/truststore.jks differ diff --git a/tests/integration-tests/pom.xml b/tests/integration-tests/pom.xml index 1394baee84..0249033b85 100644 --- a/tests/integration-tests/pom.xml +++ b/tests/integration-tests/pom.xml @@ -481,6 +481,15 @@ + + org.apache.maven.plugins + maven-resources-plugin + + + jks + + + diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/mqtt/imported/MQTTSecurityCRLTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/mqtt/imported/MQTTSecurityCRLTest.java new file mode 100644 index 0000000000..4f88661ad5 --- /dev/null +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/mqtt/imported/MQTTSecurityCRLTest.java @@ -0,0 +1,247 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.activemq.artemis.tests.integration.mqtt.imported; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import org.apache.activemq.artemis.api.core.TransportConfiguration; +import org.apache.activemq.artemis.core.config.Configuration; +import org.apache.activemq.artemis.core.config.WildcardConfiguration; +import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory; +import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; +import org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport; +import org.apache.activemq.artemis.core.server.ActiveMQServer; +import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; +import org.fusesource.mqtt.client.BlockingConnection; +import org.fusesource.mqtt.client.MQTT; +import org.fusesource.mqtt.client.Message; +import org.fusesource.mqtt.client.QoS; +import org.fusesource.mqtt.client.Topic; +import org.junit.Test; + +public class MQTTSecurityCRLTest extends ActiveMQTestBase { + /** + * These artifacts are required for testing mqtt with CRL + *

+ * openssl genrsa -out ca.key 2048 + * openssl req -new -x509 -days 1826 -key ca.key -out ca.crt + * touch certindex + * echo 01 > certserial + * echo 01 > crlnumber + *

+ * Create ca.conf file with + *

+ * [ ca ] + * default_ca = myca + *

+ * [ crl_ext ] + * # issuerAltName=issuer:copy #this would copy the issuer name to altname + * authorityKeyIdentifier=keyid:always + *

+ * [ myca ] + * dir = ./ + * new_certs_dir = $dir + * unique_subject = no + * certificate = $dir/ca.crt + * database = $dir/certindex + * private_key = $dir/ca.key + * serial = $dir/certserial + * default_days = 730 + * default_md = sha1 + * policy = myca_policy + * x509_extensions = myca_extensions + * crlnumber = $dir/crlnumber + * default_crl_days = 730 + *

+ * [ myca_policy ] + * commonName = supplied + * stateOrProvinceName = supplied + * countryName = optional + * emailAddress = optional + * organizationName = supplied + * organizationalUnitName = optional + *

+ * [ myca_extensions ] + * basicConstraints = CA:false + * subjectKeyIdentifier = hash + * authorityKeyIdentifier = keyid:always + * keyUsage = digitalSignature,keyEncipherment + * extendedKeyUsage = serverAuth, clientAuth + * crlDistributionPoints = URI:http://example.com/root.crl + * subjectAltName = @alt_names + *

+ * [alt_names] + * DNS.1 = example.com + * DNS.2 = *.example.com + *

+ * Continue executing the commands: + *

+ * openssl genrsa -out keystore1.key 2048 + * openssl req -new -key keystore1.key -out keystore1.csr + * openssl ca -batch -config ca.conf -notext -in keystore1.csr -out keystore1.crt + * openssl genrsa -out client_revoked.key 2048 + * openssl req -new -key client_revoked.key -out client_revoked.csr + * openssl ca -batch -config ca.conf -notext -in client_revoked.csr -out client_revoked.crt + * openssl genrsa -out client_not_revoked.key 2048 + * openssl req -new -key client_not_revoked.key -out client_not_revoked.csr + * openssl ca -batch -config ca.conf -notext -in client_not_revoked.csr -out client_not_revoked.crt + * openssl ca -config ca.conf -gencrl -keyfile ca.key -cert ca.crt -out root.crl.pem + * openssl ca -config ca.conf -revoke client_revoked.crt -keyfile ca.key -cert ca.crt + * openssl ca -config ca.conf -gencrl -keyfile ca.key -cert ca.crt -out root.crl.pem + *

+ * openssl pkcs12 -export -name client_revoked -in client_revoked.crt -inkey client_revoked.key -out client_revoked.p12 + * keytool -importkeystore -destkeystore client_revoked.jks -srckeystore client_revoked.p12 -srcstoretype pkcs12 -alias client_revoked + *

+ * openssl pkcs12 -export -name client_not_revoked -in client_not_revoked.crt -inkey client_not_revoked.key -out client_not_revoked.p12 + * keytool -importkeystore -destkeystore client_not_revoked.jks -srckeystore client_not_revoked.p12 -srcstoretype pkcs12 -alias client_not_revoked + *

+ * openssl pkcs12 -export -name keystore1 -in keystore1.crt -inkey keystore1.key -out keystore1.p12 + * keytool -importkeystore -destkeystore keystore1.jks -srckeystore keystore1.p12 -srcstoretype pkcs12 -alias keystore1 + *

+ * keytool -import -trustcacerts -alias trust_key -file ca.crt -keystore truststore.jks + */ + + @Test(expected = SSLException.class) + public void crlRevokedTest() throws Exception { + + ActiveMQServer server1 = initServer(); + BlockingConnection connection1 = null; + try { + server1.start(); + + while (!server1.isStarted()) { + Thread.sleep(50); + } + + connection1 = retrieveMQTTConnection("ssl://localhost:1883", "truststore.jks", "changeit", "client_revoked.jks", "changeit"); + + // Subscribe to topics + Topic[] topics = {new Topic("test/+/some/#", QoS.AT_MOST_ONCE)}; + connection1.subscribe(topics); + + // Publish Messages + String payload1 = "This is message 1"; + + connection1.publish("test/1/some/la", payload1.getBytes(), QoS.AT_LEAST_ONCE, false); + + Message message1 = connection1.receive(5, TimeUnit.SECONDS); + + assertEquals(payload1, new String(message1.getPayload())); + + } finally { + if (connection1 != null) { + connection1.disconnect(); + } + if (server1.isStarted()) { + server1.stop(); + } + } + } + + @Test + public void crlNotRevokedTest() throws Exception { + + ActiveMQServer server1 = initServer(); + BlockingConnection connection1 = null; + try { + server1.start(); + + while (!server1.isStarted()) { + Thread.sleep(50); + } + + connection1 = retrieveMQTTConnection("ssl://localhost:1883", "truststore.jks", "changeit", "client_not_revoked.jks", "changeit"); + + // Subscribe to topics + Topic[] topics = {new Topic("test/+/some/#", QoS.AT_MOST_ONCE)}; + connection1.subscribe(topics); + + // Publish Messages + String payload1 = "This is message 1"; + + connection1.publish("test/1/some/la", payload1.getBytes(), QoS.AT_LEAST_ONCE, false); + + Message message1 = connection1.receive(5, TimeUnit.SECONDS); + + assertEquals(payload1, new String(message1.getPayload())); + + } finally { + if (connection1 != null) { + connection1.disconnect(); + } + if (server1.isStarted()) { + server1.stop(); + } + } + } + + + private ActiveMQServer initServer() throws Exception { + Configuration configuration = createDefaultNettyConfig().setSecurityEnabled(false); + + addMqttTransportConfiguration(configuration); + addWildCardConfiguration(configuration); + + ActiveMQServer server = createServer(true, configuration); + return server; + } + + private void addWildCardConfiguration(Configuration configuration) { + WildcardConfiguration wildcardConfiguration = new WildcardConfiguration(); + wildcardConfiguration.setAnyWords('#'); + wildcardConfiguration.setDelimiter('/'); + wildcardConfiguration.setRoutingEnabled(true); + wildcardConfiguration.setSingleWord('+'); + + configuration.setWildCardConfiguration(wildcardConfiguration); + } + + private void addMqttTransportConfiguration(Configuration configuration) throws IOException { + TransportConfiguration transportConfiguration = new TransportConfiguration(NettyAcceptorFactory.class.getCanonicalName(), null, "mqtt", null); + + transportConfiguration.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true); + transportConfiguration.getParams().put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, "truststore.jks"); + transportConfiguration.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, "changeit"); + transportConfiguration.getParams().put(TransportConstants.KEYSTORE_PATH_PROP_NAME, "keystore1.jks"); + transportConfiguration.getParams().put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, "changeit"); + transportConfiguration.getParams().put(TransportConstants.CRL_PATH_PROP_NAME, "root.crl.pem"); + transportConfiguration.getParams().put(TransportConstants.NEED_CLIENT_AUTH_PROP_NAME, "true"); + transportConfiguration.getParams().put(TransportConstants.PORT_PROP_NAME, "1883"); + transportConfiguration.getParams().put(TransportConstants.HOST_PROP_NAME, "localhost"); + transportConfiguration.getParams().put(TransportConstants.PROTOCOLS_PROP_NAME, "MQTT"); + + configuration.getAcceptorConfigurations().add(transportConfiguration); + } + + private BlockingConnection retrieveMQTTConnection(String host, String truststorePath, String truststorePass, String keystorePath, String keystorePass) throws Exception { + MQTT mqtt = new MQTT(); + mqtt.setConnectAttemptsMax(1); + mqtt.setReconnectAttemptsMax(0); + mqtt.setHost(host); + SSLContext sslContext = SSLSupport.createContext(TransportConstants.DEFAULT_KEYSTORE_PROVIDER, keystorePath, keystorePass, TransportConstants.DEFAULT_TRUSTSTORE_PROVIDER, truststorePath, truststorePass); + mqtt.setSslContext(sslContext); + + BlockingConnection connection = mqtt.blockingConnection(); + connection.connect(); + return connection; + } + + +} diff --git a/tests/integration-tests/src/test/resources/client_not_revoked.jks b/tests/integration-tests/src/test/resources/client_not_revoked.jks new file mode 100644 index 0000000000..7e47443a17 Binary files /dev/null and b/tests/integration-tests/src/test/resources/client_not_revoked.jks differ diff --git a/tests/integration-tests/src/test/resources/client_revoked.jks b/tests/integration-tests/src/test/resources/client_revoked.jks new file mode 100644 index 0000000000..8d6f480702 Binary files /dev/null and b/tests/integration-tests/src/test/resources/client_revoked.jks differ diff --git a/tests/integration-tests/src/test/resources/keystore1.jks b/tests/integration-tests/src/test/resources/keystore1.jks new file mode 100644 index 0000000000..f1d8857a11 Binary files /dev/null and b/tests/integration-tests/src/test/resources/keystore1.jks differ diff --git a/tests/integration-tests/src/test/resources/mqttCrl/client0/truststore.jks b/tests/integration-tests/src/test/resources/mqttCrl/client0/truststore.jks new file mode 100644 index 0000000000..5a8d79fc61 Binary files /dev/null and b/tests/integration-tests/src/test/resources/mqttCrl/client0/truststore.jks differ diff --git a/tests/integration-tests/src/test/resources/mqttCrl/client1/truststore.jks b/tests/integration-tests/src/test/resources/mqttCrl/client1/truststore.jks new file mode 100644 index 0000000000..5a8d79fc61 Binary files /dev/null and b/tests/integration-tests/src/test/resources/mqttCrl/client1/truststore.jks differ diff --git a/tests/integration-tests/src/test/resources/root.crl.pem b/tests/integration-tests/src/test/resources/root.crl.pem new file mode 100644 index 0000000000..8938392af6 --- /dev/null +++ b/tests/integration-tests/src/test/resources/root.crl.pem @@ -0,0 +1,12 @@ +-----BEGIN X509 CRL----- +MIIB2DCBwQIBATANBgkqhkiG9w0BAQUFADBpMQswCQYDVQQGEwJBVTETMBEGA1UE +CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk +MRAwDgYDVQQDDAdhc2ZnZGZnMRAwDgYJKoZIhvcNAQkBFgFhFw0xNzEyMTQxODAw +NDVaFw0xOTEyMTQxODAwNDVaMBQwEgIBAhcNMTcxMjE0MTgwMDM2WqAOMAwwCgYD +VR0UBAMCAQIwDQYJKoZIhvcNAQEFBQADggEBACNiLQvZayn+ULeeSTnxcOOPaIku +1E5AGG3M6uUBalECEpstzmXQELdiZvQb2BMRb1hpm1pNJ8uITjrjeT6bf1+KGgeN +6lRMg36AwyQm8LGiE6ry9jF1OCHqERuImQUrRKWRUbL4hT79Fmji1xm9T9CA3RmE +hjN5oHXM5avF+pm6aU2L2bZ03DhU4Ur0rOd1DCXcGWiZc7VJEQicSrG2R8dagFO/ +w0OUFiTahbdxSguNNU5kIuSltm4kfMM7GcFMb9/kMTTz/U+nUarm7ZzZozn7p/Sb +9FjJ39JzFwq0jTT2bK+3WEWagQs9eNWAPjb5F3ofBSUleZ1f3rdhWWCSS+A= +-----END X509 CRL----- diff --git a/tests/integration-tests/src/test/resources/truststore.jks b/tests/integration-tests/src/test/resources/truststore.jks new file mode 100644 index 0000000000..5a8d79fc61 Binary files /dev/null and b/tests/integration-tests/src/test/resources/truststore.jks differ