From 87e9b361bb4a6d594402ae2d9ca30a67e9c2b609 Mon Sep 17 00:00:00 2001 From: Justin Bertram Date: Wed, 18 May 2022 22:13:10 -0500 Subject: [PATCH] ARTEMIS-3785 support specifying alias for SSL keystore --- .../client/ActiveMQClientMessageBundle.java | 3 + .../remoting/impl/netty/NettyConnector.java | 10 +++ .../impl/netty/TransportConstants.java | 6 ++ .../remoting/impl/ssl/AliasedKeyManager.java | 89 +++++++++++++++++++ .../core/remoting/impl/ssl/SSLSupport.java | 77 ++++++++++++++-- .../core/remoting/ssl/SSLContextConfig.java | 24 ++++- .../remoting/impl/netty/NettyAcceptor.java | 10 ++- docs/user-manual/en/configuring-transports.md | 12 +++ .../ssl/CoreClientOverOneWaySSLTest.java | 59 +++++++----- .../ssl/CoreClientOverTwoWaySSLTest.java | 68 ++++++++++++++ 10 files changed, 321 insertions(+), 37 deletions(-) create mode 100644 artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/ssl/AliasedKeyManager.java diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientMessageBundle.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientMessageBundle.java index 15194db21a..f2ac6c3b5b 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientMessageBundle.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/ActiveMQClientMessageBundle.java @@ -241,4 +241,7 @@ public interface ActiveMQClientMessageBundle { @Message(id = 219066, value = "The connection was redirected") ActiveMQRoutingException redirected(); + + @Message(id = 219067, value = "Keystore alias {0} not found in {1}", format = Message.Format.MESSAGE_FORMAT) + IllegalArgumentException keystoreAliasNotFound(String keystoreAlias, String keystorePath); } 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 0514411fb5..45a1faeb42 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 @@ -236,6 +236,8 @@ public class NettyConnector extends AbstractConnector { private String keyStorePassword; + private String keyStoreAlias; + private String trustStoreProvider; private String trustStoreType; @@ -399,6 +401,8 @@ public class NettyConnector extends AbstractConnector { keyStorePassword = ConfigurationHelper.getPasswordProperty(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, TransportConstants.DEFAULT_KEYSTORE_PASSWORD, configuration, ActiveMQDefaultConfiguration.getPropMaskPassword(), ActiveMQDefaultConfiguration.getPropPasswordCodec()); + keyStoreAlias = ConfigurationHelper.getStringProperty(TransportConstants.KEYSTORE_ALIAS_PROP_NAME, TransportConstants.DEFAULT_KEYSTORE_ALIAS, configuration); + trustStoreProvider = ConfigurationHelper.getStringProperty(TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME, TransportConstants.DEFAULT_TRUSTSTORE_PROVIDER, configuration); trustStoreType = ConfigurationHelper.getStringProperty(TransportConstants.TRUSTSTORE_TYPE_PROP_NAME, TransportConstants.DEFAULT_TRUSTSTORE_TYPE, configuration); @@ -431,6 +435,7 @@ public class NettyConnector extends AbstractConnector { keyStoreType = TransportConstants.DEFAULT_KEYSTORE_TYPE; keyStorePath = TransportConstants.DEFAULT_KEYSTORE_PATH; keyStorePassword = TransportConstants.DEFAULT_KEYSTORE_PASSWORD; + keyStoreAlias = TransportConstants.DEFAULT_KEYSTORE_ALIAS; trustStoreProvider = TransportConstants.DEFAULT_TRUSTSTORE_PROVIDER; trustStoreType = TransportConstants.DEFAULT_TRUSTSTORE_TYPE; trustStorePath = TransportConstants.DEFAULT_TRUSTSTORE_PATH; @@ -567,6 +572,7 @@ public class NettyConnector extends AbstractConnector { final String realKeyStoreProvider; final String realKeyStoreType; final String realKeyStorePassword; + final String realKeyStoreAlias; final String realTrustStorePath; final String realTrustStoreProvider; final String realTrustStoreType; @@ -578,6 +584,7 @@ public class NettyConnector extends AbstractConnector { realKeyStoreProvider = keyStoreProvider; realKeyStoreType = keyStoreType; realKeyStorePassword = keyStorePassword; + realKeyStoreAlias = keyStoreAlias; realTrustStorePath = trustStorePath; realTrustStoreProvider = trustStoreProvider; realTrustStoreType = trustStoreType; @@ -585,6 +592,7 @@ public class NettyConnector extends AbstractConnector { } else { realKeyStorePath = Stream.of(System.getProperty(ACTIVEMQ_KEYSTORE_PATH_PROP_NAME), System.getProperty(JAVAX_KEYSTORE_PATH_PROP_NAME), keyStorePath).map(v -> useDefaultSslContext ? keyStorePath : v).filter(Objects::nonNull).findFirst().orElse(null); realKeyStorePassword = Stream.of(System.getProperty(ACTIVEMQ_KEYSTORE_PASSWORD_PROP_NAME), System.getProperty(JAVAX_KEYSTORE_PASSWORD_PROP_NAME), keyStorePassword).map(v -> useDefaultSslContext ? keyStorePassword : v).filter(Objects::nonNull).findFirst().orElse(null); + realKeyStoreAlias = keyStoreAlias; Pair keyStoreCompat = SSLSupport.getValidProviderAndType(Stream.of(System.getProperty(ACTIVEMQ_KEYSTORE_PROVIDER_PROP_NAME), System.getProperty(JAVAX_KEYSTORE_PROVIDER_PROP_NAME), keyStoreProvider).map(v -> useDefaultSslContext ? keyStoreProvider : v).filter(Objects::nonNull).findFirst().orElse(null), Stream.of(System.getProperty(ACTIVEMQ_KEYSTORE_TYPE_PROP_NAME), System.getProperty(JAVAX_KEYSTORE_TYPE_PROP_NAME), keyStoreType).map(v -> useDefaultSslContext ? keyStoreType : v).filter(Objects::nonNull).findFirst().orElse(null)); @@ -604,6 +612,7 @@ public class NettyConnector extends AbstractConnector { realKeyStoreProvider = null; realKeyStoreType = null; realKeyStorePassword = null; + realKeyStoreAlias = null; realTrustStorePath = null; realTrustStoreProvider = null; realTrustStoreType = null; @@ -645,6 +654,7 @@ public class NettyConnector extends AbstractConnector { .keystorePath(realKeyStorePath) .keystoreType(realKeyStoreType) .keystorePassword(realKeyStorePassword) + .keystoreAlias(realKeyStoreAlias) .truststoreProvider(realTrustStoreProvider) .truststorePath(realTrustStorePath) .truststoreType(realTrustStoreType) 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 de5eaad2a7..f52987deb2 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 @@ -97,6 +97,8 @@ public class TransportConstants { public static final String KEYSTORE_PASSWORD_PROP_NAME = "keyStorePassword"; + public static final String KEYSTORE_ALIAS_PROP_NAME = "keyStoreAlias"; + public static final String TRUSTSTORE_PROVIDER_PROP_NAME = "trustStoreProvider"; public static final String TRUSTSTORE_TYPE_PROP_NAME = "trustStoreType"; @@ -258,6 +260,8 @@ public class TransportConstants { public static final boolean DEFAULT_USE_DEFAULT_SSL_CONTEXT = false; + public static final String DEFAULT_KEYSTORE_ALIAS = null; + public static final boolean DEFAULT_TCP_NODELAY = true; public static final int DEFAULT_TCP_SENDBUFFER_SIZE = 1024 * 1024; @@ -400,6 +404,7 @@ public class TransportConstants { allowableAcceptorKeys.add(TransportConstants.KEYSTORE_TYPE_PROP_NAME); allowableAcceptorKeys.add(TransportConstants.KEYSTORE_PATH_PROP_NAME); allowableAcceptorKeys.add(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME); + allowableAcceptorKeys.add(TransportConstants.KEYSTORE_ALIAS_PROP_NAME); allowableAcceptorKeys.add(TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME); allowableAcceptorKeys.add(TransportConstants.TRUSTSTORE_TYPE_PROP_NAME); allowableAcceptorKeys.add(TransportConstants.TRUSTSTORE_PATH_PROP_NAME); @@ -472,6 +477,7 @@ public class TransportConstants { allowableConnectorKeys.add(TransportConstants.KEYSTORE_TYPE_PROP_NAME); allowableConnectorKeys.add(TransportConstants.KEYSTORE_PATH_PROP_NAME); allowableConnectorKeys.add(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME); + allowableConnectorKeys.add(TransportConstants.KEYSTORE_ALIAS_PROP_NAME); allowableConnectorKeys.add(TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME); allowableConnectorKeys.add(TransportConstants.TRUSTSTORE_TYPE_PROP_NAME); allowableConnectorKeys.add(TransportConstants.TRUSTSTORE_PATH_PROP_NAME); diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/ssl/AliasedKeyManager.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/ssl/AliasedKeyManager.java new file mode 100644 index 0000000000..0a948cebda --- /dev/null +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/remoting/impl/ssl/AliasedKeyManager.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.activemq.artemis.core.remoting.impl.ssl; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.X509ExtendedKeyManager; +import javax.net.ssl.X509KeyManager; +import java.net.Socket; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +public final class AliasedKeyManager extends X509ExtendedKeyManager { + + private X509KeyManager wrapped; + private String keystoreAlias; + + public AliasedKeyManager(X509KeyManager wrapped, String keystoreAlias) { + super(); + this.wrapped = wrapped; + this.keystoreAlias = keystoreAlias; + } + + @Override + public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { + if (keystoreAlias != null) { + return keystoreAlias; + } + + return wrapped.chooseServerAlias(keyType, issuers, socket); + } + + @Override + public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) { + if (keystoreAlias != null) { + return keystoreAlias; + } + + return super.chooseEngineServerAlias(keyType, issuers, engine); + } + + @Override + public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) { + if (keystoreAlias != null) { + return keystoreAlias; + } + + return wrapped.chooseClientAlias(keyType, issuers, socket); + } + + @Override + public X509Certificate[] getCertificateChain(String alias) { + return wrapped.getCertificateChain(alias); + } + + @Override + public String[] getClientAliases(String keyType, Principal[] issuers) { + return wrapped.getClientAliases(keyType, issuers); + } + + @Override + public String[] getServerAliases(String keyType, Principal[] issuers) { + return wrapped.getServerAliases(keyType, issuers); + } + + @Override + public PrivateKey getPrivateKey(String alias) { + return wrapped.getPrivateKey(alias); + } + + @Override + public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) { + return chooseClientAlias(keyType, issuers, null); + } +} \ No newline at end of file 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 21f4d8a402..7f322a9507 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 @@ -22,6 +22,7 @@ import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509KeyManager; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -29,15 +30,21 @@ import java.net.MalformedURLException; import java.net.URL; import java.security.AccessController; import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; import java.security.PrivilegedAction; import java.security.SecureRandom; import java.security.Security; +import java.security.UnrecoverableKeyException; import java.security.cert.CRL; import java.security.cert.CertStore; +import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.CollectionCertStoreParameters; import java.security.cert.PKIXBuilderParameters; import java.security.cert.X509CertSelector; +import java.security.cert.X509Certificate; import java.util.Collection; import io.netty.handler.ssl.SslContext; @@ -47,6 +54,7 @@ import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import org.apache.activemq.artemis.api.core.Pair; import org.apache.activemq.artemis.api.core.TrustManagerFactoryPlugin; import org.apache.activemq.artemis.core.client.ActiveMQClientLogger; +import org.apache.activemq.artemis.core.client.ActiveMQClientMessageBundle; import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextConfig; import org.apache.activemq.artemis.utils.ClassloadingUtil; @@ -72,6 +80,7 @@ public class SSLSupport { private String sslProvider = TransportConstants.DEFAULT_SSL_PROVIDER; private boolean trustAll = TransportConstants.DEFAULT_TRUST_ALL; private String trustManagerFactoryPlugin = TransportConstants.DEFAULT_TRUST_MANAGER_FACTORY_PLUGIN; + private String keystoreAlias = TransportConstants.DEFAULT_KEYSTORE_ALIAS; public SSLSupport() { } @@ -88,6 +97,7 @@ public class SSLSupport { crlPath = config.getCrlPath(); trustAll = config.isTrustAll(); trustManagerFactoryPlugin = config.getTrustManagerFactoryPlugin(); + keystoreAlias = config.getKeystoreAlias(); } public String getKeystoreProvider() { @@ -126,6 +136,15 @@ public class SSLSupport { return this; } + public String getKeystoreAlias() { + return keystoreAlias; + } + + public SSLSupport setKeystoreAlias(String keystoreAlias) { + this.keystoreAlias = keystoreAlias; + return this; + } + public String getTruststoreProvider() { return truststoreProvider; } @@ -208,18 +227,34 @@ public class SSLSupport { public SslContext createNettyContext() throws Exception { KeyStore keyStore = SSLSupport.loadKeystore(keystoreProvider, keystoreType, keystorePath, keystorePassword); - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keyStore, keystorePassword.toCharArray()); - return SslContextBuilder.forServer(keyManagerFactory).sslProvider(SslProvider.valueOf(sslProvider)).trustManager(loadTrustManagerFactory()).build(); + SslContextBuilder sslContextBuilder; + if (keystoreAlias != null) { + Pair privateKeyAndCertChain = getPrivateKeyAndCertChain(keyStore); + sslContextBuilder = SslContextBuilder.forServer(privateKeyAndCertChain.getA(), privateKeyAndCertChain.getB()); + } else { + sslContextBuilder = SslContextBuilder.forServer(getKeyManagerFactory(keyStore, keystorePassword == null ? null : keystorePassword.toCharArray())); + } + return sslContextBuilder + .sslProvider(SslProvider.valueOf(sslProvider)) + .trustManager(loadTrustManagerFactory()) + .build(); } public SslContext createNettyClientContext() throws Exception { KeyStore keyStore = SSLSupport.loadKeystore(keystoreProvider, keystoreType, keystorePath, keystorePassword); - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keyStore, keystorePassword == null ? null : keystorePassword.toCharArray()); - return SslContextBuilder.forClient().sslProvider(SslProvider.valueOf(sslProvider)).keyManager(keyManagerFactory).trustManager(loadTrustManagerFactory()).build(); - } + SslContextBuilder sslContextBuilder = SslContextBuilder + .forClient() + .sslProvider(SslProvider.valueOf(sslProvider)) + .trustManager(loadTrustManagerFactory()); + if (keystoreAlias != null) { + Pair privateKeyAndCertChain = getPrivateKeyAndCertChain(keyStore); + sslContextBuilder.keyManager(privateKeyAndCertChain.getA(), privateKeyAndCertChain.getB()); + } else { + sslContextBuilder.keyManager(getKeyManagerFactory(keyStore, keystorePassword == null ? null : keystorePassword.toCharArray())); + } + return sslContextBuilder.build(); + } public static String[] parseCommaSeparatedListIntoArray(String suites) { String[] cipherSuites = suites.split(","); @@ -321,7 +356,15 @@ public class SSLSupport { if (factory == null) { return null; } - return factory.getKeyManagers(); + KeyManager[] keyManagers = factory.getKeyManagers(); + if (keystoreAlias != null) { + for (int i = 0; i < keyManagers.length; i++) { + if (keyManagers[i] instanceof X509KeyManager) { + keyManagers[i] = new AliasedKeyManager((X509KeyManager) keyManagers[i], keystoreAlias); + } + } + } + return keyManagers; } private KeyManagerFactory loadKeyManagerFactory() throws Exception { @@ -370,6 +413,24 @@ public class SSLSupport { }); } + private Pair getPrivateKeyAndCertChain(KeyStore keyStore) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException { + PrivateKey key = (PrivateKey) keyStore.getKey(keystoreAlias, keystorePassword.toCharArray()); + if (key == null) { + throw ActiveMQClientMessageBundle.BUNDLE.keystoreAliasNotFound(keystoreAlias, keystorePath); + } + + Certificate[] chain = keyStore.getCertificateChain(keystoreAlias); + X509Certificate[] certChain = new X509Certificate[chain.length]; + System.arraycopy(chain, 0, certChain, 0, chain.length); + return new Pair(key, certChain); + } + + private KeyManagerFactory getKeyManagerFactory(KeyStore keyStore, char[] keystorePassword) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException { + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, keystorePassword); + return keyManagerFactory; + } + /** * The changes ARTEMIS-3155 introduced an incompatibility with old clients using the keyStoreProvider and * trustStoreProvider URL properties. These old clients use these properties to set the *type* of store diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ssl/SSLContextConfig.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ssl/SSLContextConfig.java index 032c7120d0..c047d754b3 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ssl/SSLContextConfig.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/spi/core/remoting/ssl/SSLContextConfig.java @@ -40,6 +40,7 @@ public final class SSLContextConfig { private String crlPath = TransportConstants.DEFAULT_CRL_PATH; private String trustManagerFactoryPlugin = TransportConstants.DEFAULT_TRUST_MANAGER_FACTORY_PLUGIN; private boolean trustAll = TransportConstants.DEFAULT_TRUST_ALL; + private String keystoreAlias = TransportConstants.DEFAULT_KEYSTORE_ALIAS; private Builder() { } @@ -58,6 +59,7 @@ public final class SSLContextConfig { crlPath = config.getCrlPath(); truststoreProvider = config.getTruststoreProvider(); trustAll = config.trustAll; + keystoreAlias = config.keystoreAlias; return this; } @@ -65,7 +67,7 @@ public final class SSLContextConfig { return new SSLContextConfig( keystoreProvider, keystorePath, keystoreType, keystorePassword, truststoreProvider, truststorePath, truststoreType, truststorePassword, - crlPath, trustManagerFactoryPlugin, trustAll + crlPath, trustManagerFactoryPlugin, trustAll, keystoreAlias ); } @@ -119,6 +121,11 @@ public final class SSLContextConfig { return this; } + public Builder keystoreAlias(final String keystoreAlias) { + this.keystoreAlias = keystoreAlias; + return this; + } + public Builder trustManagerFactoryPlugin(final String trustManagerFactoryPlugin) { this.trustManagerFactoryPlugin = trustManagerFactoryPlugin; return this; @@ -140,6 +147,7 @@ public final class SSLContextConfig { private final String trustManagerFactoryPlugin; private final String crlPath; private final boolean trustAll; + private final String keystoreAlias; private final int hashCode; private SSLContextConfig(final String keystoreProvider, @@ -152,7 +160,8 @@ public final class SSLContextConfig { final String truststorePassword, final String crlPath, final String trustManagerFactoryPlugin, - final boolean trustAll) { + final boolean trustAll, + final String keystoreAlias) { this.keystorePath = keystorePath; this.keystoreType = keystoreType; this.keystoreProvider = keystoreProvider; @@ -164,10 +173,11 @@ public final class SSLContextConfig { this.trustManagerFactoryPlugin = trustManagerFactoryPlugin; this.crlPath = crlPath; this.trustAll = trustAll; + this.keystoreAlias = keystoreAlias; hashCode = Objects.hash( keystorePath, keystoreType, keystoreProvider, truststorePath, truststoreType, truststoreProvider, - crlPath, trustManagerFactoryPlugin, trustAll + crlPath, trustManagerFactoryPlugin, trustAll, keystoreAlias ); } @@ -186,7 +196,8 @@ public final class SSLContextConfig { && Objects.equals(truststoreProvider, other.truststoreProvider) && Objects.equals(crlPath, other.crlPath) && Objects.equals(trustManagerFactoryPlugin, other.trustManagerFactoryPlugin) - && trustAll == other.trustAll; + && trustAll == other.trustAll + && Objects.equals(keystoreAlias, other.keystoreAlias); } public String getCrlPath() { @@ -238,6 +249,10 @@ public final class SSLContextConfig { return trustAll; } + public String getKeystoreAlias() { + return keystoreAlias; + } + @Override public String toString() { return "SSLSupport [" + @@ -252,6 +267,7 @@ public final class SSLContextConfig { ", crlPath=" + crlPath + ", trustAll=" + trustAll + ", trustManagerFactoryPlugin=" + trustManagerFactoryPlugin + + ", keystoreAlias=" + keystoreAlias + "]"; } } 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 f8ad5e3a66..dd1bb70e7d 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 @@ -161,6 +161,8 @@ public class NettyAcceptor extends AbstractAcceptor { private final String keyStorePassword; + private final String keystoreAlias; + private final String trustStoreProvider; private final String trustStoreType; @@ -327,11 +329,14 @@ public class NettyAcceptor extends AbstractAcceptor { trustManagerFactoryPlugin = ConfigurationHelper.getStringProperty(TransportConstants.TRUST_MANAGER_FACTORY_PLUGIN_PROP_NAME, TransportConstants.DEFAULT_TRUST_MANAGER_FACTORY_PLUGIN, configuration); + keystoreAlias = ConfigurationHelper.getStringProperty(TransportConstants.KEYSTORE_ALIAS_PROP_NAME, TransportConstants.DEFAULT_KEYSTORE_ALIAS, configuration); + sslContextConfig = SSLContextConfig.builder() .keystoreProvider(keyStoreProvider) .keystorePath(keyStorePath) .keystoreType(keyStoreType) .keystorePassword(keyStorePassword) + .keystoreAlias(keystoreAlias) .truststoreProvider(trustStoreProvider) .truststorePath(trustStorePath) .truststoreType(trustStoreType) @@ -345,6 +350,7 @@ public class NettyAcceptor extends AbstractAcceptor { keyStoreType = TransportConstants.DEFAULT_KEYSTORE_TYPE; keyStorePath = TransportConstants.DEFAULT_KEYSTORE_PATH; keyStorePassword = TransportConstants.DEFAULT_KEYSTORE_PASSWORD; + keystoreAlias = TransportConstants.DEFAULT_KEYSTORE_ALIAS; trustStoreProvider = TransportConstants.DEFAULT_TRUSTSTORE_PROVIDER; trustStoreType = TransportConstants.DEFAULT_TRUSTSTORE_TYPE; trustStorePath = TransportConstants.DEFAULT_TRUSTSTORE_PATH; @@ -563,12 +569,14 @@ public class NettyAcceptor extends AbstractAcceptor { } // only for testing purposes - public void setKeyStorePath(String keyStorePath) { + public void setKeyStoreParameters(String keyStorePath, String keyStoreAlias) { this.keyStorePath = keyStorePath; this.configuration.put(TransportConstants.KEYSTORE_PATH_PROP_NAME, keyStorePath); + this.configuration.put(TransportConstants.KEYSTORE_ALIAS_PROP_NAME, keyStoreAlias); sslContextConfig = SSLContextConfig.builder() .from(sslContextConfig) .keystorePath(keyStorePath) + .keystoreAlias(keyStoreAlias) .build(); } diff --git a/docs/user-manual/en/configuring-transports.md b/docs/user-manual/en/configuring-transports.md index ca31bab189..ee93ad146e 100644 --- a/docs/user-manual/en/configuring-transports.md +++ b/docs/user-manual/en/configuring-transports.md @@ -379,6 +379,18 @@ additional properties: on the client is already making use of the standard Java system property. Default is `null`. +- `keyStoreAlias` + + When used on an `acceptor` this is the alias to select from the SSL key store + (specified via `keyStorePath`) to present to the client when it connects. + + When used on a `connector` this is the alias to select from the SSL key store + (specified via `keyStorePath`) to present to the server when the client + connects to it. This is only relevant for a `connector` when using 2-way SSL + (i.e. mutual authentication). + + Default is `null`. + - `trustStorePath` When used on an `acceptor` this is the path to the server-side SSL key store diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverOneWaySSLTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverOneWaySSLTest.java index 65ce1550c1..34df8d603d 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverOneWaySSLTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverOneWaySSLTest.java @@ -61,25 +61,41 @@ import org.junit.runners.Parameterized; */ @RunWith(value = Parameterized.class) public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase { - String suffix = ""; - @Parameterized.Parameters(name = "storeProvider={0}, storeType={1}") + public static final SimpleString QUEUE = new SimpleString("QueueOverSSL"); + + private boolean generateWarning; + private boolean useKeystoreAlias; + private String storeProvider; + private String storeType; + private String SERVER_SIDE_KEYSTORE; + private String CLIENT_SIDE_TRUSTSTORE; + private final String PASSWORD = "securepass"; + private String suffix = ""; + + private ActiveMQServer server; + + private TransportConfiguration tc; + + @Parameterized.Parameters(name = "storeProvider={0}, storeType={1}, generateWarning={2}, useKeystoreAlias={3}") public static Collection getParameters() { return Arrays.asList(new Object[][]{ - {TransportConstants.DEFAULT_KEYSTORE_PROVIDER, TransportConstants.DEFAULT_KEYSTORE_TYPE, false}, - {"SunJCE", "JCEKS", false}, - {"SUN", "JKS", false}, - {"SunJSSE", "PKCS12", false}, - {"JCEKS", null, true}, // for compatibility with old keyStoreProvider - {"JKS", null, true}, // for compatibility with old keyStoreProvider - {"PKCS12", null, true} // for compatibility with old keyStoreProvider + {TransportConstants.DEFAULT_KEYSTORE_PROVIDER, TransportConstants.DEFAULT_KEYSTORE_TYPE, false, false}, + {TransportConstants.DEFAULT_KEYSTORE_PROVIDER, TransportConstants.DEFAULT_KEYSTORE_TYPE, false, true}, + {"SunJCE", "JCEKS", false, false}, + {"SUN", "JKS", false, false}, + {"SunJSSE", "PKCS12", false, false}, + {"JCEKS", null, true, false}, // for compatibility with old keyStoreProvider + {"JKS", null, true, false}, // for compatibility with old keyStoreProvider + {"PKCS12", null, true, false} // for compatibility with old keyStoreProvider }); } - public CoreClientOverOneWaySSLTest(String storeProvider, String storeType, boolean generateWarning) { + public CoreClientOverOneWaySSLTest(String storeProvider, String storeType, boolean generateWarning, boolean useKeystoreAlias) { this.storeProvider = storeProvider; this.storeType = storeType; this.generateWarning = generateWarning; + this.useKeystoreAlias = useKeystoreAlias; suffix = storeType == null || storeType.length() == 0 ? storeProvider.toLowerCase() : storeType.toLowerCase(); // keytool expects PKCS12 stores to use the extension "p12" if (suffix.equalsIgnoreCase("PKCS12")) { @@ -89,19 +105,6 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase { CLIENT_SIDE_TRUSTSTORE = "server-ca-truststore." + suffix; } - public static final SimpleString QUEUE = new SimpleString("QueueOverSSL"); - - private boolean generateWarning; - private String storeProvider; - private String storeType; - private String SERVER_SIDE_KEYSTORE; - private String CLIENT_SIDE_TRUSTSTORE; - private final String PASSWORD = "securepass"; - - private ActiveMQServer server; - - private TransportConfiguration tc; - @Before public void validateLogging() { AssertionLoggerHandler.startCapture(); @@ -522,7 +525,11 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase { // reload the acceptor to reload the SSL stores NettyAcceptor acceptor = (NettyAcceptor) server.getRemotingService().getAcceptor("nettySSL"); - acceptor.setKeyStorePath("other-" + SERVER_SIDE_KEYSTORE); + if (useKeystoreAlias) { + acceptor.setKeyStoreParameters("other-" + SERVER_SIDE_KEYSTORE, "other-server"); + } else { + acceptor.setKeyStoreParameters("other-" + SERVER_SIDE_KEYSTORE, null); + } acceptor.reload(); // create a session with the locator which failed previously proving that the SSL stores have been reloaded @@ -990,6 +997,10 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase { params.put(TransportConstants.KEYSTORE_PATH_PROP_NAME, SERVER_SIDE_KEYSTORE); } params.put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, PASSWORD); + if (useKeystoreAlias) { + // the alias is specified when the keystore is created; see tests/security-resources/build.sh + params.put(TransportConstants.KEYSTORE_ALIAS_PROP_NAME, "server"); + } params.put(TransportConstants.HOST_PROP_NAME, "localhost"); if (cipherSuites != null) { diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverTwoWaySSLTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverTwoWaySSLTest.java index f9c9eb715b..c5799162f4 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverTwoWaySSLTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverTwoWaySSLTest.java @@ -178,6 +178,74 @@ public class CoreClientOverTwoWaySSLTest extends ActiveMQTestBase { Assert.assertEquals(text, m.getBodyBuffer().readString()); } + @Test + public void testTwoWaySSLChooseAliasPositive() throws Exception { + String text = RandomUtil.randomString(); + + tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true); + tc.getParams().put(TransportConstants.SSL_PROVIDER, clientSSLProvider); + + tc.getParams().put(TransportConstants.KEYSTORE_PROVIDER_PROP_NAME, storeProvider); + tc.getParams().put(TransportConstants.KEYSTORE_TYPE_PROP_NAME, storeType); + tc.getParams().put(TransportConstants.KEYSTORE_PATH_PROP_NAME, CLIENT_SIDE_KEYSTORE); + tc.getParams().put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, PASSWORD); + + // the alias is specified when the keystore is created; see tests/security-resources/build.sh + tc.getParams().put(TransportConstants.KEYSTORE_ALIAS_PROP_NAME, "client"); + + tc.getParams().put(TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME, storeProvider); + tc.getParams().put(TransportConstants.TRUSTSTORE_TYPE_PROP_NAME, storeType); + tc.getParams().put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, CLIENT_SIDE_TRUSTSTORE); + tc.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, PASSWORD); + + server.getRemotingService().addIncomingInterceptor(new MyInterceptor()); + + ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc)); + ClientSessionFactory sf = createSessionFactory(locator); + ClientSession session = sf.createSession(false, true, true); + session.createQueue(new QueueConfiguration(CoreClientOverTwoWaySSLTest.QUEUE).setDurable(false)); + ClientProducer producer = session.createProducer(CoreClientOverTwoWaySSLTest.QUEUE); + + ClientMessage message = createTextMessage(session, text); + producer.send(message); + + ClientConsumer consumer = session.createConsumer(CoreClientOverTwoWaySSLTest.QUEUE); + session.start(); + + ClientMessage m = consumer.receive(1000); + Assert.assertNotNull(m); + Assert.assertEquals(text, m.getBodyBuffer().readString()); + } + + @Test + public void testTwoWaySSLChooseAliasNegative() throws Exception { + + tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true); + tc.getParams().put(TransportConstants.SSL_PROVIDER, clientSSLProvider); + + tc.getParams().put(TransportConstants.KEYSTORE_PROVIDER_PROP_NAME, storeProvider); + tc.getParams().put(TransportConstants.KEYSTORE_TYPE_PROP_NAME, storeType); + tc.getParams().put(TransportConstants.KEYSTORE_PATH_PROP_NAME, CLIENT_SIDE_KEYSTORE); + tc.getParams().put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, PASSWORD); + tc.getParams().put(TransportConstants.KEYSTORE_ALIAS_PROP_NAME, RandomUtil.randomString()); + + tc.getParams().put(TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME, storeProvider); + tc.getParams().put(TransportConstants.TRUSTSTORE_TYPE_PROP_NAME, storeType); + tc.getParams().put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, CLIENT_SIDE_TRUSTSTORE); + tc.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, PASSWORD); + + server.getRemotingService().addIncomingInterceptor(new MyInterceptor()); + + ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc)); + locator.setCallTimeout(500); + try { + ClientSessionFactory sf = createSessionFactory(locator); + fail(); + } catch (ActiveMQNotConnectedException | ActiveMQConnectionTimedOutException e) { + // expected + } + } + @Test public void testTwoWaySSLVerifyClientHost() throws Exception { NettyAcceptor acceptor = (NettyAcceptor) server.getRemotingService().getAcceptor("nettySSL");