From 4d8184df0ce14255158beaf33c654cb76fbdaf4e Mon Sep 17 00:00:00 2001 From: rabbitmq-ci Date: Tue, 15 Sep 2026 08:11:22 +0000 Subject: [PATCH 01/14] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3aba7e258..d0a1691d4 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.rabbitmq amqp-client - 5.36.0 + 5.37.0-SNAPSHOT jar RabbitMQ Java Client @@ -42,7 +42,7 @@ https://github.com/rabbitmq/rabbitmq-java-client scm:git:git://github.com/rabbitmq/rabbitmq-java-client.git scm:git:https://github.com/rabbitmq/rabbitmq-java-client.git - v5.36.0 + HEAD From 9d1036640990f9d9818b6631bfb837e6569261d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Cogolu=C3=A8gnes?= <514737+acogoluegnes@users.noreply.github.com> Date: Wed, 16 Sep 2026 15:47:16 +0200 Subject: [PATCH 02/14] test(network): add advanced TLS tests (cherry picked from commit ade369b9dbb096bca0c527b5840304edaa662742) --- .github/workflows/test-rabbitmq-alphas.yml | 5 + .../test-supported-java-versions-5.x.yml | 2 + .../test-supported-java-versions-main.yml | 2 + .github/workflows/test.yml | 2 + ci/cluster/advanced.config | 26 ++ ci/cluster/rabbitmq_post_erlang_28.conf | 13 + ...bitmq.conf => rabbitmq_pre_erlang_28.conf} | 0 ci/start-broker.sh | 48 ++++ ci/start-cluster.sh | 19 +- pom.xml | 31 +- .../com/rabbitmq/client/test/TestUtils.java | 112 +++++--- .../ConnectionFactoryDefaultTlsVersion.java | 2 +- .../com/rabbitmq/client/test/ssl/Pqc.java | 266 ++++++++++++++++++ .../client/test/ssl/SslTestSuite.java | 3 +- .../client/test/ssl/TlsConnectionLogging.java | 2 + .../client/test/ssl/TlsTestUtils.java | 83 +++++- 16 files changed, 573 insertions(+), 43 deletions(-) create mode 100644 ci/cluster/advanced.config create mode 100644 ci/cluster/rabbitmq_post_erlang_28.conf rename ci/cluster/{configuration/rabbitmq.conf => rabbitmq_pre_erlang_28.conf} (100%) create mode 100644 src/test/java/com/rabbitmq/client/test/ssl/Pqc.java diff --git a/.github/workflows/test-rabbitmq-alphas.yml b/.github/workflows/test-rabbitmq-alphas.yml index 8b610cbb0..d364cd24d 100644 --- a/.github/workflows/test-rabbitmq-alphas.yml +++ b/.github/workflows/test-rabbitmq-alphas.yml @@ -26,8 +26,10 @@ jobs: matrix: include: - rabbitmq-image: pivotalrabbitmq/rabbitmq:v4.3.x-otp27 + erlang-version: 27 rabbitmq-branch: 4.3 - rabbitmq-image: pivotalrabbitmq/rabbitmq:main-otp27 + erlang-version: 28 rabbitmq-branch: 4.4 name: Test against ${{ matrix.rabbitmq-image }} steps: @@ -51,6 +53,7 @@ jobs: run: ci/start-cluster.sh env: RABBITMQ_IMAGE: ${{ matrix.rabbitmq-image }} + ERLANG_VERSION: ${{ matrix.erlang-version }} - name: Get dependencies run: make deps - name: Test with Netty @@ -59,6 +62,7 @@ jobs: -Dtest-broker.A.nodename=rabbit@node0 -Dtest-broker.B.nodename=rabbit@node1 \ -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ + -Dclient.key=./tls-gen/basic/result/client_$(hostname)_key.pem \ -Dmaven.javadoc.skip=true \ --no-transfer-progress - name: Test with blocking IO @@ -67,6 +71,7 @@ jobs: -Dtest-broker.A.nodename=rabbit@node0 -Dtest-broker.B.nodename=rabbit@node1 \ -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ + -Dclient.key=./tls-gen/basic/result/client_$(hostname)_key.pem \ -Dmaven.javadoc.skip=true \ --no-transfer-progress - name: Get broker logs diff --git a/.github/workflows/test-supported-java-versions-5.x.yml b/.github/workflows/test-supported-java-versions-5.x.yml index e4f9e916b..aaef4c0eb 100644 --- a/.github/workflows/test-supported-java-versions-5.x.yml +++ b/.github/workflows/test-supported-java-versions-5.x.yml @@ -47,6 +47,7 @@ jobs: -Dtest-broker.A.nodename=rabbit@$(hostname) -Dmaven.javadoc.skip=true \ -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ + -Dclient.key=./tls-gen/basic/result/client_$(hostname)_key.pem \ -Dit.test=ClientTestSuite,FunctionalTestSuite,ServerTestSuite,SslTestSuite \ --no-transfer-progress - name: Test with blocking IO @@ -55,6 +56,7 @@ jobs: -Dtest-broker.A.nodename=rabbit@$(hostname) -Dmaven.javadoc.skip=true \ -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ + -Dclient.key=./tls-gen/basic/result/client_$(hostname)_key.pem \ -Dit.test=ClientTestSuite,FunctionalTestSuite,ServerTestSuite,SslTestSuite \ --no-transfer-progress \ -Dnet.bytebuddy.experimental=true diff --git a/.github/workflows/test-supported-java-versions-main.yml b/.github/workflows/test-supported-java-versions-main.yml index c0c5c5262..2170f3b30 100644 --- a/.github/workflows/test-supported-java-versions-main.yml +++ b/.github/workflows/test-supported-java-versions-main.yml @@ -45,6 +45,7 @@ jobs: -Dtest-broker.A.nodename=rabbit@$(hostname) -Dmaven.javadoc.skip=true \ -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ + -Dclient.key=./tls-gen/basic/result/client_$(hostname)_key.pem \ -Dit.test=ClientTestSuite,FunctionalTestSuite,ServerTestSuite,SslTestSuite \ --no-transfer-progress - name: Test with blocking IO @@ -53,6 +54,7 @@ jobs: -Dtest-broker.A.nodename=rabbit@$(hostname) -Dmaven.javadoc.skip=true \ -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ + -Dclient.key=./tls-gen/basic/result/client_$(hostname)_key.pem \ -Dit.test=ClientTestSuite,FunctionalTestSuite,ServerTestSuite,SslTestSuite \ --no-transfer-progress \ -Dnet.bytebuddy.experimental=true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 64ccd6c39..0f6a0692a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,6 +53,7 @@ jobs: -Dtest-broker.A.nodename=rabbit@node0 -Dtest-broker.B.nodename=rabbit@node1 \ -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ + -Dclient.key=./tls-gen/basic/result/client_$(hostname)_key.pem \ -Dmaven.javadoc.skip=true \ --no-transfer-progress - name: Test with blocking IO @@ -61,6 +62,7 @@ jobs: -Dtest-broker.A.nodename=rabbit@node0 -Dtest-broker.B.nodename=rabbit@node1 \ -Dca.certificate=./tls-gen/basic/result/ca_certificate.pem \ -Dclient.certificate=./tls-gen/basic/result/client_$(hostname)_certificate.pem \ + -Dclient.key=./tls-gen/basic/result/client_$(hostname)_key.pem \ -Dmaven.javadoc.skip=true \ --no-transfer-progress - name: Get broker logs diff --git a/ci/cluster/advanced.config b/ci/cluster/advanced.config new file mode 100644 index 000000000..1e72342ff --- /dev/null +++ b/ci/cluster/advanced.config @@ -0,0 +1,26 @@ +[ + {rabbit, [ + {ssl_options, [ + {cacertfile, "/etc/rabbitmq/tls/ca_certificate.pem"}, + {certfile, "/etc/rabbitmq/tls/server_certificate.pem"}, + {keyfile, "/etc/rabbitmq/tls/server_key.pem"}, + {verify, verify_peer}, + {fail_if_no_peer_cert, false}, + {depth, 1}, + {versions, ['tlsv1.3', 'tlsv1.2']}, + {supported_groups, [x25519mlkem768, x25519, secp256r1]}, + {honor_cipher_order, true}, + {ciphers, [ + %% TLS 1.3 Ciphers + "TLS_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256", + "TLS_AES_128_GCM_SHA256", + %% TLS 1.2 Ciphers + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" + ]} + ]} + ]} +]. \ No newline at end of file diff --git a/ci/cluster/rabbitmq_post_erlang_28.conf b/ci/cluster/rabbitmq_post_erlang_28.conf new file mode 100644 index 000000000..c5225b656 --- /dev/null +++ b/ci/cluster/rabbitmq_post_erlang_28.conf @@ -0,0 +1,13 @@ +cluster_formation.peer_discovery_backend = rabbit_peer_discovery_classic_config +cluster_formation.classic_config.nodes.1 = rabbit@node0 +cluster_formation.classic_config.nodes.2 = rabbit@node1 +cluster_formation.classic_config.nodes.3 = rabbit@node2 +loopback_users = none + +listeners.ssl.default = 5671 + +auth_mechanisms.1 = PLAIN +auth_mechanisms.2 = ANONYMOUS +auth_mechanisms.3 = AMQPLAIN +auth_mechanisms.4 = EXTERNAL +auth_mechanisms.5 = RABBIT-CR-DEMO diff --git a/ci/cluster/configuration/rabbitmq.conf b/ci/cluster/rabbitmq_pre_erlang_28.conf similarity index 100% rename from ci/cluster/configuration/rabbitmq.conf rename to ci/cluster/rabbitmq_pre_erlang_28.conf diff --git a/ci/start-broker.sh b/ci/start-broker.sh index 493225567..32d8c0f8d 100755 --- a/ci/start-broker.sh +++ b/ci/start-broker.sh @@ -3,6 +3,7 @@ LOCAL_SCRIPT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" RABBITMQ_IMAGE=${RABBITMQ_IMAGE:-rabbitmq:4.3} +ERLANG_VERSION=${ERLANG_VERSION:-27} wait_for_message() { while ! docker logs "$1" | grep -q "$2"; @@ -23,6 +24,10 @@ cp -R "${PWD}"/tls-gen/basic/result/* rabbitmq-configuration/tls chmod o+r rabbitmq-configuration/tls/* chmod g+r rabbitmq-configuration/tls/* +if [ "$ERLANG_VERSION" -lt 28 ]; then + +# Erlang < 28 + echo "loopback_users = none listeners.ssl.default = 5671 @@ -40,6 +45,49 @@ auth_mechanisms.3 = AMQPLAIN auth_mechanisms.4 = EXTERNAL auth_mechanisms.5 = RABBIT-CR-DEMO" >> rabbitmq-configuration/rabbitmq.conf +else + +# Erlang >= 28 + +echo "loopback_users = none + +listeners.ssl.default = 5671 + +auth_mechanisms.1 = PLAIN +auth_mechanisms.2 = ANONYMOUS +auth_mechanisms.3 = AMQPLAIN +auth_mechanisms.4 = EXTERNAL +auth_mechanisms.5 = RABBIT-CR-DEMO" >> rabbitmq-configuration/rabbitmq.conf + +echo "[ + {rabbit, [ + {ssl_options, [ + {cacertfile, \"/etc/rabbitmq/tls/ca_certificate.pem\"}, + {certfile, \"/etc/rabbitmq/tls/server_$(hostname)_certificate.pem\"}, + {keyfile, \"/etc/rabbitmq/tls/server_$(hostname)_key.pem\"}, + {verify, verify_peer}, + {fail_if_no_peer_cert, false}, + {depth, 1}, + {versions, ['tlsv1.3']}, + {supported_groups, [x25519mlkem768, x25519, secp256r1]}, + {honor_cipher_order, true}, + {ciphers, [ + %% TLS 1.3 Ciphers + \"TLS_AES_256_GCM_SHA384\", + \"TLS_CHACHA20_POLY1305_SHA256\", + \"TLS_AES_128_GCM_SHA256\", + %% TLS 1.2 Ciphers + \"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384\", + \"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384\", + \"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256\", + \"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256\" + ]} + ]} + ]} +]." >> rabbitmq-configuration/advanced.config + +fi + echo "Running RabbitMQ ${RABBITMQ_IMAGE}" docker rm -f rabbitmq 2>/dev/null || echo "rabbitmq was not running" diff --git a/ci/start-cluster.sh b/ci/start-cluster.sh index e6be5f91b..d04bc8a5d 100755 --- a/ci/start-cluster.sh +++ b/ci/start-cluster.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash -export RABBITMQ_IMAGE=${RABBITMQ_IMAGE:-rabbitmq:4.3} +RABBITMQ_IMAGE=${RABBITMQ_IMAGE:-rabbitmq:4.3} +ERLANG_VERSION=${ERLANG_VERSION:-27} wait_for_message() { while ! docker logs "$1" | grep -q "$2"; @@ -23,6 +24,22 @@ mv rabbitmq-configuration/tls/server_$(hostname)_key.pem rabbitmq-configuration/ chmod o+r rabbitmq-configuration/tls/* chmod g+r rabbitmq-configuration/tls/* +rm -rf "${PWD}"/ci/cluster/configuration +mkdir "${PWD}"/ci/cluster/configuration + +if [ "$ERLANG_VERSION" -lt 28 ]; then + +# Erlang < 28 +cp "${PWD}"/ci/cluster/rabbitmq_pre_erlang_28.conf "${PWD}"/ci/cluster/configuration/rabbitmq.conf + +else + +# Erlang >= 28 +cp "${PWD}"/ci/cluster/rabbitmq_post_erlang_28.conf "${PWD}"/ci/cluster/configuration/rabbitmq.conf +cp "${PWD}"/ci/cluster/advanced.config "${PWD}"/ci/cluster/configuration/advanced.config + +fi + docker compose --file ci/cluster/docker-compose.yml down docker compose --file ci/cluster/docker-compose.yml up --detach diff --git a/pom.xml b/pom.xml index d0a1691d4..dd2786d87 100644 --- a/pom.xml +++ b/pom.xml @@ -68,7 +68,7 @@ 1.7.1 1.0.4 9.4.58.v20250814 - 1.86 + 1.85 0.10 2.14.0 @@ -92,6 +92,7 @@ 1.4 3.10.2 1.36.1 + 2.0.84.Final @@ -790,6 +818,7 @@ src/main/java/com/rabbitmq/client/impl/Environment.java src/main/java/com/rabbitmq/client/observation/**/*.java src/test/java/com/rabbitmq/client/AmqpClientTestExtension.java + src/test/java/com/rabbitmq/client/test/ssl/Pqc.java src/test/java/com/rabbitmq/client/test/ssl/TlsTestUtils.java src/test/java/com/rabbitmq/client/test/functional/MicrometerObservationCollectorMetrics.java src/test/java/com/rabbitmq/client/test/functional/DurableOnTransient.java diff --git a/src/test/java/com/rabbitmq/client/test/TestUtils.java b/src/test/java/com/rabbitmq/client/test/TestUtils.java index 089b9cd14..b8f5900f9 100644 --- a/src/test/java/com/rabbitmq/client/test/TestUtils.java +++ b/src/test/java/com/rabbitmq/client/test/TestUtils.java @@ -256,6 +256,26 @@ static String currentVersion(String currentVersion) { return currentVersion; } + static Integer erlangMajorVersion(Object platformObj) { + if (platformObj == null) { + return null; + } + String platform = platformObj.toString(); + // platform looks like: Erlang/OTP 27.3.4.14 + int spaceIndex = platform.lastIndexOf(' '); + if (spaceIndex < 0 || spaceIndex == platform.length() - 1) { + return null; + } + String version = platform.substring(spaceIndex + 1); + int dotIndex = version.indexOf('.'); + String majorVersion = dotIndex < 0 ? version : version.substring(0, dotIndex); + try { + return Integer.valueOf(majorVersion); + } catch (NumberFormatException e) { + return null; + } + } + public static boolean sendAndConsumeMessage( String exchange, String routingKey, String queue, Connection c) throws IOException, TimeoutException, InterruptedException { @@ -463,44 +483,6 @@ public void handleDelivery( return messageReceived; } - /* - public static class DefaultTestSuite extends Suite { - - - public DefaultTestSuite(Class klass, RunnerBuilder builder) - throws InitializationError { - super(klass, builder); - } - - public DefaultTestSuite(RunnerBuilder builder, Class[] classes) - throws InitializationError { - super(builder, classes); - } - - protected DefaultTestSuite(Class klass, Class[] suiteClasses) - throws InitializationError { - super(klass, suiteClasses); - } - - protected DefaultTestSuite(RunnerBuilder builder, Class klass, Class[] suiteClasses) - throws InitializationError { - super(builder, klass, suiteClasses); - } - - @Override - protected void runChild(Runner runner, RunNotifier notifier) { - LOGGER.info("Running test {}", runner.getDescription().getDisplayName()); - super.runChild(runner, notifier); - } - - protected DefaultTestSuite(Class klass, List runners) - throws InitializationError { - super(klass, runners); - } - } - - */ - public static void safeDelete(Connection connection, String queue) { try { Channel ch = connection.createChannel(); @@ -511,6 +493,52 @@ public static void safeDelete(Connection connection, String queue) { } } + private static class ErlangVersionCondition + implements org.junit.jupiter.api.extension.ExecutionCondition { + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + if (!context.getTestMethod().isPresent()) { + return ConditionEvaluationResult.enabled("Apply only to methods"); + } + ErlangVersionAtLeast annotation = + context.getElement().get().getAnnotation(ErlangVersionAtLeast.class); + int expectedVersion = annotation == null ? 0 : annotation.value(); + if (expectedVersion == 0) { + return ConditionEvaluationResult.enabled("No Erlang version requirement"); + } else { + Integer erlangVersion = + context + .getRoot() + .getStore(Namespace.GLOBAL) + .getOrComputeIfAbsent( + "erlangVersion", + k -> { + try (Connection c = TestUtils.connectionFactory().newConnection()) { + return erlangMajorVersion(c.getServerProperties().get("platform")); + } catch (Exception e) { + throw new RuntimeException(e); + } + }, + Integer.class); + + if (erlangVersion != null && erlangVersion >= expectedVersion) { + return ConditionEvaluationResult.enabled( + "Erlang version requirement met, expected " + + expectedVersion + + ", actual " + + erlangVersion); + } else { + return ConditionEvaluationResult.disabled( + "Erlang version requirement not met, expected " + + expectedVersion + + ", actual " + + erlangVersion); + } + } + } + } + private static class BaseBrokerVersionCondition implements org.junit.jupiter.api.extension.ExecutionCondition { @@ -632,6 +660,14 @@ public String toString() { } } + @Target({ElementType.TYPE, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @ExtendWith(ErlangVersionCondition.class) + public @interface ErlangVersionAtLeast { + int value(); + } + static class DisabledIfBrokerRunningOnDockerCondition implements org.junit.jupiter.api.extension.ExecutionCondition { diff --git a/src/test/java/com/rabbitmq/client/test/ssl/ConnectionFactoryDefaultTlsVersion.java b/src/test/java/com/rabbitmq/client/test/ssl/ConnectionFactoryDefaultTlsVersion.java index f64b887dc..1984dfead 100644 --- a/src/test/java/com/rabbitmq/client/test/ssl/ConnectionFactoryDefaultTlsVersion.java +++ b/src/test/java/com/rabbitmq/client/test/ssl/ConnectionFactoryDefaultTlsVersion.java @@ -24,7 +24,7 @@ public class ConnectionFactoryDefaultTlsVersion { @Test public void defaultTlsVersionJdk16ShouldTakeFallback() { String [] supportedProtocols = {"SSLv2Hello", "SSLv3", "TLSv1"}; String tlsProtocol = ConnectionFactory.computeDefaultTlsProtocol(supportedProtocols); - Assertions.assertEquals("TLSv1",tlsProtocol); + Assertions.assertEquals("TLSv1.2",tlsProtocol); } @Test public void defaultTlsVersionJdk17ShouldTakePrefered() { diff --git a/src/test/java/com/rabbitmq/client/test/ssl/Pqc.java b/src/test/java/com/rabbitmq/client/test/ssl/Pqc.java new file mode 100644 index 000000000..15215ef61 --- /dev/null +++ b/src/test/java/com/rabbitmq/client/test/ssl/Pqc.java @@ -0,0 +1,266 @@ +// Copyright (c) 2026 Broadcom. All Rights Reserved. +// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +// +// This software, the RabbitMQ Java client library, is triple-licensed under the +// Mozilla Public License 2.0 ("MPL"), the GNU General Public License version 2 +// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see +// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, +// please see LICENSE-APACHE2. +// +// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, +// either express or implied. See the LICENSE file for specific language governing +// rights and limitations of this software. +// +// If you have any questions regarding licensing, please contact us at +// info@rabbitmq.com. +package com.rabbitmq.client.test.ssl; + +import static com.rabbitmq.client.test.ssl.TlsTestUtils.caCertificate; +import static com.rabbitmq.client.test.ssl.TlsTestUtils.clientCertificate; +import static com.rabbitmq.client.test.ssl.TlsTestUtils.clientKey; +import static com.rabbitmq.client.test.ssl.TlsTestUtils.hostname; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.rabbitmq.client.Address; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.SocketConfigurator; +import com.rabbitmq.client.test.TestUtils; +import com.rabbitmq.client.test.TestUtils.ErlangVersionAtLeast; +import io.netty.channel.Channel; +import io.netty.handler.ssl.OpenSslContextOption; +import io.netty.handler.ssl.OpenSslSession; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.SslHandler; +import io.netty.handler.ssl.SslProvider; +import java.io.IOException; +import java.security.Security; +import java.util.Collections; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; +import javax.net.ssl.SNIHostName; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; + +class Pqc { + + String group = "X25519MLKEM768"; + String cipher = "TLS_AES_256_GCM_SHA384"; + String protocol = "TLSv1.3"; + + @BeforeAll + static void init() { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + if (Security.getProvider(BouncyCastleJsseProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleJsseProvider()); + } + } + + @Test + @EnabledForJreRange(minVersion = 27) + @ErlangVersionAtLeast(28) + void pqcBlockingIoJsse() throws Exception { + AtomicReference socket = new AtomicReference<>(); + SSLContext sslContext = TlsTestUtils.verifiedSslContext(); + ConnectionFactory cf = TestUtils.connectionFactory(); + cf.useBlockingIo(); + cf.useSslProtocol(sslContext); + + SocketConfigurator sc = + cf.getSocketConfigurator() + .andThen( + s -> { + if (s instanceof SSLSocket) { + SSLSocket sslSocket = (SSLSocket) s; + SSLParameters sslParameters = sslSocket.getSSLParameters(); + sslParameters = sslParameters == null ? new SSLParameters() : sslParameters; + // to compile on Java < 20 + TlsTestUtils.setNamesGroups(sslParameters, new String[] {group}); + sslParameters.setCipherSuites(new String[] {cipher}); + sslSocket.setSSLParameters(sslParameters); + socket.set(sslSocket); + } + }); + cf.setSocketConfigurator(sc); + + try (Connection conn = conn(cf)) { + assertTrue(conn.isOpen()); + assertThat(socket).doesNotHaveNullValue(); + SSLSession session = socket.get().getSession(); + assertThat(session.getCipherSuite()).isEqualTo(cipher); + assertThat(session.getProtocol()).isEqualTo(protocol); + } + } + + @Test + @ErlangVersionAtLeast(28) + void pqcBlockingIoBouncyCastle() throws Exception { + AtomicReference socket = new AtomicReference<>(); + SSLContext sslContext = + TlsTestUtils.verifiedSslContext( + () -> SSLContext.getInstance("TLS", BouncyCastleJsseProvider.PROVIDER_NAME)); + ConnectionFactory cf = TestUtils.connectionFactory(); + cf.useBlockingIo(); + cf.useSslProtocol(sslContext); + + SocketConfigurator sc = + cf.getSocketConfigurator() + .andThen( + s -> { + if (s instanceof SSLSocket) { + SSLSocket sslSocket = (SSLSocket) s; + SSLParameters sslParameters = sslSocket.getSSLParameters(); + sslParameters = sslParameters == null ? new SSLParameters() : sslParameters; + // to compile on Java < 20 + TlsTestUtils.setNamesGroups(sslParameters, new String[] {group}); + sslParameters.setCipherSuites(new String[] {cipher}); + // Bouncycastle is stricter than JSSE for hostname verification + sslParameters.setServerNames( + Collections.singletonList(new SNIHostName(hostname()))); + sslSocket.setSSLParameters(sslParameters); + socket.set(sslSocket); + } + }); + cf.setSocketConfigurator(sc); + try (Connection conn = conn(cf)) { + assertTrue(conn.isOpen()); + assertThat(socket).doesNotHaveNullValue(); + SSLSession session = socket.get().getSession(); + assertThat(session.getCipherSuite()).isEqualTo(cipher); + assertThat(session.getProtocol()).isEqualTo(protocol); + } + } + + @Test + @EnabledForJreRange(minVersion = 27) + @ErlangVersionAtLeast(28) + void pqcNettyJsse() throws Exception { + SslContext context = + SslContextBuilder.forClient() + .sslProvider(SslProvider.JDK) + .trustManager(caCertificate()) + .keyManager(clientKey(), clientCertificate()) + .ciphers(singletonList(cipher)) + .build(); + + ConnectionFactory cf = TestUtils.connectionFactory(); + AtomicReference channel = new AtomicReference<>(); + cf.netty() + .sslContext(context) + .channelCustomizer( + ch -> { + channel.set(ch); + SslHandler sslHandler = ch.pipeline().get(SslHandler.class); + if (sslHandler != null) { + SSLParameters sslParams = sslHandler.engine().getSSLParameters(); + // to compile on Java < 20 + TlsTestUtils.setNamesGroups(sslParams, new String[] {group}); + sslHandler.engine().setSSLParameters(sslParams); + } + }); + + try (Connection conn = conn(cf)) { + assertTrue(conn.isOpen()); + assertThat(channel).doesNotHaveNullValue(); + Channel ch = channel.get(); + SslHandler sslHandler = ch.pipeline().get(SslHandler.class); + assertThat(sslHandler).isNotNull(); + SSLSession session = sslHandler.engine().getSession(); + assertThat(session.getCipherSuite()).isEqualTo(cipher); + assertThat(session.getProtocol()).isEqualTo(protocol); + } + } + + @Test + @ErlangVersionAtLeast(28) + void pqcNettyOpenSsl() throws Exception { + SslContext context = + SslContextBuilder.forClient() + .sslProvider(SslProvider.OPENSSL) + .trustManager(caCertificate()) + .keyManager(clientKey(), clientCertificate()) + .option(OpenSslContextOption.GROUPS, new String[] {group}) + .ciphers(singletonList(cipher)) + .build(); + + ConnectionFactory cf = TestUtils.connectionFactory(); + AtomicReference channel = new AtomicReference<>(); + cf.netty().sslContext(context).channelCustomizer(channel::set); + + try (Connection conn = conn(cf)) { + assertTrue(conn.isOpen()); + assertThat(channel).doesNotHaveNullValue(); + Channel ch = channel.get(); + SslHandler sslHandler = ch.pipeline().get(SslHandler.class); + assertThat(sslHandler).isNotNull(); + SSLSession session = sslHandler.engine().getSession(); + assertThat(session.getCipherSuite()).isEqualTo(cipher); + assertThat(session.getProtocol()).isEqualTo(protocol); + assertThat(session).isInstanceOf(OpenSslSession.class); + OpenSslSession openSslSession = (OpenSslSession) session; + assertThat(openSslSession.getNamedGroup()) + .as("Negotiated TLS key exchange group") + .isEqualTo(group); + } + } + + @Test + @ErlangVersionAtLeast(28) + void pqcNettyBouncyCastle() throws Exception { + java.security.Provider bcJsseProvider = new BouncyCastleJsseProvider(); + SslContext context = + SslContextBuilder.forClient() + .sslProvider(SslProvider.JDK) + .sslContextProvider(bcJsseProvider) + .trustManager(caCertificate()) + .keyManager(clientKey(), clientCertificate()) + .ciphers(singletonList(cipher)) + .build(); + + ConnectionFactory cf = TestUtils.connectionFactory(); + AtomicReference channel = new AtomicReference<>(); + cf.netty() + .sslContext(context) + .channelCustomizer( + ch -> { + channel.set(ch); + SslHandler sslHandler = ch.pipeline().get(SslHandler.class); + if (sslHandler != null) { + SSLParameters sslParams = sslHandler.engine().getSSLParameters(); + // to compile on Java < 20 + TlsTestUtils.setNamesGroups(sslParams, new String[] {group}); + sslHandler.engine().setSSLParameters(sslParams); + } + }); + + try (Connection conn = conn(cf)) { + assertTrue(conn.isOpen()); + assertThat(channel).doesNotHaveNullValue(); + Channel ch = channel.get(); + SslHandler sslHandler = ch.pipeline().get(SslHandler.class); + assertThat(sslHandler).isNotNull(); + SSLSession session = sslHandler.engine().getSession(); + assertThat(session.getCipherSuite()).isEqualTo(cipher); + assertThat(session.getProtocol()).isEqualTo(protocol); + assertThat(session.getClass().getName()).containsIgnoringCase("bouncycastle"); + } + } + + private static Connection conn(ConnectionFactory cf) throws IOException, TimeoutException { + return cf.newConnection( + () -> + singletonList(new Address("localhost", ConnectionFactory.DEFAULT_AMQP_OVER_SSL_PORT))); + } +} diff --git a/src/test/java/com/rabbitmq/client/test/ssl/SslTestSuite.java b/src/test/java/com/rabbitmq/client/test/ssl/SslTestSuite.java index b6a7e85a1..99c9a204f 100644 --- a/src/test/java/com/rabbitmq/client/test/ssl/SslTestSuite.java +++ b/src/test/java/com/rabbitmq/client/test/ssl/SslTestSuite.java @@ -28,7 +28,8 @@ NioTlsUnverifiedConnection.class, HostnameVerification.class, TlsConnectionLogging.class, - SslContextFactoryTest.class + SslContextFactoryTest.class, + Pqc.class }) public class SslTestSuite { diff --git a/src/test/java/com/rabbitmq/client/test/ssl/TlsConnectionLogging.java b/src/test/java/com/rabbitmq/client/test/ssl/TlsConnectionLogging.java index 6c7a978b7..cf995418e 100644 --- a/src/test/java/com/rabbitmq/client/test/ssl/TlsConnectionLogging.java +++ b/src/test/java/com/rabbitmq/client/test/ssl/TlsConnectionLogging.java @@ -22,6 +22,7 @@ import com.rabbitmq.client.test.TestUtils; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslHandler; +import io.netty.handler.ssl.SslProvider; import org.assertj.core.api.Assertions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -71,6 +72,7 @@ public static Function> netty() { sslEngineCaptor.set(sslHandler.engine()); }) .sslContext(SslContextBuilder.forClient() + .sslProvider(SslProvider.JDK) .trustManager(TlsTestUtils.ALWAYS_TRUST_MANAGER) .build()); } catch (SSLException e) { diff --git a/src/test/java/com/rabbitmq/client/test/ssl/TlsTestUtils.java b/src/test/java/com/rabbitmq/client/test/ssl/TlsTestUtils.java index 2e56c9a03..97449b31a 100644 --- a/src/test/java/com/rabbitmq/client/test/ssl/TlsTestUtils.java +++ b/src/test/java/com/rabbitmq/client/test/ssl/TlsTestUtils.java @@ -25,21 +25,33 @@ import io.netty.handler.ssl.IdentityCipherSuiteFilter; import io.netty.handler.ssl.JdkSslContext; import io.netty.handler.ssl.SslContext; +import io.netty.util.internal.PlatformDependent; +import java.io.File; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.file.Files; import java.nio.file.Paths; +import java.security.AccessController; +import java.security.KeyFactory; import java.security.KeyStore; import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PrivilegedAction; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.security.spec.PKCS8EncodedKeySpec; import java.util.Arrays; +import java.util.Base64; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; @@ -142,6 +154,33 @@ static X509Certificate caCertificate() throws Exception { return loadCertificate(caCertificateFile()); } + static PrivateKey clientKey() throws Exception { + return loadPrivateKey(clientKeyFile()); + } + + static PrivateKey loadPrivateKey(String filename) throws Exception { + File file = new File(filename); + String key = new String(Files.readAllBytes(file.toPath()), US_ASCII); + + String privateKeyPEM = + key.replace("-----BEGIN PRIVATE KEY-----", "") + .replaceAll(System.lineSeparator(), "") + .replace("-----END PRIVATE KEY-----", ""); + + byte[] decoded = Base64.getDecoder().decode(privateKeyPEM); + + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decoded); + PrivateKey privateKey = keyFactory.generatePrivate(keySpec); + return privateKey; + } + + static String clientKeyFile() { + return tlsArtefactPath( + System.getProperty( + "client.key", "./rabbitmq-configuration/tls/client_" + hostname() + "_key.pem")); + } + static String caCertificateFile() { return tlsArtefactPath( System.getProperty("ca.certificate", "./rabbitmq-configuration/tls/ca_certificate.pem")); @@ -209,7 +248,7 @@ private static String tlsArtefactPath(String in) { return in.replace("$(hostname)", hostname()).replace("$(hostname -s)", hostname()); } - private static String hostname() { + static String hostname() { try { return InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { @@ -236,4 +275,46 @@ public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } } + + // from Netty's OpenSslParametersUtil + private static final MethodHandle SET_NAMED_GROUPS; + + static { + MethodHandle setNamedGroups = null; + if (PlatformDependent.javaVersion() >= 20) { + final MethodHandles.Lookup lookup = MethodHandles.lookup(); + setNamedGroups = + obtainHandle(lookup, "setNamedGroups", MethodType.methodType(void.class, String[].class)); + } + SET_NAMED_GROUPS = setNamedGroups; + } + + @SuppressWarnings("removal") + private static MethodHandle obtainHandle( + final MethodHandles.Lookup lookup, final String methodName, final MethodType type) { + return AccessController.doPrivileged( + (PrivilegedAction) + () -> { + try { + return lookup.findVirtual(SSLParameters.class, methodName, type); + } catch (UnsupportedOperationException + | SecurityException + | NoSuchMethodException + | IllegalAccessException e) { + // Just ignore it. + return null; + } + }); + } + + static void setNamesGroups(SSLParameters parameters, String[] names) { + if (SET_NAMED_GROUPS == null) { + return; + } + try { + SET_NAMED_GROUPS.invoke(parameters, names); + } catch (Throwable ignore) { + // Ignore + } + } } From 7a94a03db12fefeb1396c47c70d7f7b4dea372b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Cogolu=C3=A8gnes?= <514737+acogoluegnes@users.noreply.github.com> Date: Thu, 17 Sep 2026 08:05:45 +0200 Subject: [PATCH 03/14] Set preferred TLS protocol to version 1.3 And fallback to 1.2. (cherry picked from commit dcf158b5b69443035cc4966a1ce4edebb9dc09a5) --- src/main/java/com/rabbitmq/client/ConnectionFactory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/rabbitmq/client/ConnectionFactory.java b/src/main/java/com/rabbitmq/client/ConnectionFactory.java index 665a13531..c5c5f79bd 100644 --- a/src/main/java/com/rabbitmq/client/ConnectionFactory.java +++ b/src/main/java/com/rabbitmq/client/ConnectionFactory.java @@ -120,9 +120,9 @@ public class ConnectionFactory implements Cloneable { /** The default timeout for work pool enqueueing: no timeout */ public static final int DEFAULT_WORK_POOL_TIMEOUT = -1; - private static final String PREFERRED_TLS_PROTOCOL = "TLSv1.2"; + private static final String PREFERRED_TLS_PROTOCOL = "TLSv1.3"; - private static final String FALLBACK_TLS_PROTOCOL = "TLSv1"; + private static final String FALLBACK_TLS_PROTOCOL = "TLSv1.2"; private String virtualHost = DEFAULT_VHOST; private String host = DEFAULT_HOST; From 218893d470fe1a0b48929de907ff61cd91488b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Cogolu=C3=A8gnes?= <514737+acogoluegnes@users.noreply.github.com> Date: Thu, 17 Sep 2026 16:32:16 +0200 Subject: [PATCH 04/14] Use Erlang 28 image for RabbitMQ 4.4 alpha (cherry picked from commit 2b53128b7b07e96b353c35ba0393bee414da9f27) --- .github/workflows/test-rabbitmq-alphas.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-rabbitmq-alphas.yml b/.github/workflows/test-rabbitmq-alphas.yml index d364cd24d..0b7affd2e 100644 --- a/.github/workflows/test-rabbitmq-alphas.yml +++ b/.github/workflows/test-rabbitmq-alphas.yml @@ -28,7 +28,7 @@ jobs: - rabbitmq-image: pivotalrabbitmq/rabbitmq:v4.3.x-otp27 erlang-version: 27 rabbitmq-branch: 4.3 - - rabbitmq-image: pivotalrabbitmq/rabbitmq:main-otp27 + - rabbitmq-image: pivotalrabbitmq/rabbitmq:main-otp28 erlang-version: 28 rabbitmq-branch: 4.4 name: Test against ${{ matrix.rabbitmq-image }} From 4e83d128bd1d9c8e38ac1c7de2f8c10e3ef76620 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Cogolu=C3=A8gnes?= <514737+acogoluegnes@users.noreply.github.com> Date: Fri, 18 Sep 2026 15:39:01 +0200 Subject: [PATCH 05/14] Use Bouncy Castle API in test To illustrate how to configure PQC without depending on Java 20 or more. (cherry picked from commit 5d9ad89d110577197c3d8ba317a55383af9c7c1f) --- .../com/rabbitmq/client/test/ssl/Pqc.java | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/test/java/com/rabbitmq/client/test/ssl/Pqc.java b/src/test/java/com/rabbitmq/client/test/ssl/Pqc.java index 15215ef61..b1292cf0b 100644 --- a/src/test/java/com/rabbitmq/client/test/ssl/Pqc.java +++ b/src/test/java/com/rabbitmq/client/test/ssl/Pqc.java @@ -38,15 +38,17 @@ import io.netty.handler.ssl.SslProvider; import java.io.IOException; import java.security.Security; -import java.util.Collections; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; -import javax.net.ssl.SNIHostName; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jsse.BCSNIHostName; +import org.bouncycastle.jsse.BCSSLEngine; +import org.bouncycastle.jsse.BCSSLParameters; +import org.bouncycastle.jsse.BCSSLSocket; import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -110,27 +112,27 @@ void pqcBlockingIoBouncyCastle() throws Exception { AtomicReference socket = new AtomicReference<>(); SSLContext sslContext = TlsTestUtils.verifiedSslContext( - () -> SSLContext.getInstance("TLS", BouncyCastleJsseProvider.PROVIDER_NAME)); + () -> SSLContext.getInstance(protocol, BouncyCastleJsseProvider.PROVIDER_NAME)); ConnectionFactory cf = TestUtils.connectionFactory(); cf.useBlockingIo(); cf.useSslProtocol(sslContext); + cf.setSocketFactory(sslContext.getSocketFactory()); SocketConfigurator sc = cf.getSocketConfigurator() .andThen( s -> { - if (s instanceof SSLSocket) { - SSLSocket sslSocket = (SSLSocket) s; - SSLParameters sslParameters = sslSocket.getSSLParameters(); - sslParameters = sslParameters == null ? new SSLParameters() : sslParameters; - // to compile on Java < 20 - TlsTestUtils.setNamesGroups(sslParameters, new String[] {group}); + if (s instanceof BCSSLSocket) { + // use BC API, to set named groups with Java < 20 + BCSSLSocket sslSocket = (BCSSLSocket) s; + BCSSLParameters sslParameters = sslSocket.getParameters(); + sslParameters = sslParameters == null ? new BCSSLParameters() : sslParameters; sslParameters.setCipherSuites(new String[] {cipher}); + sslParameters.setNamedGroups(new String[] {group}); // Bouncycastle is stricter than JSSE for hostname verification - sslParameters.setServerNames( - Collections.singletonList(new SNIHostName(hostname()))); - sslSocket.setSSLParameters(sslParameters); - socket.set(sslSocket); + sslParameters.setServerNames(singletonList(new BCSNIHostName(hostname()))); + sslSocket.setParameters(sslParameters); + socket.set((SSLSocket) s); } }); cf.setSocketConfigurator(sc); @@ -235,13 +237,14 @@ void pqcNettyBouncyCastle() throws Exception { .sslContext(context) .channelCustomizer( ch -> { - channel.set(ch); SslHandler sslHandler = ch.pipeline().get(SslHandler.class); - if (sslHandler != null) { - SSLParameters sslParams = sslHandler.engine().getSSLParameters(); - // to compile on Java < 20 - TlsTestUtils.setNamesGroups(sslParams, new String[] {group}); - sslHandler.engine().setSSLParameters(sslParams); + if (sslHandler != null && sslHandler.engine() instanceof BCSSLEngine) { + channel.set(ch); + // use BC API, to set named groups with Java < 20 + BCSSLEngine engine = (BCSSLEngine) sslHandler.engine(); + BCSSLParameters sslParams = engine.getParameters(); + sslParams.setNamedGroups(new String[] {group}); + engine.setParameters(sslParams); } }); From 25fad817291feff3195c32620117d295598f8b41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Cogolu=C3=A8gnes?= <514737+acogoluegnes@users.noreply.github.com> Date: Mon, 21 Sep 2026 08:48:45 +0200 Subject: [PATCH 06/14] Harden (deprecated) JSON reader --- .../com/rabbitmq/tools/json/JSONReader.java | 7 ++++++- .../rabbitmq/tools/jsonrpc/JsonRpcServer.java | 6 ++++-- .../client/test/JSONReadWriteTest.java | 20 +++++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/rabbitmq/tools/json/JSONReader.java b/src/main/java/com/rabbitmq/tools/json/JSONReader.java index ab3c4fc84..a91e7e522 100644 --- a/src/main/java/com/rabbitmq/tools/json/JSONReader.java +++ b/src/main/java/com/rabbitmq/tools/json/JSONReader.java @@ -87,7 +87,7 @@ private void skipWhiteSpace() { next(); } else if (c == '/' && next() == '/') { - while (c != '\n') { + while (c != '\n' && c != CharacterIterator.DONE) { next(); } } @@ -208,6 +208,11 @@ private Object number() { private Object string(char sep) { buf.setLength(0); while (c != sep) { + if (c == CharacterIterator.DONE) { + throw new IllegalStateException( + "Unterminated string while parsing JSON (around character " + + (it.getIndex() - it.getBeginIndex()) + ")"); + } if (c == '\\') { next(); if (c == 'u') { diff --git a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java index 843bc85e6..394c48adc 100644 --- a/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java +++ b/src/main/java/com/rabbitmq/tools/jsonrpc/JsonRpcServer.java @@ -185,8 +185,10 @@ public String doCall(String requestBody) { } } } - } catch (ClassCastException cce) { - // Bogus request! + } catch (RuntimeException e) { + // Bogus request! Note this also covers a request body that the mapper + // cannot parse at all: such a request must not take the server down. + LOGGER.debug("Error while processing JSON RPC request", e); response = errorResponse(null, 400, "Bad Request", null); } diff --git a/src/test/java/com/rabbitmq/client/test/JSONReadWriteTest.java b/src/test/java/com/rabbitmq/client/test/JSONReadWriteTest.java index 027f2ab50..ecc5d14ce 100644 --- a/src/test/java/com/rabbitmq/client/test/JSONReadWriteTest.java +++ b/src/test/java/com/rabbitmq/client/test/JSONReadWriteTest.java @@ -17,11 +17,13 @@ package com.rabbitmq.client.test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.fail; import com.rabbitmq.tools.json.JSONReader; import com.rabbitmq.tools.json.JSONWriter; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; public class JSONReadWriteTest { @@ -109,4 +111,22 @@ public void readWriteSimple() throws Exception { } + @Test + @Timeout(10) + public void truncatedInputInsideStringShouldThrow() { + for (String json : new String[] { + "{\"method\":\"x", "\"", "'abc", "{\"a\":\"b\\\\", "[\"x" }) { + assertThrows(IllegalStateException.class, () -> new JSONReader().read(json), json); + } + } + + @Test + @Timeout(10) + public void truncatedLineCommentShouldThrow() { + assertThrows(IllegalStateException.class, () -> new JSONReader().read("//")); + assertThrows(IllegalStateException.class, () -> new JSONReader().read("// no newline")); + // a terminated comment is still skipped + assertEquals(1, ((java.util.Map) new JSONReader().read("// c\n{\"a\":1}")).get("a")); + } + } From 12197bcb35b16878364791116af8c632bb014cf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Cogolu=C3=A8gnes?= <514737+acogoluegnes@users.noreply.github.com> Date: Wed, 23 Sep 2026 11:20:09 +0200 Subject: [PATCH 07/14] ci: test against 4.4 alphas (cherry picked from commit 35ed444d3f0d9d5d4f5500453aa6ad19d4bf41e4) --- .github/workflows/test-rabbitmq-alphas.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-rabbitmq-alphas.yml b/.github/workflows/test-rabbitmq-alphas.yml index 0b7affd2e..a9db62bc6 100644 --- a/.github/workflows/test-rabbitmq-alphas.yml +++ b/.github/workflows/test-rabbitmq-alphas.yml @@ -28,7 +28,7 @@ jobs: - rabbitmq-image: pivotalrabbitmq/rabbitmq:v4.3.x-otp27 erlang-version: 27 rabbitmq-branch: 4.3 - - rabbitmq-image: pivotalrabbitmq/rabbitmq:main-otp28 + - rabbitmq-image: pivotalrabbitmq/rabbitmq:v4.4.x-otp27 erlang-version: 28 rabbitmq-branch: 4.4 name: Test against ${{ matrix.rabbitmq-image }} From b5572cf88dad65255c76256e1fbebb1f2525203f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Cogolu=C3=A8gnes?= <514737+acogoluegnes@users.noreply.github.com> Date: Wed, 23 Sep 2026 11:22:45 +0200 Subject: [PATCH 08/14] ci: test on Ubuntu 26.04 (cherry picked from commit 4d865561ac5c3c519a2a0892d2025710d3e22e9c) --- .github/workflows/publish-snapshot.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/test-rabbitmq-alphas.yml | 2 +- .github/workflows/test-supported-java-versions-5.x.yml | 2 +- .github/workflows/test-supported-java-versions-main.yml | 2 +- .github/workflows/test.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml index c0d9168fc..a9f098c70 100644 --- a/.github/workflows/publish-snapshot.yml +++ b/.github/workflows/publish-snapshot.yml @@ -4,7 +4,7 @@ on: workflow_dispatch jobs: build: - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 steps: - uses: actions/checkout@v7 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c4573c680..9f75c8ece 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,7 +5,7 @@ on: jobs: build: - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 steps: - uses: actions/checkout@v7 diff --git a/.github/workflows/test-rabbitmq-alphas.yml b/.github/workflows/test-rabbitmq-alphas.yml index a9db62bc6..7ee8fb223 100644 --- a/.github/workflows/test-rabbitmq-alphas.yml +++ b/.github/workflows/test-rabbitmq-alphas.yml @@ -21,7 +21,7 @@ on: jobs: build: - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 strategy: matrix: include: diff --git a/.github/workflows/test-supported-java-versions-5.x.yml b/.github/workflows/test-supported-java-versions-5.x.yml index aaef4c0eb..d1378b5e2 100644 --- a/.github/workflows/test-supported-java-versions-5.x.yml +++ b/.github/workflows/test-supported-java-versions-5.x.yml @@ -7,7 +7,7 @@ on: jobs: build: - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 strategy: matrix: distribution: [ 'temurin' ] diff --git a/.github/workflows/test-supported-java-versions-main.yml b/.github/workflows/test-supported-java-versions-main.yml index 2170f3b30..33c858f7b 100644 --- a/.github/workflows/test-supported-java-versions-main.yml +++ b/.github/workflows/test-supported-java-versions-main.yml @@ -7,7 +7,7 @@ on: jobs: build: - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 strategy: matrix: distribution: [ 'temurin' ] diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0f6a0692a..e1d04be8c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ on: jobs: build: - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 steps: - uses: actions/checkout@v7 From a1f294af6f69abdb6952f9824222ab25a474dffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Cogolu=C3=A8gnes?= <514737+acogoluegnes@users.noreply.github.com> Date: Wed, 23 Sep 2026 14:16:15 +0200 Subject: [PATCH 09/14] ci: use Erlang 28 alphas for 4.4 (cherry picked from commit 459baff3272d23c3f552fa11e45398dded625a96) --- .github/workflows/test-rabbitmq-alphas.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-rabbitmq-alphas.yml b/.github/workflows/test-rabbitmq-alphas.yml index 7ee8fb223..1d6243159 100644 --- a/.github/workflows/test-rabbitmq-alphas.yml +++ b/.github/workflows/test-rabbitmq-alphas.yml @@ -28,7 +28,7 @@ jobs: - rabbitmq-image: pivotalrabbitmq/rabbitmq:v4.3.x-otp27 erlang-version: 27 rabbitmq-branch: 4.3 - - rabbitmq-image: pivotalrabbitmq/rabbitmq:v4.4.x-otp27 + - rabbitmq-image: pivotalrabbitmq/rabbitmq:v4.4.x-otp28 erlang-version: 28 rabbitmq-branch: 4.4 name: Test against ${{ matrix.rabbitmq-image }} From 2d7a009eeeda7912c65cbf920f01a3db48e51bca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Sep 2026 00:33:21 +0000 Subject: [PATCH 10/14] Bump com.fasterxml.jackson.core:jackson-databind from 2.22.2 to 2.22.3 Bumps [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson-databind) from 2.22.2 to 2.22.3. - [Commits](https://github.com/FasterXML/jackson-databind/compare/jackson-databind-2.22.2...jackson-databind-2.22.3) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-version: 2.22.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dd2786d87..a158347b6 100644 --- a/pom.xml +++ b/pom.xml @@ -60,7 +60,7 @@ 4.2.40 1.17.1 1.66.0 - 2.22.2 + 2.22.3 1.2.13 5.14.4 5.23.0 From 50b27c541eb68b825b3833c3f9327e5ea2a4006b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 00:41:08 +0000 Subject: [PATCH 11/14] Bump org.mockito:mockito-core from 5.23.0 to 5.24.0 Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.23.0 to 5.24.0. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v5.23.0...v5.24.0) --- updated-dependencies: - dependency-name: org.mockito:mockito-core dependency-version: 5.24.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a158347b6..47b7642ab 100644 --- a/pom.xml +++ b/pom.xml @@ -63,7 +63,7 @@ 2.22.3 1.2.13 5.14.4 - 5.23.0 + 5.24.0 3.27.7 1.7.1 1.0.4 From a54430968cae334389a7f0a12c2eac6492ab4f11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 00:35:28 +0000 Subject: [PATCH 12/14] Bump com.diffplug.spotless:spotless-maven-plugin from 3.10.2 to 3.10.3 Bumps [com.diffplug.spotless:spotless-maven-plugin](https://github.com/diffplug/spotless) from 3.10.2 to 3.10.3. - [Release notes](https://github.com/diffplug/spotless/releases) - [Changelog](https://github.com/diffplug/spotless/blob/main/CHANGES.md) - [Commits](https://github.com/diffplug/spotless/compare/maven/3.10.2...maven/3.10.3) --- updated-dependencies: - dependency-name: com.diffplug.spotless:spotless-maven-plugin dependency-version: 3.10.3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 47b7642ab..0891be883 100644 --- a/pom.xml +++ b/pom.xml @@ -90,7 +90,7 @@ 1.11 0.11.0 1.4 - 3.10.2 + 3.10.3 1.36.1 2.0.84.Final