This closes #1715
This commit is contained in:
commit
fee083a621
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
```
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
Binary file not shown.
|
@ -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-----
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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>
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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-----
|
Binary file not shown.
Loading…
Reference in New Issue