This is an automated email from the ASF dual-hosted git repository.

jbertram pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/activemq-artemis.git


The following commit(s) were added to refs/heads/main by this push:
     new 87e9b361bb ARTEMIS-3785 support specifying alias for SSL keystore
     new d8a35bf0e7 This closes #4088
87e9b361bb is described below

commit 87e9b361bb4a6d594402ae2d9ca30a67e9c2b609
Author: Justin Bertram <[email protected]>
AuthorDate: Wed May 18 22:13:10 2022 -0500

    ARTEMIS-3785 support specifying alias for SSL keystore
---
 .../core/client/ActiveMQClientMessageBundle.java   |  3 +
 .../core/remoting/impl/netty/NettyConnector.java   | 10 +++
 .../remoting/impl/netty/TransportConstants.java    |  6 ++
 .../core/remoting/impl/ssl/AliasedKeyManager.java  | 89 ++++++++++++++++++++++
 .../artemis/core/remoting/impl/ssl/SSLSupport.java | 77 +++++++++++++++++--
 .../spi/core/remoting/ssl/SSLContextConfig.java    | 24 +++++-
 .../core/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(-)

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<String, String> 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<PrivateKey, X509Certificate[]> 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<PrivateKey, X509Certificate[]> 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<PrivateKey, X509Certificate[]> 
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");

Reply via email to