From ac282f22ef26bb5ad235a1e0cc75393c5d37553a Mon Sep 17 00:00:00 2001 From: Aayush Atharva Date: Mon, 30 Mar 2026 22:03:18 +0000 Subject: [PATCH] ARTEMIS-5599 Support key password in Netty SSL --- .../remoting/impl/netty/NettyConnector.java | 10 + .../impl/netty/TransportConstants.java | 6 + .../core/remoting/impl/ssl/SSLSupport.java | 26 ++- .../core/remoting/ssl/SSLContextConfig.java | 17 +- .../remoting/impl/netty/NettyAcceptor.java | 6 + docs/user-manual/configuring-transports.adoc | 9 + ...oreClientOverOneWaySSLKeyPasswordTest.java | 174 ++++++++++++++++++ tests/security-resources/build.sh | 8 + .../server-keystore-keypass.jceks | Bin 0 -> 4211 bytes .../server-keystore-keypass.jks | Bin 0 -> 4228 bytes .../impl/ssl/SSLContextConfigTest.java | 77 +++++++- .../remoting/impl/ssl/SSLSupportTest.java | 59 ++++++ 12 files changed, 386 insertions(+), 6 deletions(-) create mode 100644 tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverOneWaySSLKeyPasswordTest.java create mode 100644 tests/security-resources/server-keystore-keypass.jceks create mode 100644 tests/security-resources/server-keystore-keypass.jks 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 d9166033607..90d34f9be29 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 @@ -241,6 +241,8 @@ public class NettyConnector extends AbstractConnector { private String keyStorePassword; + private String keyPassword; + private String keyStoreAlias; private String trustStoreProvider; @@ -418,6 +420,8 @@ public NettyConnector(final Map configuration, keyStorePassword = ConfigurationHelper.getPasswordProperty(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, TransportConstants.DEFAULT_KEYSTORE_PASSWORD, configuration, ActiveMQDefaultConfiguration.getPropMaskPassword(), ActiveMQDefaultConfiguration.getPropPasswordCodec()); + keyPassword = ConfigurationHelper.getPasswordProperty(TransportConstants.KEY_PASSWORD_PROP_NAME, TransportConstants.DEFAULT_KEY_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); @@ -456,6 +460,7 @@ public NettyConnector(final Map configuration, keyStoreType = TransportConstants.DEFAULT_KEYSTORE_TYPE; keyStorePath = TransportConstants.DEFAULT_KEYSTORE_PATH; keyStorePassword = TransportConstants.DEFAULT_KEYSTORE_PASSWORD; + keyPassword = TransportConstants.DEFAULT_KEY_PASSWORD; keyStoreAlias = TransportConstants.DEFAULT_KEYSTORE_ALIAS; crcOptions = TransportConstants.DEFAULT_CRC_OPTIONS; ocspResponderURL = TransportConstants.DEFAULT_OCSP_RESPONDER_URL; @@ -604,6 +609,7 @@ public synchronized void start() { final String realKeyStoreProvider; final String realKeyStoreType; final String realKeyStorePassword; + final String realKeyPassword; final String realKeyStoreAlias; final String realTrustStorePath; final String realTrustStoreProvider; @@ -616,6 +622,7 @@ public synchronized void start() { realKeyStoreProvider = keyStoreProvider; realKeyStoreType = keyStoreType; realKeyStorePassword = keyStorePassword; + realKeyPassword = keyPassword; realKeyStoreAlias = keyStoreAlias; realTrustStorePath = trustStorePath; realTrustStoreProvider = trustStoreProvider; @@ -630,6 +637,7 @@ public synchronized void start() { tempKeyStorePassword = processSslPasswordProperty(tempKeyStorePassword, tempPasswordCodecClass); } realKeyStorePassword = tempKeyStorePassword; + realKeyPassword = keyPassword; 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), @@ -654,6 +662,7 @@ public synchronized void start() { realKeyStoreProvider = null; realKeyStoreType = null; realKeyStorePassword = null; + realKeyPassword = null; realKeyStoreAlias = null; realTrustStorePath = null; realTrustStoreProvider = null; @@ -690,6 +699,7 @@ public void initChannel(Channel channel) throws Exception { .keystorePath(realKeyStorePath) .keystoreType(realKeyStoreType) .keystorePassword(realKeyStorePassword) + .keyPassword(realKeyPassword) .keystoreAlias(realKeyStoreAlias) .truststoreProvider(realTrustStoreProvider) .truststorePath(realTrustStorePath) 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 46d0745b33c..b92947e97d8 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 @@ -112,6 +112,8 @@ public class TransportConstants { public static final String KEYSTORE_PASSWORD_PROP_NAME = "keyStorePassword"; + public static final String KEY_PASSWORD_PROP_NAME = "keyPassword"; + public static final String KEYSTORE_ALIAS_PROP_NAME = "keyStoreAlias"; public static final String TRUSTSTORE_PROVIDER_PROP_NAME = "trustStoreProvider"; @@ -248,6 +250,8 @@ public class TransportConstants { public static final String DEFAULT_KEYSTORE_PASSWORD = null; + public static final String DEFAULT_KEY_PASSWORD = null; + public static final String DEFAULT_TRUSTSTORE_PROVIDER = null; public static final String DEFAULT_TRUSTSTORE_TYPE = "JKS"; @@ -449,6 +453,7 @@ private static int parseDefaultVariable(String variableName, int defaultValue) { allowableAcceptorKeys.add(TransportConstants.KEYSTORE_TYPE_PROP_NAME); allowableAcceptorKeys.add(TransportConstants.KEYSTORE_PATH_PROP_NAME); allowableAcceptorKeys.add(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME); + allowableAcceptorKeys.add(TransportConstants.KEY_PASSWORD_PROP_NAME); allowableAcceptorKeys.add(TransportConstants.KEYSTORE_ALIAS_PROP_NAME); allowableAcceptorKeys.add(TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME); allowableAcceptorKeys.add(TransportConstants.TRUSTSTORE_TYPE_PROP_NAME); @@ -526,6 +531,7 @@ private static int parseDefaultVariable(String variableName, int defaultValue) { allowableConnectorKeys.add(TransportConstants.KEYSTORE_TYPE_PROP_NAME); allowableConnectorKeys.add(TransportConstants.KEYSTORE_PATH_PROP_NAME); allowableConnectorKeys.add(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME); + allowableConnectorKeys.add(TransportConstants.KEY_PASSWORD_PROP_NAME); allowableConnectorKeys.add(TransportConstants.KEYSTORE_ALIAS_PROP_NAME); allowableConnectorKeys.add(TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME); allowableConnectorKeys.add(TransportConstants.TRUSTSTORE_TYPE_PROP_NAME); 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 9e8c21dc331..deb9c955ead 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 @@ -80,6 +80,7 @@ public class SSLSupport { private String keystoreType = TransportConstants.DEFAULT_KEYSTORE_TYPE; private String keystorePath = TransportConstants.DEFAULT_KEYSTORE_PATH; private String keystorePassword = TransportConstants.DEFAULT_KEYSTORE_PASSWORD; + private String keyPassword = TransportConstants.DEFAULT_KEY_PASSWORD; private String truststoreProvider = TransportConstants.DEFAULT_TRUSTSTORE_PROVIDER; private String truststoreType = TransportConstants.DEFAULT_TRUSTSTORE_TYPE; private String truststorePath = TransportConstants.DEFAULT_TRUSTSTORE_PATH; @@ -100,6 +101,7 @@ public SSLSupport(final SSLContextConfig config) { keystorePath = config.getKeystorePath(); keystoreType = config.getKeystoreType(); keystorePassword = config.getKeystorePassword(); + keyPassword = config.getKeyPassword(); truststoreProvider = config.getTruststoreProvider(); truststorePath = config.getTruststorePath(); truststoreType = config.getTruststoreType(); @@ -148,6 +150,15 @@ public SSLSupport setKeystorePassword(String keystorePassword) { return this; } + public String getKeyPassword() { + return keyPassword; + } + + public SSLSupport setKeyPassword(String keyPassword) { + this.keyPassword = keyPassword; + return this; + } + public String getKeystoreAlias() { return keystoreAlias; } @@ -262,7 +273,7 @@ public SslContext createNettyContext() throws Exception { Pair privateKeyAndCertChain = getPrivateKeyAndCertChain(keyStore); sslContextBuilder = SslContextBuilder.forServer(privateKeyAndCertChain.getA(), privateKeyAndCertChain.getB()); } else { - sslContextBuilder = SslContextBuilder.forServer(getKeyManagerFactory(keyStore, keystorePassword == null ? null : keystorePassword.toCharArray())); + sslContextBuilder = SslContextBuilder.forServer(getKeyManagerFactory(keyStore, getKeyPasswordOrDefault())); } return sslContextBuilder .sslProvider(SslProvider.valueOf(sslProvider)) @@ -280,7 +291,7 @@ public SslContext createNettyClientContext() throws Exception { Pair privateKeyAndCertChain = getPrivateKeyAndCertChain(keyStore); sslContextBuilder.keyManager(privateKeyAndCertChain.getA(), privateKeyAndCertChain.getB()); } else { - sslContextBuilder.keyManager(getKeyManagerFactory(keyStore, keystorePassword == null ? null : keystorePassword.toCharArray())); + sslContextBuilder.keyManager(getKeyManagerFactory(keyStore, getKeyPasswordOrDefault())); } return sslContextBuilder.build(); @@ -509,7 +520,7 @@ private KeyManagerFactory loadKeyManagerFactory() throws Exception { } else { KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); KeyStore ks = SSLSupport.loadKeystore(keystoreProvider, keystoreType, keystorePath, keystorePassword); - kmf.init(ks, keystorePassword == null ? null : keystorePassword.toCharArray()); + kmf.init(ks, getKeyPasswordOrDefault()); return kmf; } } @@ -545,7 +556,7 @@ private static URL findResource(final String resourceName) { } private Pair getPrivateKeyAndCertChain(KeyStore keyStore) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException { - PrivateKey key = (PrivateKey) keyStore.getKey(keystoreAlias, keystorePassword.toCharArray()); + PrivateKey key = (PrivateKey) keyStore.getKey(keystoreAlias, getKeyPasswordOrDefault()); if (key == null) { throw ActiveMQClientMessageBundle.BUNDLE.keystoreAliasNotFound(keystoreAlias, keystorePath); } @@ -562,6 +573,13 @@ private KeyManagerFactory getKeyManagerFactory(KeyStore keyStore, char[] keystor return keyManagerFactory; } + private char[] getKeyPasswordOrDefault() { + if (keyPassword != null) { + return keyPassword.toCharArray(); + } + return keystorePassword != null ? keystorePassword.toCharArray() : null; + } + /** * 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 (e.g. PKCS12, 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 34cc1557ced..ca56b4eba16 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 @@ -32,6 +32,7 @@ public static final class Builder { private String keystorePath = TransportConstants.DEFAULT_KEYSTORE_PATH; private String keystoreType = TransportConstants.DEFAULT_KEYSTORE_TYPE; private String keystorePassword = TransportConstants.DEFAULT_KEYSTORE_PASSWORD; + private String keyPassword = TransportConstants.DEFAULT_KEY_PASSWORD; private String keystoreProvider = TransportConstants.DEFAULT_KEYSTORE_PROVIDER; private String truststorePath = TransportConstants.DEFAULT_TRUSTSTORE_PATH; private String truststoreType = TransportConstants.DEFAULT_TRUSTSTORE_TYPE; @@ -55,6 +56,7 @@ public Builder from(final SSLContextConfig config) { keystorePath = config.getKeystorePath(); keystoreType = config.getKeystoreType(); keystorePassword = config.getKeystorePassword(); + keyPassword = config.getKeyPassword(); keystoreProvider = config.getKeystoreProvider(); truststorePath = config.getTruststorePath(); truststoreType = config.getTruststoreType(); @@ -70,7 +72,7 @@ public Builder from(final SSLContextConfig config) { public SSLContextConfig build() { return new SSLContextConfig( - keystoreProvider, keystorePath, keystoreType, keystorePassword, + keystoreProvider, keystorePath, keystoreType, keystorePassword, keyPassword, truststoreProvider, truststorePath, truststoreType, truststorePassword, crlPath, trustManagerFactoryPlugin, trustAll, keystoreAlias, crcOptions, ocspResponderURL ); @@ -91,6 +93,11 @@ public Builder keystorePassword(final String keystorePassword) { return this; } + public Builder keyPassword(final String keyPassword) { + this.keyPassword = keyPassword; + return this; + } + public Builder keystoreProvider(final String keystoreProvider) { this.keystoreProvider = keystoreProvider; return this; @@ -154,6 +161,7 @@ public static Builder builder() { private final String keystorePath; private final String keystoreType; private final String keystorePassword; + private final String keyPassword; private final String keystoreProvider; private final String truststorePath; private final String truststoreType; @@ -171,6 +179,7 @@ private SSLContextConfig(final String keystoreProvider, final String keystorePath, final String keystoreType, final String keystorePassword, + final String keyPassword, final String truststoreProvider, final String truststorePath, final String truststoreType, @@ -185,6 +194,7 @@ private SSLContextConfig(final String keystoreProvider, this.keystoreType = keystoreType; this.keystoreProvider = keystoreProvider; this.keystorePassword = keystorePassword; + this.keyPassword = keyPassword; this.truststorePath = truststorePath; this.truststoreType = truststoreType; this.truststorePassword = truststorePassword; @@ -233,6 +243,10 @@ public String getKeystorePassword() { return keystorePassword; } + public String getKeyPassword() { + return keyPassword; + } + public String getKeystorePath() { return keystorePath; } @@ -293,6 +307,7 @@ public String toString() { ", keystorePath=" + keystorePath + ", keystoreType=" + keystoreType + ", keystorePassword=" + (keystorePassword == null ? null : "******") + + ", keyPassword=" + (keyPassword == null ? null : "******") + ", truststoreProvider=" + truststoreProvider + ", truststorePath=" + truststorePath + ", truststoreType=" + truststoreType + 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 e4bc1ae8aaa..a44114734e5 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 @@ -173,6 +173,8 @@ public class NettyAcceptor extends AbstractAcceptor { private final String keyStorePassword; + private final String keyPassword; + private final String keystoreAlias; private final String trustStoreProvider; @@ -332,6 +334,8 @@ public NettyAcceptor(final String name, keyStorePassword = ConfigurationHelper.getPasswordProperty(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, TransportConstants.DEFAULT_KEYSTORE_PASSWORD, configuration, ActiveMQDefaultConfiguration.getPropMaskPassword(), ActiveMQDefaultConfiguration.getPropPasswordCodec()); + keyPassword = ConfigurationHelper.getPasswordProperty(TransportConstants.KEY_PASSWORD_PROP_NAME, TransportConstants.DEFAULT_KEY_PASSWORD, configuration, ActiveMQDefaultConfiguration.getPropMaskPassword(), ActiveMQDefaultConfiguration.getPropPasswordCodec()); + Pair trustStoreCompat = SSLSupport.getValidProviderAndType(ConfigurationHelper.getStringProperty(TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME, TransportConstants.DEFAULT_TRUSTSTORE_PROVIDER, configuration), ConfigurationHelper.getStringProperty(TransportConstants.TRUSTSTORE_TYPE_PROP_NAME, TransportConstants.DEFAULT_TRUSTSTORE_TYPE, configuration)); @@ -372,6 +376,7 @@ public NettyAcceptor(final String name, .keystorePath(keyStorePath) .keystoreType(keyStoreType) .keystorePassword(keyStorePassword) + .keyPassword(keyPassword) .keystoreAlias(keystoreAlias) .truststoreProvider(trustStoreProvider) .truststorePath(trustStorePath) @@ -388,6 +393,7 @@ public NettyAcceptor(final String name, keyStoreType = TransportConstants.DEFAULT_KEYSTORE_TYPE; keyStorePath = TransportConstants.DEFAULT_KEYSTORE_PATH; keyStorePassword = TransportConstants.DEFAULT_KEYSTORE_PASSWORD; + keyPassword = TransportConstants.DEFAULT_KEY_PASSWORD; keystoreAlias = TransportConstants.DEFAULT_KEYSTORE_ALIAS; trustStoreProvider = TransportConstants.DEFAULT_TRUSTSTORE_PROVIDER; trustStoreType = TransportConstants.DEFAULT_TRUSTSTORE_TYPE; diff --git a/docs/user-manual/configuring-transports.adoc b/docs/user-manual/configuring-transports.adoc index da44ba7bb18..9655f4158f6 100644 --- a/docs/user-manual/configuring-transports.adoc +++ b/docs/user-manual/configuring-transports.adoc @@ -321,6 +321,15 @@ Although this value can be configured on the server, it is downloaded and used b If the client needs to use a different password from that set on the server then it can override the server-side setting by either using the customary "javax.net.ssl.keyStorePassword" system property or the Artemis-specific "org.apache.activemq.ssl.keyStorePassword" system property. The Artemis-specific system property is useful if another component on the client is already making use of the standard Java system property. +keyPassword:: +The password used to access the private key within the keystore. +When not set (the default), `keyStorePassword` is used for both opening the keystore and accessing the private key. +This is useful when the keystore and its private key have different passwords, which is supported by JKS and JCEKS keystore types. ++ +NOTE: PKCS12 keystores do not support separate key and store passwords. ++ +Default is `null`. + keyStoreType:: The type of keystore being used. For example, `JKS`, `JCEKS`, `PKCS12`, `PEM` etc. diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverOneWaySSLKeyPasswordTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverOneWaySSLKeyPasswordTest.java new file mode 100644 index 00000000000..7c30425a060 --- /dev/null +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ssl/CoreClientOverOneWaySSLKeyPasswordTest.java @@ -0,0 +1,174 @@ +/* + * 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.tests.integration.ssl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.activemq.artemis.api.core.QueueConfiguration; +import org.apache.activemq.artemis.api.core.SimpleString; +import org.apache.activemq.artemis.api.core.TransportConfiguration; +import org.apache.activemq.artemis.api.core.client.ActiveMQClient; +import org.apache.activemq.artemis.api.core.client.ClientConsumer; +import org.apache.activemq.artemis.api.core.client.ClientMessage; +import org.apache.activemq.artemis.api.core.client.ClientProducer; +import org.apache.activemq.artemis.api.core.client.ClientSession; +import org.apache.activemq.artemis.api.core.client.ClientSessionFactory; +import org.apache.activemq.artemis.api.core.client.ServerLocator; +import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl; +import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants; +import org.apache.activemq.artemis.core.server.ActiveMQServer; +import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; +import org.apache.activemq.artemis.utils.RandomUtil; +import org.junit.jupiter.api.Test; + +/** + * Tests for the keyPassword SSL parameter which allows using a key password + * that differs from the keystore password. This is supported by JKS and JCEKS + * keystore types but not PKCS12. + * + * See the tests/security-resources/build.sh script for details on the security resources used. + */ +public class CoreClientOverOneWaySSLKeyPasswordTest extends ActiveMQTestBase { + + public static final SimpleString QUEUE = SimpleString.of("QueueOverSSLKeyPassword"); + + private static final String SERVER_SIDE_KEYSTORE = "server-keystore-keypass.jks"; + private static final String CLIENT_SIDE_TRUSTSTORE = "server-ca-truststore.jks"; + private static final String STORE_PASSWORD = "securepass"; + private static final String KEY_PASSWORD = "keypass123"; + + private ActiveMQServer server; + private TransportConfiguration tc; + + @Test + public void testOneWaySSLWithKeyPassword() throws Exception { + createSslServerWithKeyPassword(KEY_PASSWORD); + String text = RandomUtil.randomUUIDString(); + + tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true); + tc.getParams().put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, CLIENT_SIDE_TRUSTSTORE); + tc.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, STORE_PASSWORD); + + ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc)); + ClientSessionFactory sf = addSessionFactory(createSessionFactory(locator)); + ClientSession session = addClientSession(sf.createSession(false, true, true)); + session.createQueue(QueueConfiguration.of(QUEUE).setDurable(false)); + ClientProducer producer = addClientProducer(session.createProducer(QUEUE)); + + ClientMessage message = createTextMessage(session, text); + producer.send(message); + + ClientConsumer consumer = addClientConsumer(session.createConsumer(QUEUE)); + session.start(); + + ClientMessage m = consumer.receive(1000); + assertNotNull(m); + assertEquals(text, m.getBodyBuffer().readString()); + } + + @Test + public void testOneWaySSLWithWrongKeyPassword() throws Exception { + // Wrong key password causes the SSL acceptor to fail initialization; client cannot connect + createSslServerWithKeyPassword("wrongKeyPassword"); + + tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true); + tc.getParams().put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, CLIENT_SIDE_TRUSTSTORE); + tc.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, STORE_PASSWORD); + + ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc)); + try { + createSessionFactory(locator); + fail("Should have failed to connect with wrong key password"); + } catch (Exception e) { + // Expected + } + } + + @Test + public void testOneWaySSLWithKeyPasswordAndAlias() throws Exception { + createSslServerWithKeyPasswordAndAlias(KEY_PASSWORD, "server"); + String text = RandomUtil.randomUUIDString(); + + tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true); + tc.getParams().put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, CLIENT_SIDE_TRUSTSTORE); + tc.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, STORE_PASSWORD); + + ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc)); + ClientSessionFactory sf = addSessionFactory(createSessionFactory(locator)); + ClientSession session = addClientSession(sf.createSession(false, true, true)); + session.createQueue(QueueConfiguration.of(QUEUE).setDurable(false)); + ClientProducer producer = addClientProducer(session.createProducer(QUEUE)); + + ClientMessage message = createTextMessage(session, text); + producer.send(message); + + ClientConsumer consumer = addClientConsumer(session.createConsumer(QUEUE)); + session.start(); + + ClientMessage m = consumer.receive(1000); + assertNotNull(m); + assertEquals(text, m.getBodyBuffer().readString()); + } + + @Test + public void testOneWaySSLWithoutKeyPasswordFails() throws Exception { + // Without keyPassword, keystorePassword is used for the key; mismatch causes acceptor failure + createSslServerWithKeyPassword(null); + + tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true); + tc.getParams().put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, CLIENT_SIDE_TRUSTSTORE); + tc.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, STORE_PASSWORD); + + ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc)); + try { + createSessionFactory(locator); + fail("Should have failed to connect without specifying keyPassword"); + } catch (Exception e) { + // Expected + } + } + + private void createSslServerWithKeyPassword(String keyPassword) throws Exception { + createSslServerWithKeyPasswordAndAlias(keyPassword, null); + } + + private void createSslServerWithKeyPasswordAndAlias(String keyPassword, String alias) throws Exception { + Map params = new HashMap<>(); + params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true); + params.put(TransportConstants.KEYSTORE_TYPE_PROP_NAME, "JKS"); + params.put(TransportConstants.KEYSTORE_PATH_PROP_NAME, SERVER_SIDE_KEYSTORE); + params.put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, STORE_PASSWORD); + if (keyPassword != null) { + params.put(TransportConstants.KEY_PASSWORD_PROP_NAME, keyPassword); + } + if (alias != null) { + params.put(TransportConstants.KEYSTORE_ALIAS_PROP_NAME, alias); + } + params.put(TransportConstants.HOST_PROP_NAME, "localhost"); + + ConfigurationImpl config = createBasicConfig().addAcceptorConfiguration(new TransportConfiguration(NETTY_ACCEPTOR_FACTORY, params, "nettySSL")); + server = createServer(false, config); + server.start(); + waitForServerToStart(server); + tc = new TransportConfiguration(NETTY_CONNECTOR_FACTORY); + } +} diff --git a/tests/security-resources/build.sh b/tests/security-resources/build.sh index 93d6ee20596..6aa1e8ea16e 100755 --- a/tests/security-resources/build.sh +++ b/tests/security-resources/build.sh @@ -182,6 +182,14 @@ cat client-ca-cert.pem server-ca-cert.pem > client-and-server-ca-certs.pem ## a cert for the ExternalCertificateLoginModule keytool -storetype pkcs12 -keystore san-keystore.p12 -storepass $STORE_PASS -keypass $KEY_PASS -alias san-roles -genkey -keyalg "RSA" -keysize 2048 -dname "CN=ok" -validity $VALIDITY -ext "SAN=uri:urn:jaas:role:admin,uri:urn:jaas:role:view" +# Create JKS and JCEKS keystores with a different key password (for testing keyPassword parameter): +# ------------------------------------------------------------------------------------------------- +DIFFERENT_KEY_PASS=keypass123 +keytool -importkeystore -srckeystore server-keystore.p12 -destkeystore server-keystore-keypass.jks -srcstoretype pkcs12 -deststoretype jks -srcstorepass $STORE_PASS -deststorepass $STORE_PASS +keytool -keypasswd -keystore server-keystore-keypass.jks -storepass $STORE_PASS -alias server -keypass $STORE_PASS -new $DIFFERENT_KEY_PASS +keytool -importkeystore -srckeystore server-keystore.p12 -destkeystore server-keystore-keypass.jceks -srcstoretype pkcs12 -deststoretype jceks -srcstorepass $STORE_PASS -deststorepass $STORE_PASS +keytool -keypasswd -keystore server-keystore-keypass.jceks -storepass $STORE_PASS -alias server -keypass $STORE_PASS -new $DIFFERENT_KEY_PASS + # Clean up working files # ----------------------- rm -f *.crt *.csr openssl-* diff --git a/tests/security-resources/server-keystore-keypass.jceks b/tests/security-resources/server-keystore-keypass.jceks new file mode 100644 index 0000000000000000000000000000000000000000..45a35dc073ce34af7316a7a5dddb354b3d6719f3 GIT binary patch literal 4211 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 JNUoD-{R0$quzmml literal 0 HcmV?d00001 diff --git a/tests/security-resources/server-keystore-keypass.jks b/tests/security-resources/server-keystore-keypass.jks new file mode 100644 index 0000000000000000000000000000000000000000..68fcb6b7e2e3d005f1a90cf7020f4ab1a45afb90 GIT binary patch literal 4228 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 literal 0 HcmV?d00001 diff --git a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/ssl/SSLContextConfigTest.java b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/ssl/SSLContextConfigTest.java index 63a8b31f898..974cbb45b1c 100644 --- a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/ssl/SSLContextConfigTest.java +++ b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/ssl/SSLContextConfigTest.java @@ -17,8 +17,10 @@ package org.apache.activemq.artemis.tests.unit.core.remoting.impl.ssl; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.apache.activemq.artemis.spi.core.remoting.ssl.SSLContextConfig; import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; @@ -184,4 +186,77 @@ public void testBuilderFromExistingConfigWithOcspResponderURL() { assertEquals(originalConfig.getOcspResponderURL(), copiedConfig.getOcspResponderURL()); assertEquals(ocspURL, copiedConfig.getOcspResponderURL()); } -} \ No newline at end of file + + @Test + public void testKeyPasswordInBuilder() { + final String keyPassword = "keypass123"; + + SSLContextConfig config = SSLContextConfig.builder() + .keyPassword(keyPassword) + .build(); + + assertEquals(keyPassword, config.getKeyPassword()); + } + + @Test + public void testDefaultKeyPassword() { + SSLContextConfig config = SSLContextConfig.builder() + .build(); + + assertNull(config.getKeyPassword()); + } + + @Test + public void testEqualsExcludesKeyPassword() { + SSLContextConfig config1 = SSLContextConfig.builder() + .keyPassword("password1") + .build(); + + SSLContextConfig config2 = SSLContextConfig.builder() + .keyPassword("password2") + .build(); + + SSLContextConfig config3 = SSLContextConfig.builder() + .build(); + + // Passwords are intentionally excluded from equals/hashCode + assertEquals(config1, config2); + assertEquals(config1, config3); + } + + @Test + public void testToStringMasksKeyPassword() { + SSLContextConfig config = SSLContextConfig.builder() + .keyPassword("secretKeyPass") + .build(); + + String toString = config.toString(); + assertTrue(toString.contains("keyPassword=******")); + assertFalse(toString.contains("secretKeyPass")); + } + + @Test + public void testToStringKeyPasswordNull() { + SSLContextConfig config = SSLContextConfig.builder() + .build(); + + String toString = config.toString(); + assertTrue(toString.contains("keyPassword=null")); + } + + @Test + public void testBuilderFromExistingConfigWithKeyPassword() { + final String keyPassword = "keypass123"; + + SSLContextConfig originalConfig = SSLContextConfig.builder() + .keyPassword(keyPassword) + .build(); + + SSLContextConfig copiedConfig = SSLContextConfig.builder() + .from(originalConfig) + .build(); + + assertEquals(originalConfig.getKeyPassword(), copiedConfig.getKeyPassword()); + assertEquals(keyPassword, copiedConfig.getKeyPassword()); + } +} diff --git a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/ssl/SSLSupportTest.java b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/ssl/SSLSupportTest.java index 8e458896e64..2752495c764 100644 --- a/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/ssl/SSLSupportTest.java +++ b/tests/unit-tests/src/test/java/org/apache/activemq/artemis/tests/unit/core/remoting/impl/ssl/SSLSupportTest.java @@ -271,6 +271,65 @@ public void testContextWithBadTrustStorePassword() throws Exception { } } + @TestTemplate + public void testContextWithDifferentKeyPassword() throws Exception { + // JKS and JCEKS keystores support a private key password different from the store password + if (!"JKS".equals(storeType) && !"JCEKS".equals(storeType)) { + return; + } + String suffix = storeType.toLowerCase(); + new SSLSupport() + .setKeystoreProvider(storeProvider) + .setKeystoreType(storeType) + .setKeystorePath("server-keystore-keypass." + suffix) + .setKeystorePassword(keyStorePassword) + .setKeyPassword("keypass123") + .setTruststoreProvider(storeProvider) + .setTruststoreType(storeType) + .setTruststorePath(trustStorePath) + .setTruststorePassword(trustStorePassword) + .createContext(); + } + + @TestTemplate + public void testContextWithWrongKeyPassword() throws Exception { + // Verify that a wrong key password fails + if (!"JKS".equals(storeType) && !"JCEKS".equals(storeType)) { + return; + } + String suffix = storeType.toLowerCase(); + try { + new SSLSupport() + .setKeystoreProvider(storeProvider) + .setKeystoreType(storeType) + .setKeystorePath("server-keystore-keypass." + suffix) + .setKeystorePassword(keyStorePassword) + .setKeyPassword("wrongpassword") + .setTruststoreProvider(storeProvider) + .setTruststoreType(storeType) + .setTruststorePath(trustStorePath) + .setTruststorePassword(trustStorePassword) + .createContext(); + fail(); + } catch (Exception e) { + } + } + + @TestTemplate + public void testContextWithKeyPasswordFallback() throws Exception { + // When keyPassword is not set, keystorePassword is used (existing behavior) + new SSLSupport() + .setKeystoreProvider(storeProvider) + .setKeystoreType(storeType) + .setKeystorePath(keyStorePath) + .setKeystorePassword(keyStorePassword) + .setTruststoreProvider(storeProvider) + .setTruststoreType(storeType) + .setTruststorePath(trustStorePath) + .setTruststorePassword(trustStorePassword) + .createContext(); + } + @TestTemplate public void testContextWithTrustAll() throws Exception { //This is using a bad password but should not fail because the trust store should be ignored with