ARTEMIS-1548 Support CRL

Add support for CRL in client authentication
This commit is contained in:
raul.valdoleiros 2017-12-05 15:57:13 +00:00 committed by Justin Bertram
parent 7232302f03
commit 8c0e1e96ed
24 changed files with 702 additions and 9 deletions

View File

@ -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();

View File

@ -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);

View File

@ -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());
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<? extends CRL> 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<? extends CRL> 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);
}
}

View File

@ -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);

View File

@ -102,6 +102,7 @@ under the License.
<module>xa-heuristic</module>
<module>xa-receive</module>
<module>xa-send</module>
<module>ssl-enabled-crl-mqtt</module>
</modules>
</profile>
<profile>
@ -173,6 +174,7 @@ under the License.
<module>xa-heuristic</module>
<module>xa-receive</module>
<module>xa-send</module>
<module>ssl-enabled-crl-mqtt</module>
</modules>
</profile>

View File

@ -0,0 +1,115 @@
<?xml version='1.0'?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.activemq.examples.broker</groupId>
<artifactId>jms-examples</artifactId>
<version>2.5.0-SNAPSHOT</version>
</parent>
<artifactId>ssl-enabled-crl-mqtt</artifactId>
<packaging>jar</packaging>
<name>ActiveMQ Artemis Mqtt CRL Example</name>
<properties>
<activemq.basedir>${project.basedir}/../../../..</activemq.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-jms-client-all</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.fusesource.mqtt-client</groupId>
<artifactId>mqtt-client</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-maven-plugin</artifactId>
<executions>
<execution>
<id>create</id>
<goals>
<goal>create</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
</configuration>
</execution>
<execution>
<id>start</id>
<goals>
<goal>cli</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<spawn>true</spawn>
<testURI>tcp://localhost:61616</testURI>
<testUser>consumer</testUser>
<testPassword>activemq</testPassword>
<args>
<param>run</param>
</args>
</configuration>
</execution>
<execution>
<id>runClient</id>
<goals>
<goal>runClient</goal>
</goals>
<configuration>
<clientClass>org.apache.activemq.artemis.jms.example.MqttCrlEnabledExample</clientClass>
</configuration>
</execution>
<execution>
<id>stop</id>
<goals>
<goal>cli</goal>
</goals>
<configuration>
<ignore>${noServer}</ignore>
<args>
<param>stop</param>
</args>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.apache.activemq.examples.broker</groupId>
<artifactId>ssl-enabled-crl-mqtt</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>

View File

@ -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:
```
<acceptor name="mqtt">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</acceptor>`
```
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
```

View File

@ -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;
}
}

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
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.
-->
<configuration xmlns="urn:activemq" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:activemq /schema/artemis-server.xsd">
<core xmlns="urn:activemq:core">
<security-enabled>false</security-enabled>
<acceptors>
<acceptor name="netty-acceptor">tcp://localhost:61616</acceptor>
<acceptor name="mqtt">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</acceptor>
</acceptors>
<wildcard-addresses>
<routing-enabled>true</routing-enabled>
<delimiter>/</delimiter>
<any-words>#</any-words>
<single-word>+</single-word>
</wildcard-addresses>
</core>
</configuration>

View File

@ -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-----

View File

@ -481,6 +481,15 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>jks</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
</plugins>
</build>

View File

@ -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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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
* <p>
* 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
* <p>
* Create ca.conf file with
* <p>
* [ ca ]
* default_ca = myca
* <p>
* [ crl_ext ]
* # issuerAltName=issuer:copy #this would copy the issuer name to altname
* authorityKeyIdentifier=keyid:always
* <p>
* [ 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
* <p>
* [ myca_policy ]
* commonName = supplied
* stateOrProvinceName = supplied
* countryName = optional
* emailAddress = optional
* organizationName = supplied
* organizationalUnitName = optional
* <p>
* [ 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
* <p>
* [alt_names]
* DNS.1 = example.com
* DNS.2 = *.example.com
* <p>
* Continue executing the commands:
* <p>
* 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
* <p>
* 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
* <p>
* 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
* <p>
* 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
* <p>
* 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;
}
}

View File

@ -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-----