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 df6a9ce232f..dfe650680d7 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 Connection createConnection(URI remoteURI, String username, String passwo } 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 @@ private TopicConnectionFactory createTopicConnectionFactory( } 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 @@ private ConnectionFactory createConnectionFactory( 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 f13884d282e..c718fb84479 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 @@ protected URI getBrokerURI() { 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 @@ protected boolean isUseNioPlusSslConnector() { 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 b0bd3a7f598..d8372cffd25 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 @@ 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 @@ protected boolean isUseSslConnector() { 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 127c6d6d895..90e8fd0fb68 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 @@ protected URI getBrokerURI() { 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 @@ protected boolean isUseAutoNioPlusSslConnector() { 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 b16d5575f94..06aa4677715 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 void serviceRead() { 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 void serviceRead() { 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 00000000000..4488d606f81 --- /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 3ba064d13d8..606503288cf 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.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 void serviceRead() { } 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 void serviceRead() { 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 @@ protected synchronized int secureRead(ByteBuffer plain) throws Exception { 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 b6dd9f91581..c5c397f9213 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 String getProtocolScheme() { 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 7cf1523beef..1bd7078e3e9 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.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.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.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.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 boolean isSatisified() throws Exception { } } } + + 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 881ad634243..3ad4e06c78e 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 void setUp() throws Exception { System.setProperty("javax.net.ssl.keyStoreType", "jks"); exceptions.clear(); + startBroker(); } @@ -413,7 +414,11 @@ private MQTT createMQTTTcpConnection(String clientId, boolean clean) throws Exce 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 @@ private MQTT createMQTTSslConnection(String clientId, boolean clean) throws Exce 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 e777385f1b8..ac11c31c4cb 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 String getProtocolScheme() { 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 836657c4c97..e54a9d82936 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 @@ 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 @@ protected boolean isUseNioPlusSslConnector() { @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 03c24c436b0..02c561630f2 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 @@ 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 @@ protected BrokerPlugin configureAuthentication() throws Exception { @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 1e9b66cb764..f02ebb16fd3 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 @@ 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 @@ protected boolean isUseSslConnector() { @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 e93df5a4d27..4c9c2484a75 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 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.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.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 = "\n" + " Dejan\n" @@ -137,6 +147,7 @@ public void setUp() throws Exception { queue = new ActiveMQQueue(getQueueName()); super.setUp(); + clientSslProtocol = null; stompConnect(); connection = cf.createConnection("system", "manager"); @@ -2523,6 +2534,97 @@ public void testFrameHeaderEscapes() throws Exception { } + // 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 static org.apache.activemq.broker.region.Destination getDestination(Broke } private static Map 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 021f08e5e7c..ecdeae58331 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.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 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 exceptions = new Vector(); + protected String transportConnectorName = null; @Rule public TestName name = new TestName(); @@ -118,6 +121,30 @@ public void tearDown() throws Exception { 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 void startBroker() throws Exception { ArrayList plugins = new ArrayList(); - addTranportConnectors(); + transportConnectorName = addTranportConnectors(); addOpenWireConnector(); BrokerPlugin authenticationPlugin = configureAuthentication(); @@ -291,7 +318,7 @@ public void addOpenWireConnector() throws Exception { cf = new ActiveMQConnectionFactory(jmsUri); } - protected void addTranportConnectors() throws Exception { + protected String addTranportConnectors() throws Exception { TransportConnector connector = null; if (isUseTcpConnector()) { @@ -342,6 +369,8 @@ protected void addTranportConnectors() throws Exception { 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 90fc18cd3d8..7c20c9260f7 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 @@ 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 @@ protected boolean isUseAutoNioPlusSslConnector() { @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 c4ac5c28719..c00d8e3dbc5 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 @@ 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 @@ protected boolean isUseAutoSslConnector() { @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 b40f574188e..46c2127465c 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 connections = new ArrayList(); + protected String enabledProtocols = null; + @Override protected void setUp() throws Exception { + enabledProtocols = null; super.setUp(); } @@ -87,4 +106,59 @@ protected StubConnection createConnection() throws Exception { 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 cb90b415c30..05cd100778d 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 @@ protected String getBindLocation() { @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 @@ protected void setUp() throws Exception { 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 static void main(String[] args) { 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 203f6ee97a7..99cea2d5b71 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 @@ protected String getBindLocation() { @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 @@ protected void setUp() throws Exception { 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 59b58443e92..4af3f7f682f 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.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 void basicConnector() throws Exception { @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 2d6d13ef19f..f9c19c38c09 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 @@ protected String getBindLocation() { @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 @@ protected void tearDown() throws Exception { 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 de07a74f869..17b4a94a333 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 @@ protected String getBindLocation() { @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 @@ protected void setUp() throws Exception { 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); }