You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by jb...@apache.org on 2026/04/08 13:53:44 UTC
(activemq) branch activemq-6.2.x updated: Properly handle SSL handshake updates (#1886)
This is an automated email from the ASF dual-hosted git repository.
jbonofre pushed a commit to branch activemq-6.2.x
in repository https://gitbox.apache.org/repos/asf/activemq.git
The following commit(s) were added to refs/heads/activemq-6.2.x by this push:
new 32bd50fef1 Properly handle SSL handshake updates (#1886)
32bd50fef1 is described below
commit 32bd50fef1245c81ade070f23e7de1989f754b76
Author: Christopher L. Shannon <cs...@apache.org>
AuthorDate: Wed Apr 8 09:53:38 2026 -0400
Properly handle SSL handshake updates (#1886)
Handle different handshake state changes in NIOSSL transports
---
.../activemq/transport/amqp/JMSClientContext.java | 17 +++-
.../transport/amqp/JMSClientNioPlusSslTest.java | 22 +++++
.../activemq/transport/amqp/JMSClientSslTest.java | 92 ++++++++++++++++++
.../amqp/auto/JMSClientAutoNioPlusSslTest.java | 22 +++++
.../transport/nio/AutoInitNioSSLTransport.java | 25 ++++-
.../org/apache/activemq/util/NioSslTestUtil.java | 57 +++++++++++
.../activemq/transport/nio/NIOSSLTransport.java | 64 ++++++++++++-
.../activemq/transport/mqtt/MQTTNIOSSLTest.java | 11 +++
.../apache/activemq/transport/mqtt/MQTTTest.java | 71 ++++++++++++++
.../activemq/transport/mqtt/MQTTTestSupport.java | 10 +-
.../transport/mqtt/auto/MQTTAutoNioSslTest.java | 11 +++
.../activemq/transport/stomp/StompNIOSSLTest.java | 6 +-
.../activemq/transport/stomp/StompSslAuthTest.java | 6 +-
.../activemq/transport/stomp/StompSslTest.java | 6 +-
.../apache/activemq/transport/stomp/StompTest.java | 104 ++++++++++++++++++++-
.../activemq/transport/stomp/StompTestSupport.java | 33 ++++++-
.../transport/stomp/auto/StompAutoNioSslTest.java | 8 +-
.../transport/stomp/auto/StompAutoSslTest.java | 6 +-
.../transport/TransportBrokerTestSupport.java | 74 +++++++++++++++
.../auto/AutoNIOSslTransportBrokerTest.java | 37 +++++++-
.../transport/auto/AutoSslTransportBrokerTest.java | 12 ++-
.../activemq/transport/nio/NIOSSLBasicTest.java | 8 +-
.../transport/nio/NIOSSLTransportBrokerTest.java | 12 ++-
.../transport/tcp/SslTransportBrokerTest.java | 12 ++-
24 files changed, 683 insertions(+), 43 deletions(-)
diff --git a/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSClientContext.java b/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSClientContext.java
index df6a9ce232..dfe650680d 100644
--- a/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSClientContext.java
+++ b/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSClientContext.java
@@ -59,7 +59,12 @@ public class JMSClientContext {
}
public Connection createConnection(URI remoteURI, String username, String password, String clientId, boolean syncPublish) throws JMSException {
- ConnectionFactory factory = createConnectionFactory(remoteURI, username, password, syncPublish);
+ return createConnection(remoteURI, username, password, clientId, syncPublish, null);
+ }
+
+ public Connection createConnection(URI remoteURI, String username, String password, String clientId, boolean syncPublish,
+ String sslProtocol) throws JMSException {
+ ConnectionFactory factory = createConnectionFactory(remoteURI, username, password, syncPublish, sslProtocol);
Connection connection = factory.createConnection();
connection.setExceptionListener(new ExceptionListener() {
@@ -166,7 +171,12 @@ public class JMSClientContext {
}
private ConnectionFactory createConnectionFactory(
- URI remoteURI, String username, String password, boolean syncPublish) {
+ URI remoteURI, String username, String password, boolean syncPublish) {
+ return createConnectionFactory(remoteURI, username, password, syncPublish, null);
+ }
+
+ private ConnectionFactory createConnectionFactory(
+ URI remoteURI, String username, String password, boolean syncPublish, String sslProtocol) {
String clientScheme;
boolean useSSL = false;
@@ -204,6 +214,9 @@ public class JMSClientContext {
if (useSSL) {
amqpURI += "?transport.verifyHost=false";
+ if (sslProtocol != null) {
+ amqpURI += "&transport.enabledProtocols=" + sslProtocol;
+ }
}
LOG.debug("In createConnectionFactory using URI: {}", amqpURI);
diff --git a/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSClientNioPlusSslTest.java b/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSClientNioPlusSslTest.java
index f13884d282..c718fb8447 100644
--- a/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSClientNioPlusSslTest.java
+++ b/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSClientNioPlusSslTest.java
@@ -16,8 +16,12 @@
*/
package org.apache.activemq.transport.amqp;
+import jakarta.jms.Connection;
+import jakarta.jms.JMSException;
import java.net.URI;
+import java.net.URISyntaxException;
+import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,6 +36,13 @@ public class JMSClientNioPlusSslTest extends JMSClientSslTest {
return amqpNioPlusSslURI;
}
+ protected Connection createConnection(String clientId, boolean syncPublish) throws JMSException {
+ Connection connection = JMSClientContext.INSTANCE.createConnection(getBrokerURI(), "admin", "password", clientId, syncPublish,
+ enabledProtocols);
+ connection.start();
+ return connection;
+ }
+
@Override
protected boolean isUseTcpConnector() {
return false;
@@ -46,4 +57,15 @@ public class JMSClientNioPlusSslTest extends JMSClientSslTest {
protected String getTargetConnectorName() {
return "amqp+nio+ssl";
}
+
+ @Test(timeout=30000)
+ public void testSslHandshakeRenegotiationTlsv12() throws Exception {
+ testSslHandshakeRenegotiation("TLSv1.2");
+ }
+
+ @Test(timeout=30000)
+ public void testSslHandshakeRenegotiationTlsv13() throws Exception {
+ testSslHandshakeRenegotiation("TLSv1.3");
+ }
+
}
diff --git a/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSClientSslTest.java b/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSClientSslTest.java
index b0bd3a7f59..d8372cffd2 100644
--- a/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSClientSslTest.java
+++ b/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/JMSClientSslTest.java
@@ -16,8 +16,29 @@
*/
package org.apache.activemq.transport.amqp;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import io.netty.channel.Channel;
+import io.netty.handler.ssl.SslHandler;
+import jakarta.jms.Message;
+import jakarta.jms.MessageConsumer;
+import jakarta.jms.MessageProducer;
+import jakarta.jms.Queue;
+import jakarta.jms.Session;
+import jakarta.jms.TextMessage;
+import java.lang.reflect.Field;
+import java.net.InetSocketAddress;
import java.net.URI;
+import org.apache.activemq.broker.TransportConnector;
+import org.apache.activemq.transport.amqp.joram.ActiveMQAdmin;
+import org.apache.activemq.util.NioSslTestUtil;
+import org.apache.qpid.jms.JmsConnection;
+import org.apache.qpid.jms.provider.amqp.AmqpProvider;
+import org.apache.qpid.jms.transports.netty.NettyTcpTransport;
+import org.objectweb.jtests.jms.framework.TestConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -27,6 +48,16 @@ import org.slf4j.LoggerFactory;
public class JMSClientSslTest extends JMSClientTest {
protected static final Logger LOG = LoggerFactory.getLogger(JMSClientSslTest.class);
+ protected String enabledProtocols = null;
+
+
+ @Override
+ public void setUp() throws Exception {
+ enabledProtocols = null;
+ super.setUp();
+ }
+
+
@Override
protected URI getBrokerURI() {
return amqpSslURI;
@@ -46,4 +77,65 @@ public class JMSClientSslTest extends JMSClientTest {
protected String getTargetConnectorName() {
return "amqp+ssl";
}
+
+ protected void testSslHandshakeRenegotiation(String protocol) throws Exception {
+ enabledProtocols = protocol;
+
+ ActiveMQAdmin.enableJMSFrameTracing();
+
+ connection = createConnection();
+
+ JmsConnection jmsCon = (JmsConnection) connection;
+ NettyTcpTransport transport = getNettyTransport(jmsCon);
+ Channel channel = getNettyChannel(transport);
+ SslHandler sslHandler = channel.pipeline().get(SslHandler.class);
+ assertEquals(protocol, sslHandler.engine().getSession().getProtocol());
+
+ // trigger handshakes
+ for (int i = 0; i < 10; i++) {
+ sslHandler.engine().beginHandshake();
+ }
+
+ // give some time for the handshake updates
+ Thread.sleep(100);
+
+ // check status advances if NIOSSL, then continue
+ // below to verify transports are not stuck
+ checkHandshakeStatusAdvances(((InetSocketAddress)channel.localAddress()).getPort());
+
+ // Make sure messages still work
+ Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ Queue queue = session.createQueue(getDestinationName());
+ MessageProducer p = session.createProducer(queue);
+
+ TextMessage message = session.createTextMessage();
+ message.setText("hello");
+ p.send(message);
+
+ MessageConsumer consumer = session.createConsumer(queue);
+ Message msg = consumer.receive(100);
+ assertNotNull(msg);
+ assertTrue(msg instanceof TextMessage);
+
+ }
+
+ // This only applies to NIO SSL
+ protected void checkHandshakeStatusAdvances(int localPort) throws Exception {
+ TransportConnector connector = brokerService.getTransportConnectorByScheme(getBrokerURI().getScheme());
+ NioSslTestUtil.checkHandshakeStatusAdvances(connector, localPort);
+ }
+
+ private NettyTcpTransport getNettyTransport(JmsConnection jmsCon) throws Exception {
+ Field providerField = JmsConnection.class.getDeclaredField("provider");
+ providerField.setAccessible(true);
+ AmqpProvider provider = (AmqpProvider) providerField.get(jmsCon);
+ return (NettyTcpTransport) provider.getTransport();
+ }
+
+ private Channel getNettyChannel(NettyTcpTransport transport) throws Exception {
+ Field channelField = NettyTcpTransport.class.getDeclaredField("channel");
+ channelField.setAccessible(true);
+ return (Channel) channelField.get(transport);
+ }
+
}
diff --git a/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/auto/JMSClientAutoNioPlusSslTest.java b/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/auto/JMSClientAutoNioPlusSslTest.java
index 127c6d6d89..90e8fd0fb6 100644
--- a/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/auto/JMSClientAutoNioPlusSslTest.java
+++ b/activemq-amqp/src/test/java/org/apache/activemq/transport/amqp/auto/JMSClientAutoNioPlusSslTest.java
@@ -16,9 +16,13 @@
*/
package org.apache.activemq.transport.amqp.auto;
+import jakarta.jms.Connection;
+import jakarta.jms.JMSException;
import java.net.URI;
+import org.apache.activemq.transport.amqp.JMSClientContext;
import org.apache.activemq.transport.amqp.JMSClientSslTest;
+import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -33,6 +37,13 @@ public class JMSClientAutoNioPlusSslTest extends JMSClientSslTest {
return autoNioPlusSslURI;
}
+ protected Connection createConnection(String clientId, boolean syncPublish) throws JMSException {
+ Connection connection = JMSClientContext.INSTANCE.createConnection(getBrokerURI(), "admin", "password", clientId, syncPublish,
+ enabledProtocols);
+ connection.start();
+ return connection;
+ }
+
@Override
protected boolean isUseTcpConnector() {
return false;
@@ -47,4 +58,15 @@ public class JMSClientAutoNioPlusSslTest extends JMSClientSslTest {
protected String getTargetConnectorName() {
return "auto+nio+ssl";
}
+
+ @Test(timeout=30000)
+ public void testSslHandshakeRenegotiationTlsv12() throws Exception {
+ testSslHandshakeRenegotiation("TLSv1.2");
+ }
+
+ @Test(timeout=30000)
+ public void testSslHandshakeRenegotiationTlsv13() throws Exception {
+ testSslHandshakeRenegotiation("TLSv1.3");
+ }
+
}
diff --git a/activemq-broker/src/main/java/org/apache/activemq/transport/nio/AutoInitNioSSLTransport.java b/activemq-broker/src/main/java/org/apache/activemq/transport/nio/AutoInitNioSSLTransport.java
index b16d5575f9..06aa467771 100644
--- a/activemq-broker/src/main/java/org/apache/activemq/transport/nio/AutoInitNioSSLTransport.java
+++ b/activemq-broker/src/main/java/org/apache/activemq/transport/nio/AutoInitNioSSLTransport.java
@@ -171,7 +171,24 @@ public class AutoInitNioSSLTransport extends NIOSSLTransport {
if (!plain.hasRemaining()) {
int readCount = secureRead(plain);
- if (readCount == 0) {
+ /*
+ * 1) If data is read, continue below to the processCommand() call
+ * and handle processing the data in the buffer. This takes priority
+ * and some handshake status updates (like NEED_WRAP) can be handled
+ * concurrently with application data (like TLSv1.3 key updates)
+ * when the broker sends data to a client.
+ *
+ * 2) If no data is read, it's possible that the connection is waiting
+ * for us to process a handshake update (either KeyUpdate for
+ * TLS1.3 or renegotiation for TLSv1.2) so we need to check and process
+ * any handshake updates. If the handshake status was updated,
+ * we want to continue and loop again to recheck if we can now read new
+ * application data into the buffer after processing the updates.
+ *
+ * 3) If no data is read, and no handshake update is needed, then we
+ * are finished and can break.
+ */
+ if (readCount == 0 && !handleHandshakeUpdate()) {
break;
}
@@ -184,7 +201,11 @@ public class AutoInitNioSSLTransport extends NIOSSLTransport {
receiveCounter.addAndGet(readCount);
}
- if (status == SSLEngineResult.Status.OK && handshakeStatus != SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
+ // Try and process commands if there is any data in plain if status is OK
+ // Handshake renegotiation can happen concurrently with application data reads
+ // so it's possible to have read data that needs processing even if the
+ // handshake status indicates NEED_UNWRAP
+ if (status == SSLEngineResult.Status.OK && plain.hasRemaining()) {
processCommand(plain);
//we have received enough bytes to detect the protocol
if (receiveCounter.get() >= 8) {
diff --git a/activemq-broker/src/test/java/org/apache/activemq/util/NioSslTestUtil.java b/activemq-broker/src/test/java/org/apache/activemq/util/NioSslTestUtil.java
new file mode 100644
index 0000000000..4488d606f8
--- /dev/null
+++ b/activemq-broker/src/test/java/org/apache/activemq/util/NioSslTestUtil.java
@@ -0,0 +1,57 @@
+/*
+ * 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.util;
+
+import static org.junit.Assert.assertTrue;
+
+import java.lang.reflect.Field;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLSocket;
+import org.apache.activemq.broker.TransportConnection;
+import org.apache.activemq.broker.TransportConnector;
+import org.apache.activemq.transport.nio.NIOSSLTransport;
+
+public class NioSslTestUtil {
+
+ public static void checkHandshakeStatusAdvances(TransportConnector connector, SSLSocket socket) throws Exception {
+ checkHandshakeStatusAdvances(connector, socket.getLocalPort(), 10000, 10);
+ }
+
+ public static void checkHandshakeStatusAdvances(TransportConnector connector, int localPort) throws Exception {
+ checkHandshakeStatusAdvances(connector, localPort, 10000, 10);
+ }
+
+ public static void checkHandshakeStatusAdvances(TransportConnector connector, int localPort,
+ long duration, long sleepMillis) throws Exception {
+
+ TransportConnection con = connector.getConnections().stream()
+ .filter(tc -> tc.getRemoteAddress().contains(
+ Integer.toString(localPort))).findFirst().orElseThrow();
+
+ Field field = NIOSSLTransport.class.getDeclaredField("handshakeStatus");
+ field.setAccessible(true);
+ NIOSSLTransport t = con.getTransport().narrow(NIOSSLTransport.class);
+ // If this is the NIOSSLTransport then verify we exit NEED_WRAP and NEED_TASK
+ if (t != null) {
+ assertTrue(Wait.waitFor(() -> {
+ SSLEngineResult.HandshakeStatus status = (SSLEngineResult.HandshakeStatus) field.get(t);
+ return status != HandshakeStatus.NEED_WRAP && status != HandshakeStatus.NEED_TASK;
+ }, duration, sleepMillis));
+ }
+ }
+}
diff --git a/activemq-client/src/main/java/org/apache/activemq/transport/nio/NIOSSLTransport.java b/activemq-client/src/main/java/org/apache/activemq/transport/nio/NIOSSLTransport.java
index 3ba064d13d..606503288c 100644
--- a/activemq-client/src/main/java/org/apache/activemq/transport/nio/NIOSSLTransport.java
+++ b/activemq-client/src/main/java/org/apache/activemq/transport/nio/NIOSSLTransport.java
@@ -36,6 +36,7 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLEngineResult.Status;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
@@ -271,10 +272,26 @@ public class NIOSSLTransport extends NIOTransport {
}
if (!plain.hasRemaining()) {
-
int readCount = secureRead(plain);
- if (readCount == 0) {
+ /*
+ * 1) If data is read, continue below to the processCommand() call
+ * and handle processing the data in the buffer. This takes priority
+ * and some handshake status updates (like NEED_WRAP) can be handled
+ * concurrently with application data (like TLSv1.3 key updates)
+ * when the broker sends data to a client.
+ *
+ * 2) If no data is read, it's possible that the connection is waiting
+ * for us to process a handshake update (either KeyUpdate for
+ * TLS1.3 or renegotiation for TLSv1.2) so we need to check and process
+ * any handshake updates. If the handshake status was updated,
+ * we want to continue and loop again to recheck if we can now read new
+ * application data into the buffer after processing the updates.
+ *
+ * 3) If no data is read, and no handshake update is needed, then we
+ * are finished and can break.
+ */
+ if (readCount == 0 && !handleHandshakeUpdate()) {
break;
}
@@ -288,7 +305,11 @@ public class NIOSSLTransport extends NIOTransport {
receiveCounter.addAndGet(readCount);
}
- if (status == SSLEngineResult.Status.OK && handshakeStatus != SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
+ // Try and process commands if there is any data in plain if status is OK.
+ // Handshake renegotiation can happen concurrently with application data reads,
+ // so it's possible to have data in the buffer that needs processing even if the
+ // handshake status indicates NEED_UNWRAP
+ if (status == SSLEngineResult.Status.OK && plain.hasRemaining()) {
processCommand(plain);
}
}
@@ -431,6 +452,43 @@ public class NIOSSLTransport extends NIOTransport {
return plain.remaining();
}
+ /**
+ * Checks if the connection is waiting for the broker to process an update
+ * to continue with handshaking and processes the necessary updates
+ *
+ * @return True if the handshake status was updated
+ * @throws IOException if an error occurs during processing
+ */
+ protected boolean handleHandshakeUpdate() throws IOException {
+ switch (handshakeStatus) {
+ // For NEED_WRAP, call write with a 0 size buffer which will flush any
+ // pending handshaking updates even if there is no application data to write
+ case NEED_WRAP:
+ ((NIOOutputStream) buffOut).write(ByteBuffer.allocate(0));
+ handshakeStatus = sslEngine.getHandshakeStatus();
+ return true;
+ // For NEED_TASK, process any outstanding tasks needed to continue the
+ // handshake negotiation
+ case NEED_TASK:
+ Runnable task;
+ while ((task = sslEngine.getDelegatedTask()) != null) {
+ task.run();
+ }
+ handshakeStatus = sslEngine.getHandshakeStatus();
+ return true;
+ // We need to update the session when finished, and then
+ // we can update the handshake status which will advance to
+ // NOT_HANDSHAKING before we continue and try and read
+ // more data
+ case FINISHED:
+ sslSession = sslEngine.getSession();
+ handshakeStatus = sslEngine.getHandshakeStatus();
+ return true;
+ default:
+ return false;
+ }
+ }
+
protected void doHandshake() throws Exception {
handshakeInProgress = true;
Selector selector = null;
diff --git a/activemq-mqtt/src/test/java/org/apache/activemq/transport/mqtt/MQTTNIOSSLTest.java b/activemq-mqtt/src/test/java/org/apache/activemq/transport/mqtt/MQTTNIOSSLTest.java
index b6dd9f9158..c5c397f921 100644
--- a/activemq-mqtt/src/test/java/org/apache/activemq/transport/mqtt/MQTTNIOSSLTest.java
+++ b/activemq-mqtt/src/test/java/org/apache/activemq/transport/mqtt/MQTTNIOSSLTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.activemq.transport.mqtt;
+import org.junit.Test;
/**
* Run the basic tests with the NIO Transport.
*/
@@ -30,4 +31,14 @@ public class MQTTNIOSSLTest extends MQTTTest {
public boolean isUseSSL() {
return true;
}
+
+ @Test(timeout = 60000)
+ public void testHandshakeRenegotiationTlsv12() throws Exception {
+ testHandshakeRenegotiation("TLSv1.2");
+ }
+
+ @Test(timeout = 60000)
+ public void testHandshakeRenegotiationTlsv13() throws Exception {
+ testHandshakeRenegotiation("TLSv1.3");
+ }
}
diff --git a/activemq-mqtt/src/test/java/org/apache/activemq/transport/mqtt/MQTTTest.java b/activemq-mqtt/src/test/java/org/apache/activemq/transport/mqtt/MQTTTest.java
index 7cf1523bee..1bd7078e3e 100644
--- a/activemq-mqtt/src/test/java/org/apache/activemq/transport/mqtt/MQTTTest.java
+++ b/activemq-mqtt/src/test/java/org/apache/activemq/transport/mqtt/MQTTTest.java
@@ -25,6 +25,9 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.net.InetSocketAddress;
import java.net.ProtocolException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -46,8 +49,11 @@ import jakarta.jms.Queue;
import jakarta.jms.Session;
import jakarta.jms.TextMessage;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSocket;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
+import org.apache.activemq.broker.TransportConnector;
import org.apache.activemq.broker.region.policy.LastImageSubscriptionRecoveryPolicy;
import org.apache.activemq.broker.region.policy.PolicyEntry;
import org.apache.activemq.broker.region.policy.PolicyMap;
@@ -55,8 +61,12 @@ import org.apache.activemq.broker.region.policy.RetainedMessageSubscriptionRecov
import org.apache.activemq.command.ActiveMQMessage;
import org.apache.activemq.command.ActiveMQTopic;
import org.apache.activemq.util.ByteSequence;
+import org.apache.activemq.util.NioSslTestUtil;
import org.apache.activemq.util.Wait;
+import org.fusesource.hawtdispatch.transport.SslTransport;
import org.fusesource.mqtt.client.BlockingConnection;
+import org.fusesource.mqtt.client.CallbackConnection;
+import org.fusesource.mqtt.client.FutureConnection;
import org.fusesource.mqtt.client.MQTT;
import org.fusesource.mqtt.client.Message;
import org.fusesource.mqtt.client.QoS;
@@ -64,6 +74,7 @@ import org.fusesource.mqtt.client.Topic;
import org.fusesource.mqtt.client.Tracer;
import org.fusesource.mqtt.codec.MQTTFrame;
import org.fusesource.mqtt.codec.PUBLISH;
+import org.junit.Assume;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -1993,4 +2004,64 @@ public class MQTTTest extends MQTTTestSupport {
}
}
}
+
+ protected void testHandshakeRenegotiation(String protocol) throws Exception {
+ MQTT mqtt = createMQTTSslConnection(null, true, protocol);
+ mqtt.setClientId("");
+ mqtt.setCleanSession(true);
+
+ CallbackConnection callbackConnection = mqtt.callbackConnection();
+ BlockingConnection connection = new BlockingConnection(new FutureConnection(callbackConnection));
+ connection.connect();
+
+ SslTransport transport = getSslTransport(callbackConnection);
+ SSLEngine engine = getSslTransport(transport);
+ assertEquals(protocol, engine.getSession().getProtocol());
+
+ // Run 100 key updates in a loop so that we can
+ // verify that the transport correctly processes them
+ // and that we are not stuck in NEED_WRAP state. This
+ // only applies to NIO, for regular SSL the state is not
+ // handled by the transport
+ for (int i = 0; i < 100; i++) {
+ try {
+ engine.beginHandshake();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ // give some time for the handshake updates
+ Thread.sleep(100);
+
+ // Wait to get past NEED_WRAP as that indicates we correctly handled
+ // the key updates issued for TLSv1.3 renegotiation
+ checkHandshakeStatusAdvances(((InetSocketAddress)transport.getLocalAddress()).getPort());
+
+ // Make sure we can still subscribe and receive
+ connection.subscribe(new Topic[] { new Topic("topic1", QoS.AT_LEAST_ONCE) });
+ connection.publish("topic1", "topic".getBytes(), QoS.AT_LEAST_ONCE, false);
+ assertNotNull(connection.receive(2000, TimeUnit.MILLISECONDS));
+
+ connection.disconnect();
+ }
+
+ private void checkHandshakeStatusAdvances(int port) throws Exception {
+ TransportConnector connector = brokerService.getTransportConnectorByScheme(
+ getProtocolScheme());
+ NioSslTestUtil.checkHandshakeStatusAdvances(connector, port);
+ }
+
+ private static SslTransport getSslTransport(CallbackConnection callbackConnection) throws Exception {
+ Field transportField = CallbackConnection.class.getDeclaredField("transport");
+ transportField.setAccessible(true);
+ return (SslTransport) transportField.get(callbackConnection);
+ }
+
+ private static SSLEngine getSslTransport(SslTransport transport) throws Exception {
+ Field engineField = SslTransport.class.getDeclaredField("engine");
+ engineField.setAccessible(true);
+ return (SSLEngine) engineField.get(transport);
+ }
+
}
diff --git a/activemq-mqtt/src/test/java/org/apache/activemq/transport/mqtt/MQTTTestSupport.java b/activemq-mqtt/src/test/java/org/apache/activemq/transport/mqtt/MQTTTestSupport.java
index 881ad63424..3ad4e06c78 100644
--- a/activemq-mqtt/src/test/java/org/apache/activemq/transport/mqtt/MQTTTestSupport.java
+++ b/activemq-mqtt/src/test/java/org/apache/activemq/transport/mqtt/MQTTTestSupport.java
@@ -103,6 +103,7 @@ public class MQTTTestSupport {
System.setProperty("javax.net.ssl.keyStoreType", "jks");
exceptions.clear();
+
startBroker();
}
@@ -413,7 +414,11 @@ public class MQTTTestSupport {
return mqtt;
}
- private MQTT createMQTTSslConnection(String clientId, boolean clean) throws Exception {
+ protected MQTT createMQTTSslConnection(String clientId, boolean clean) throws Exception {
+ return createMQTTSslConnection(clientId, clean, null);
+ }
+
+ protected MQTT createMQTTSslConnection(String clientId, boolean clean, String sslProtocol) throws Exception {
MQTT mqtt = new MQTT();
mqtt.setConnectAttemptsMax(1);
mqtt.setReconnectAttemptsMax(0);
@@ -433,6 +438,9 @@ public class MQTTTestSupport {
sslContext.setKeyStorePassword("password");
sslContext.setTrustStore(trustStore.getCanonicalPath());
sslContext.setTrustStorePassword("password");
+ if (sslProtocol != null) {
+ sslContext.setProtocol(sslProtocol);
+ }
sslContext.afterPropertiesSet();
mqtt.setSslContext(sslContext.getSSLContext());
diff --git a/activemq-mqtt/src/test/java/org/apache/activemq/transport/mqtt/auto/MQTTAutoNioSslTest.java b/activemq-mqtt/src/test/java/org/apache/activemq/transport/mqtt/auto/MQTTAutoNioSslTest.java
index e777385f1b..ac11c31c4c 100644
--- a/activemq-mqtt/src/test/java/org/apache/activemq/transport/mqtt/auto/MQTTAutoNioSslTest.java
+++ b/activemq-mqtt/src/test/java/org/apache/activemq/transport/mqtt/auto/MQTTAutoNioSslTest.java
@@ -17,6 +17,7 @@
package org.apache.activemq.transport.mqtt.auto;
import org.apache.activemq.transport.mqtt.MQTTTest;
+import org.junit.Test;
/**
* Run the basic tests with the NIO Transport.
@@ -32,4 +33,14 @@ public class MQTTAutoNioSslTest extends MQTTTest {
public boolean isUseSSL() {
return true;
}
+
+ @Test(timeout = 60000)
+ public void testHandshakeRenegotiationTlsv12() throws Exception {
+ testHandshakeRenegotiation("TLSv1.2");
+ }
+
+ @Test(timeout = 60000)
+ public void testHandshakeRenegotiationTlsv13() throws Exception {
+ testHandshakeRenegotiation("TLSv1.3");
+ }
}
diff --git a/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompNIOSSLTest.java b/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompNIOSSLTest.java
index 836657c4c9..e54a9d8293 100644
--- a/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompNIOSSLTest.java
+++ b/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompNIOSSLTest.java
@@ -19,9 +19,6 @@ package org.apache.activemq.transport.stomp;
import java.io.IOException;
import java.net.Socket;
-import javax.net.SocketFactory;
-import javax.net.ssl.SSLSocketFactory;
-
public class StompNIOSSLTest extends StompTest {
@Override
@@ -36,7 +33,6 @@ public class StompNIOSSLTest extends StompTest {
@Override
protected Socket createSocket() throws IOException {
- SocketFactory factory = SSLSocketFactory.getDefault();
- return factory.createSocket("127.0.0.1", this.nioSslPort);
+ return createSslSocket("127.0.0.1", this.nioSslPort, this.clientSslProtocol);
}
}
diff --git a/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompSslAuthTest.java b/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompSslAuthTest.java
index 03c24c436b..02c561630f 100644
--- a/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompSslAuthTest.java
+++ b/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompSslAuthTest.java
@@ -19,9 +19,6 @@ package org.apache.activemq.transport.stomp;
import java.io.IOException;
import java.net.Socket;
-import javax.net.SocketFactory;
-import javax.net.ssl.SSLSocketFactory;
-
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.broker.BrokerPlugin;
import org.apache.activemq.broker.TransportConnector;
@@ -48,8 +45,7 @@ public class StompSslAuthTest extends StompTest {
@Override
protected Socket createSocket() throws IOException {
- SocketFactory factory = SSLSocketFactory.getDefault();
- return factory.createSocket("127.0.0.1", this.sslPort);
+ return createSslSocket("127.0.0.1", this.sslPort, this.clientSslProtocol);
}
@Override
diff --git a/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompSslTest.java b/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompSslTest.java
index 1e9b66cb76..f02ebb16fd 100644
--- a/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompSslTest.java
+++ b/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompSslTest.java
@@ -19,9 +19,6 @@ package org.apache.activemq.transport.stomp;
import java.io.IOException;
import java.net.Socket;
-import javax.net.SocketFactory;
-import javax.net.ssl.SSLSocketFactory;
-
public class StompSslTest extends StompTest {
@Override
@@ -36,7 +33,6 @@ public class StompSslTest extends StompTest {
@Override
protected Socket createSocket() throws IOException {
- SocketFactory factory = SSLSocketFactory.getDefault();
- return factory.createSocket("127.0.0.1", this.sslPort);
+ return createSslSocket("127.0.0.1", this.sslPort, this.clientSslProtocol);
}
}
diff --git a/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompTest.java b/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompTest.java
index e93df5a4d2..4c9c2484a7 100644
--- a/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompTest.java
+++ b/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompTest.java
@@ -26,6 +26,7 @@ import static org.junit.Assert.fail;
import jakarta.jms.Destination;
import java.io.IOException;
import java.io.StringReader;
+import java.lang.reflect.Field;
import java.net.SocketTimeoutException;
import java.util.Arrays;
import java.util.HashMap;
@@ -52,7 +53,12 @@ import jakarta.jms.Session;
import jakarta.jms.TextMessage;
import javax.management.ObjectName;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLSocket;
import org.apache.activemq.broker.BrokerService;
+import org.apache.activemq.broker.TransportConnection;
+import org.apache.activemq.broker.TransportConnector;
import org.apache.activemq.broker.jmx.BrokerViewMBean;
import org.apache.activemq.broker.jmx.QueueViewMBean;
import org.apache.activemq.broker.region.AbstractSubscription;
@@ -64,7 +70,10 @@ import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.ActiveMQMessage;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTextMessage;
+import org.apache.activemq.transport.nio.NIOSSLTransport;
+import org.apache.activemq.util.NioSslTestUtil;
import org.apache.activemq.util.Wait;
+import org.junit.Assume;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -82,6 +91,7 @@ public class StompTest extends StompTestSupport {
protected Session session;
protected ActiveMQQueue queue;
protected XStream xstream;
+ protected String clientSslProtocol;
private final String xmlObject = "<pojo>\n"
+ " <name>Dejan</name>\n"
@@ -137,6 +147,7 @@ public class StompTest extends StompTestSupport {
queue = new ActiveMQQueue(getQueueName());
super.setUp();
+ clientSslProtocol = null;
stompConnect();
connection = cf.createConnection("system", "manager");
@@ -2523,6 +2534,97 @@ public class StompTest extends StompTestSupport {
}
+ // Verify that full handshake renegotiation is handled by both SSL
+ // and NIO ssl transports and we don't get stuck. This is primarily
+ // to test NIO handles NEED_TASK but also double checks SSL works
+ @Test(timeout = 60000)
+ public void testHandshakeRenegotiationTlsv12() throws Exception {
+ Assume.assumeTrue(stompConnection.getStompSocket() instanceof SSLSocket);
+
+ // Reset
+ clientSslProtocol = "TLSv1.2";
+ stompDisconnect();
+ stompConnect();
+
+ connectForSslHandshakeTest();
+
+ SSLSocket socket = (SSLSocket) stompConnection.getStompSocket();
+ assertEquals("TLSv1.2", socket.getSession().getProtocol());
+
+ socket.startHandshake();
+ Thread.sleep(100);
+
+ // Wait to get past NEED_TASK as that indicates we correctly handled
+ // the tasks issued for TLSv1.2 renegotiation
+ checkHandshakeStatusAdvances(socket);
+
+ receiveForSslHandshakeTest();
+ }
+
+ // Verify that TLS1.3 KeyUpdates during handshake renegotiation will be processed
+ // by the server correctly
+ @Test(timeout = 60000)
+ public void testHandshakeRenegotiationTlsv13() throws Exception {
+ Assume.assumeTrue(stompConnection.getStompSocket() instanceof SSLSocket);
+
+ // Set to TLSv1.3
+ clientSslProtocol = "TLSv1.3";
+ stompDisconnect();
+ stompConnect();
+
+ connectForSslHandshakeTest();
+
+ SSLSocket socket = (SSLSocket) stompConnection.getStompSocket();
+ assertEquals("TLSv1.3", socket.getSession().getProtocol());
+
+ // Run 100 key updates in a loop so that we can
+ // verify that the transport correctly processes them
+ // and that we are not stuck in NEED_WRAP state. This
+ // only applies to NIO, for regular SSL the state is not
+ // handled by the transport
+ for (int i = 0; i < 100; i++) {
+ try {
+ socket.startHandshake();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ // give some time for the handshake updates
+ Thread.sleep(100);
+
+ // Wait to get past NEED_WRAP as that indicates we correctly handled
+ // the key updates issued for TLSv1.3 renegotiation
+ checkHandshakeStatusAdvances(socket);
+
+ // Make sure we can still subscribe and receive
+ receiveForSslHandshakeTest();
+ }
+
+ private void checkHandshakeStatusAdvances(SSLSocket socket) throws Exception {
+ TransportConnector connector = brokerService.getTransportConnectorByName(transportConnectorName);
+ NioSslTestUtil.checkHandshakeStatusAdvances(connector, socket);
+ }
+
+ private void connectForSslHandshakeTest() throws Exception {
+ String frame = "CONNECT\n" + "login:system\n" + "passcode:manager\n\n" + Stomp.NULL;
+ stompConnection.sendFrame(frame);
+ frame = stompConnection.receiveFrame();
+ assertTrue(frame.startsWith("CONNECTED"));
+ frame = "SEND\n" + "destination:/queue/" + getQueueName() + "\ncontent-length:5" + " \n\n" + "\u0001\u0002\u0000\u0004\u0005" + Stomp.NULL;
+ stompConnection.sendFrame(frame);
+ }
+
+ private void receiveForSslHandshakeTest() throws Exception {
+ String frame = "SUBSCRIBE\n" + "destination:/queue/" + getQueueName() + "\n" + "ack:auto\n\n" + Stomp.NULL;
+ stompConnection.sendFrame(frame);
+ StompFrame message = stompConnection.receive();
+ assertTrue(message.getAction().startsWith("MESSAGE"));
+ String length = message.getHeaders().get("content-length");
+ assertEquals("5", length);
+ assertEquals(5, message.getContent().length);
+ }
+
public void doTestAckInTransaction(boolean topic) throws Exception {
String frame = "CONNECT\n" + "login:system\n" + "passcode:manager\n\n" + Stomp.NULL;
@@ -2586,7 +2688,7 @@ public class StompTest extends StompTestSupport {
}
private static Map<ActiveMQDestination, org.apache.activemq.broker.region.Destination> getDestinationMap(BrokerService target,
- ActiveMQDestination destination) {
+ ActiveMQDestination destination) {
RegionBroker regionBroker = (RegionBroker) target.getRegionBroker();
if (destination.isTemporary()) {
return destination.isQueue() ? regionBroker.getTempQueueRegion().getDestinationMap() :
diff --git a/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompTestSupport.java b/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompTestSupport.java
index 021f08e5e7..ecdeae5833 100644
--- a/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompTestSupport.java
+++ b/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/StompTestSupport.java
@@ -21,6 +21,7 @@ import java.io.File;
import java.io.IOException;
import java.net.Socket;
import java.security.ProtectionDomain;
+import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
@@ -29,6 +30,7 @@ import jakarta.jms.JMSException;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
+import javax.net.ssl.SSLContext;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.broker.BrokerPlugin;
import org.apache.activemq.broker.BrokerService;
@@ -70,6 +72,7 @@ public class StompTestSupport {
protected StompConnection stompConnection;
protected ActiveMQConnectionFactory cf;
protected Vector<Throwable> exceptions = new Vector<Throwable>();
+ protected String transportConnectorName = null;
@Rule public TestName name = new TestName();
@@ -118,6 +121,30 @@ public class StompTestSupport {
LOG.info("========== finished " + getName() + " ==========");
}
+ protected Socket createSslSocket(String host, int port, String protocol) throws IOException {
+ try {
+ final SSLContext context;
+ if (protocol != null) {
+ File keyStore = new File(basedir(), "src/test/resources/server.keystore");
+ File trustStore = new File(basedir(), "src/test/resources/client.keystore");
+
+ final ResourceLoadingSslContext sslContext = new ResourceLoadingSslContext();
+ sslContext.setKeyStore(keyStore.getCanonicalPath());
+ sslContext.setKeyStorePassword("password");
+ sslContext.setTrustStore(trustStore.getCanonicalPath());
+ sslContext.setTrustStorePassword("password");
+ sslContext.setProtocol(protocol);
+ sslContext.afterPropertiesSet();
+ context = sslContext.getSSLContext();
+ } else {
+ context = SSLContext.getDefault();
+ }
+ return context.getSocketFactory().createSocket(host, port);
+ } catch (Exception e) {
+ throw new IOException(e);
+ }
+ }
+
public void startBroker() throws Exception {
if (brokerService != null) {
stopBroker();
@@ -151,7 +178,7 @@ public class StompTestSupport {
ArrayList<BrokerPlugin> plugins = new ArrayList<BrokerPlugin>();
- addTranportConnectors();
+ transportConnectorName = addTranportConnectors();
addOpenWireConnector();
BrokerPlugin authenticationPlugin = configureAuthentication();
@@ -291,7 +318,7 @@ public class StompTestSupport {
cf = new ActiveMQConnectionFactory(jmsUri);
}
- protected void addTranportConnectors() throws Exception {
+ protected String addTranportConnectors() throws Exception {
TransportConnector connector = null;
if (isUseTcpConnector()) {
@@ -342,6 +369,8 @@ public class StompTestSupport {
autoNioSslPort = connector.getConnectUri().getPort();
LOG.debug("Using auto+nio+ssl port " + autoNioSslPort);
}
+
+ return connector.getName();
}
protected boolean isPersistent() {
diff --git a/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/auto/StompAutoNioSslTest.java b/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/auto/StompAutoNioSslTest.java
index 90fc18cd3d..7c20c9260f 100644
--- a/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/auto/StompAutoNioSslTest.java
+++ b/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/auto/StompAutoNioSslTest.java
@@ -18,10 +18,6 @@ package org.apache.activemq.transport.stomp.auto;
import java.io.IOException;
import java.net.Socket;
-
-import javax.net.SocketFactory;
-import javax.net.ssl.SSLSocketFactory;
-
import org.apache.activemq.transport.stomp.StompTest;
public class StompAutoNioSslTest extends StompTest {
@@ -38,7 +34,7 @@ public class StompAutoNioSslTest extends StompTest {
@Override
protected Socket createSocket() throws IOException {
- SocketFactory factory = SSLSocketFactory.getDefault();
- return factory.createSocket("127.0.0.1", this.autoNioSslPort);
+ return createSslSocket("127.0.0.1", this.autoNioSslPort, this.clientSslProtocol);
}
+
}
diff --git a/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/auto/StompAutoSslTest.java b/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/auto/StompAutoSslTest.java
index c4ac5c2871..c00d8e3dbc 100644
--- a/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/auto/StompAutoSslTest.java
+++ b/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/auto/StompAutoSslTest.java
@@ -19,9 +19,6 @@ package org.apache.activemq.transport.stomp.auto;
import java.io.IOException;
import java.net.Socket;
-import javax.net.SocketFactory;
-import javax.net.ssl.SSLSocketFactory;
-
import org.apache.activemq.transport.stomp.StompTest;
public class StompAutoSslTest extends StompTest {
@@ -38,7 +35,6 @@ public class StompAutoSslTest extends StompTest {
@Override
protected Socket createSocket() throws IOException {
- SocketFactory factory = SSLSocketFactory.getDefault();
- return factory.createSocket("127.0.0.1", this.autoSslPort);
+ return createSslSocket("127.0.0.1", this.autoSslPort, this.clientSslProtocol);
}
}
diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/transport/TransportBrokerTestSupport.java b/activemq-unit-tests/src/test/java/org/apache/activemq/transport/TransportBrokerTestSupport.java
index b40f574188..46c2127465 100644
--- a/activemq-unit-tests/src/test/java/org/apache/activemq/transport/TransportBrokerTestSupport.java
+++ b/activemq-unit-tests/src/test/java/org/apache/activemq/transport/TransportBrokerTestSupport.java
@@ -16,23 +16,42 @@
*/
package org.apache.activemq.transport;
+import java.io.IOException;
+import java.lang.reflect.Field;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Iterator;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLSocket;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.broker.BrokerTest;
import org.apache.activemq.broker.StubConnection;
+import org.apache.activemq.broker.TransportConnection;
import org.apache.activemq.broker.TransportConnector;
+import org.apache.activemq.command.ActiveMQDestination;
+import org.apache.activemq.command.ConnectionInfo;
+import org.apache.activemq.command.ConsumerInfo;
+import org.apache.activemq.command.Message;
+import org.apache.activemq.command.ProducerInfo;
+import org.apache.activemq.command.SessionInfo;
+import org.apache.activemq.transport.nio.NIOSSLTransport;
+import org.apache.activemq.transport.tcp.TcpTransport;
+import org.apache.activemq.util.NioSslTestUtil;
+import org.apache.activemq.util.Wait;
public abstract class TransportBrokerTestSupport extends BrokerTest {
protected TransportConnector connector;
private ArrayList<StubConnection> connections = new ArrayList<StubConnection>();
+ protected String enabledProtocols = null;
+
@Override
protected void setUp() throws Exception {
+ enabledProtocols = null;
super.setUp();
}
@@ -87,4 +106,59 @@ public abstract class TransportBrokerTestSupport extends BrokerTest {
return connection;
}
+
+ protected void testSslHandshakeRenegotiation(String protocol) throws Exception {
+ this.enabledProtocols = protocol;
+
+ // Start a producer
+ StubConnection connection = createConnection();
+ ConnectionInfo connectionInfo = createConnectionInfo();
+ SessionInfo sessionInfo = createSessionInfo(connectionInfo);
+ ProducerInfo producerInfo = createProducerInfo(sessionInfo);
+ connection.send(connectionInfo);
+ connection.send(sessionInfo);
+ connection.send(producerInfo);
+
+ destination = createDestinationInfo(connection, connectionInfo, ActiveMQDestination.QUEUE_TYPE);
+
+ TcpTransport tcpTransport = connection.getTransport().narrow(TcpTransport.class);
+ Field socketField = TcpTransport.class.getDeclaredField("socket");
+ socketField.setAccessible(true);
+ SSLSocket socket = (SSLSocket) socketField.get(tcpTransport);
+ assertEquals(protocol, socket.getSession().getProtocol());
+
+ // trigger a bunch of updates (either TLSv1.2 full updates or TLSv1.3 key updates)
+ for (int i = 0; i < 10; i++) {
+ try {
+ socket.startHandshake();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ // give some time for the handshake updates
+ Thread.sleep(100);
+
+ // check status advances if NIOSSL, then continue
+ // below to verify transports are not stuck
+ checkHandshakeStatusAdvances(socket);
+
+ // Send a message to the broker.
+ connection.send(createMessage(producerInfo, destination, deliveryMode));
+
+ // Start the consumer
+ ConsumerInfo consumerInfo = createConsumerInfo(sessionInfo, destination);
+ connection.send(consumerInfo);
+
+ // Make sure the message was delivered.
+ Message m = receiveMessage(connection);
+ assertNotNull(m);
+ }
+
+ // This only applies to NIO SSL
+ protected void checkHandshakeStatusAdvances(SSLSocket socket) throws Exception {
+ TransportConnector connector = broker.getTransportConnectorByScheme(getBindURI().getScheme());
+ NioSslTestUtil.checkHandshakeStatusAdvances(connector, socket);
+ }
+
}
diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/transport/auto/AutoNIOSslTransportBrokerTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/transport/auto/AutoNIOSslTransportBrokerTest.java
index cb90b415c3..05cd100778 100644
--- a/activemq-unit-tests/src/test/java/org/apache/activemq/transport/auto/AutoNIOSslTransportBrokerTest.java
+++ b/activemq-unit-tests/src/test/java/org/apache/activemq/transport/auto/AutoNIOSslTransportBrokerTest.java
@@ -16,13 +16,37 @@
*/
package org.apache.activemq.transport.auto;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLSocket;
import junit.framework.Test;
import junit.textui.TestRunner;
+import org.apache.activemq.broker.StubConnection;
+import org.apache.activemq.broker.TransportConnection;
+import org.apache.activemq.broker.TransportConnector;
+import org.apache.activemq.command.ActiveMQDestination;
+import org.apache.activemq.command.ConnectionInfo;
+import org.apache.activemq.command.ConsumerInfo;
+import org.apache.activemq.command.Message;
+import org.apache.activemq.command.ProducerInfo;
+import org.apache.activemq.command.SessionInfo;
+import org.apache.activemq.transport.Transport;
import org.apache.activemq.transport.TransportBrokerTestSupport;
+import org.apache.activemq.transport.TransportFilter;
+import org.apache.activemq.transport.nio.NIOSSLTransport;
+import org.apache.activemq.transport.tcp.SslTransport;
+import org.apache.activemq.transport.tcp.TcpTransport;
+import org.apache.activemq.util.Wait;
public class AutoNIOSslTransportBrokerTest extends TransportBrokerTestSupport {
@@ -38,7 +62,9 @@ public class AutoNIOSslTransportBrokerTest extends TransportBrokerTestSupport {
@Override
protected URI getBindURI() throws URISyntaxException {
- return new URI("auto+nio+ssl://localhost:0?soWriteTimeout=20000");
+ String uri = super.getBindURI().toString() + "?soWriteTimeout=20000";
+ uri = enabledProtocols != null ? uri + "&socket.enabledProtocols=" + enabledProtocols : uri;
+ return new URI(uri);
}
@Override
@@ -54,6 +80,14 @@ public class AutoNIOSslTransportBrokerTest extends TransportBrokerTestSupport {
super.setUp();
}
+ public void testSslHandshakeRenegotiationTlsv12() throws Exception {
+ testSslHandshakeRenegotiation("TLSv1.2");
+ }
+
+ public void testSslHandshakeRenegotiationTlsv13() throws Exception {
+ testSslHandshakeRenegotiation("TLSv1.3");
+ }
+
public static Test suite() {
return suite(AutoNIOSslTransportBrokerTest.class);
}
@@ -62,5 +96,4 @@ public class AutoNIOSslTransportBrokerTest extends TransportBrokerTestSupport {
TestRunner.run(suite());
}
-
}
diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/transport/auto/AutoSslTransportBrokerTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/transport/auto/AutoSslTransportBrokerTest.java
index 203f6ee97a..99cea2d5b7 100644
--- a/activemq-unit-tests/src/test/java/org/apache/activemq/transport/auto/AutoSslTransportBrokerTest.java
+++ b/activemq-unit-tests/src/test/java/org/apache/activemq/transport/auto/AutoSslTransportBrokerTest.java
@@ -36,7 +36,9 @@ public class AutoSslTransportBrokerTest extends TransportBrokerTestSupport {
@Override
protected URI getBindURI() throws URISyntaxException {
- return new URI("auto+ssl://localhost:0?soWriteTimeout=20000");
+ String uri = super.getBindURI().toString() + "?soWriteTimeout=20000";
+ uri = enabledProtocols != null ? uri + "&socket.enabledProtocols=" + enabledProtocols : uri;
+ return new URI(uri);
}
@Override
@@ -53,6 +55,14 @@ public class AutoSslTransportBrokerTest extends TransportBrokerTestSupport {
super.setUp();
}
+ public void testSslHandshakeRenegotiationTlsv12() throws Exception {
+ testSslHandshakeRenegotiation("TLSv1.2");
+ }
+
+ public void testSslHandshakeRenegotiationTlsv13() throws Exception {
+ testSslHandshakeRenegotiation("TLSv1.3");
+ }
+
public static Test suite() {
return suite(AutoSslTransportBrokerTest.class);
}
diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/transport/nio/NIOSSLBasicTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/transport/nio/NIOSSLBasicTest.java
index 59b58443e9..4af3f7f682 100644
--- a/activemq-unit-tests/src/test/java/org/apache/activemq/transport/nio/NIOSSLBasicTest.java
+++ b/activemq-unit-tests/src/test/java/org/apache/activemq/transport/nio/NIOSSLBasicTest.java
@@ -24,6 +24,9 @@ import jakarta.jms.Queue;
import jakarta.jms.Session;
import jakarta.jms.TextMessage;
+import javax.net.ServerSocketFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocketFactory;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LogEvent;
@@ -98,7 +101,10 @@ public class NIOSSLBasicTest {
@Test
public void enabledCipherSuites() throws Exception {
- BrokerService broker = createBroker("nio+ssl", getTransportType() + "://localhost:0?transport.needClientAuth=true&transport.verifyHostName=false&transport.enabledCipherSuites=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256");
+ SSLServerSocketFactory s = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
+ String[] defaultCipherSuites = s.getSupportedCipherSuites();
+ assertTrue(defaultCipherSuites.length > 0);
+ BrokerService broker = createBroker("nio+ssl", getTransportType() + "://localhost:0?transport.needClientAuth=true&transport.verifyHostName=false&transport.enabledCipherSuites=" + defaultCipherSuites[0]);
basicSendReceive("ssl://localhost:" + broker.getConnectorByName("nio+ssl").getConnectUri().getPort() + "?socket.verifyHostName=false");
stopBroker(broker);
}
diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/transport/nio/NIOSSLTransportBrokerTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/transport/nio/NIOSSLTransportBrokerTest.java
index 2d6d13ef19..f9c19c38c0 100644
--- a/activemq-unit-tests/src/test/java/org/apache/activemq/transport/nio/NIOSSLTransportBrokerTest.java
+++ b/activemq-unit-tests/src/test/java/org/apache/activemq/transport/nio/NIOSSLTransportBrokerTest.java
@@ -36,7 +36,9 @@ public class NIOSSLTransportBrokerTest extends TransportBrokerTestSupport {
@Override
protected URI getBindURI() throws URISyntaxException {
- return new URI("nio+ssl://localhost:0?soWriteTimeout=20000");
+ String uri = super.getBindURI().toString() + "?soWriteTimeout=20000";
+ uri = enabledProtocols != null ? uri + "&socket.enabledProtocols=" + enabledProtocols : uri;
+ return new URI(uri);
}
@Override
@@ -58,6 +60,14 @@ public class NIOSSLTransportBrokerTest extends TransportBrokerTestSupport {
super.tearDown();
}
+ public void testSslHandshakeRenegotiationTlsv12() throws Exception {
+ testSslHandshakeRenegotiation("TLSv1.2");
+ }
+
+ public void testSslHandshakeRenegotiationTlsv13() throws Exception {
+ testSslHandshakeRenegotiation("TLSv1.3");
+ }
+
public static Test suite() {
return suite(NIOSSLTransportBrokerTest.class);
}
diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/transport/tcp/SslTransportBrokerTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/transport/tcp/SslTransportBrokerTest.java
index de07a74f86..17b4a94a33 100644
--- a/activemq-unit-tests/src/test/java/org/apache/activemq/transport/tcp/SslTransportBrokerTest.java
+++ b/activemq-unit-tests/src/test/java/org/apache/activemq/transport/tcp/SslTransportBrokerTest.java
@@ -35,7 +35,9 @@ public class SslTransportBrokerTest extends TransportBrokerTestSupport {
@Override
protected URI getBindURI() throws URISyntaxException {
- return new URI("ssl://localhost:0?soWriteTimeout=20000");
+ String uri = super.getBindURI().toString() + "?soWriteTimeout=20000";
+ uri = enabledProtocols != null ? uri + "&socket.enabledProtocols=" + enabledProtocols : uri;
+ return new URI(uri);
}
protected void setUp() throws Exception {
@@ -51,6 +53,14 @@ public class SslTransportBrokerTest extends TransportBrokerTestSupport {
super.setUp();
}
+ public void testSslHandshakeRenegotiationTlsv12() throws Exception {
+ testSslHandshakeRenegotiation("TLSv1.2");
+ }
+
+ public void testSslHandshakeRenegotiationTlsv13() throws Exception {
+ testSslHandshakeRenegotiation("TLSv1.3");
+ }
+
public static Test suite() {
return suite(SslTransportBrokerTest.class);
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@activemq.apache.org
For additional commands, e-mail: commits-help@activemq.apache.org
For further information, visit: https://activemq.apache.org/contact