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