[ https://issues.apache.org/jira/browse/ARTEMIS-1548?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16291619#comment-16291619 ]
ASF GitHub Bot commented on ARTEMIS-1548: ----------------------------------------- Github user jbertram commented on a diff in the pull request: https://github.com/apache/activemq-artemis/pull/1715#discussion_r157064083 --- Diff: tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/mqtt/imported/MQTTSecurityCRLTest.java --- @@ -0,0 +1,357 @@ +/** + * 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 java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.KeyStore; +import java.security.ProtectionDomain; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +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.config.impl.FileConfiguration; +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.security.Role; +import org.apache.activemq.artemis.core.server.ActiveMQServer; +import org.apache.activemq.artemis.core.settings.HierarchicalRepository; +import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager; +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> + * # Mainly copied from: + * # http://swearingscience.com/2009/01/18/openssl-self-signed-ca/ + * <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 + */ + + protected String fullUser = "user"; + protected String fullPass = "pass"; + + + public File basedir() throws IOException { + ProtectionDomain protectionDomain = getClass().getProtectionDomain(); + return new File(new File(protectionDomain.getCodeSource().getLocation().getPath()), "../..").getCanonicalFile(); + } + + @Test(expected = SSLException.class) + public void crlRevokedTest() throws Exception { + + ActiveMQServer server1 = initServer(); + BlockingConnection connection1 = null; + try { + server1.start(); + + while (!server1.isStarted()) { + Thread.sleep(50); + } + + String basedir = basedir().getPath() + "/src/test/resources/mqttCrl/client0/"; + connection1 = retrieveMQTTConnection("ssl://localhost:1883", basedir + "truststore.jks", "changeit", basedir + "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); + } + + String basedir = basedir().getPath() + "/src/test/resources/mqttCrl/client1/"; + connection1 = retrieveMQTTConnection("ssl://localhost:1883", basedir + "truststore.jks", "changeit", basedir + "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 = createConfiguration("broker"); + ActiveMQServer server = createServer(true, configuration); + configureBrokerSecurity(server); + return server; + } + + protected Configuration createConfiguration(String name) throws Exception { + FileConfiguration fc = new FileConfiguration(); + + // we need this otherwise the data folder will be located under activemq-server and not on the temporary directory + fc.setPagingDirectory(getTestDir() + "/" + name + "/" + fc.getPagingDirectory()); + fc.setLargeMessagesDirectory(getTestDir() + "/" + name + "/" + fc.getLargeMessagesDirectory()); + fc.setJournalDirectory(getTestDir() + "/" + name + "/" + fc.getJournalDirectory()); + fc.setBindingsDirectory(getTestDir() + "/" + name + "/" + fc.getBindingsDirectory()); + + addMqttTransportConfiguration(fc); + addWildCardConfiguration(fc); + fc.setSecurityEnabled(true); + + return fc; + } + + private void addWildCardConfiguration(FileConfiguration fc) { + WildcardConfiguration wildcardConfiguration = new WildcardConfiguration(); + wildcardConfiguration.setAnyWords('#'); + wildcardConfiguration.setDelimiter('/'); + wildcardConfiguration.setRoutingEnabled(true); + wildcardConfiguration.setSingleWord('+'); + + fc.setWildCardConfiguration(wildcardConfiguration); + } + + private void addMqttTransportConfiguration(FileConfiguration fc) throws IOException { + String basedir = basedir().getPath() + "/src/test/resources/mqttCrl/server/"; + 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, basedir + "truststore.jks"); + transportConfiguration.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, "changeit"); + transportConfiguration.getParams().put(TransportConstants.KEYSTORE_PATH_PROP_NAME, basedir + "keystore1.jks"); + transportConfiguration.getParams().put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, "changeit"); + transportConfiguration.getParams().put(TransportConstants.CRL_PATH_PROP_NAME, basedir + "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"); + + fc.getAcceptorConfigurations().add(transportConfiguration); + } + + protected void configureBrokerSecurity(ActiveMQServer server) { + + ActiveMQJAASSecurityManager securityManager = (ActiveMQJAASSecurityManager) server.getSecurityManager(); + + securityManager.getConfiguration().addUser(fullUser, fullPass); + securityManager.getConfiguration().addRole(fullUser, "full"); + // Configure roles + HierarchicalRepository<Set<Role>> securityRepository = server.getSecurityRepository(); + HashSet<Role> value = new HashSet<>(); + value.add(new Role("nothing", false, false, false, false, false, false, false, false)); + value.add(new Role("browser", false, false, false, false, false, false, false, true)); + value.add(new Role("guest", false, true, false, false, false, false, false, true)); + value.add(new Role("full", true, true, true, true, true, true, true, true)); + securityRepository.addMatch("#", value); + + server.getConfiguration().setSecurityEnabled(true); + } + + public static TrustManager[] getTrustManager(String truststorePath, String truststorePass) throws Exception { + KeyStore trustedCertStore = KeyStore.getInstance("jks"); + + trustedCertStore.load(new FileInputStream(truststorePath), truststorePass.toCharArray()); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + + tmf.init(trustedCertStore); + return tmf.getTrustManagers(); + } + + public static KeyManager[] getKeyManager(String keystorePath, String keystorePass) throws Exception { + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + KeyStore ks = KeyStore.getInstance("jks"); + KeyManager[] keystoreManagers = null; + + byte[] sslCert = loadClientCredential(keystorePath); + + if (sslCert != null && sslCert.length > 0) { + ByteArrayInputStream bin = new ByteArrayInputStream(sslCert); + ks.load(bin, keystorePass.toCharArray()); + kmf.init(ks, keystorePass.toCharArray()); + keystoreManagers = kmf.getKeyManagers(); + } + return keystoreManagers; + } + + private static byte[] loadClientCredential(String fileName) throws IOException { + if (fileName == null) { + return null; + } + FileInputStream in = new FileInputStream(fileName); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buf = new byte[512]; + int i = in.read(buf); + while (i > 0) { + out.write(buf, 0, i); + i = in.read(buf); + } + in.close(); + return out.toByteArray(); + } + + 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); + mqtt.setUserName(fullUser); + mqtt.setPassword(fullPass); + SSLContext sslContext = SSLContext.getInstance("TLS"); --- End diff -- Why not use the SSLSupport class that's already included in the code-base to generate the SSLContext? > Support CRL > ----------- > > Key: ARTEMIS-1548 > URL: https://issues.apache.org/jira/browse/ARTEMIS-1548 > Project: ActiveMQ Artemis > Issue Type: New Feature > Reporter: Justin Bertram > Assignee: Justin Bertram > -- This message was sent by Atlassian JIRA (v6.4.14#64029)