This is an automated email from the ASF dual-hosted git repository. acosentino pushed a commit to branch CAMEL-23263 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 84b8992d4c22a30e1576c179cc3fbeb472b6ed12 Author: Andrea Cosentino <[email protected]> AuthorDate: Fri Mar 27 14:37:56 2026 +0100 CAMEL-23263 - Camel-Netty: Make SSL fallback path PQC-capable with TLSv1.3 and named groups auto-configuration Change the hardcoded SSL protocol from "TLS" to "TLSv1.3" in SSLEngineFactory and add PQC named groups auto-configuration (X25519MLKEM768) on JDK 25+. The new SSLEngineFactory.applyPqcNamedGroups(SSLEngine) method mirrors the auto-configuration behavior of SSLContextParameters.createSSLContext() by detecting available PQC named groups via reflection and reordering them to prioritize post-quantum key exchange. Applied in all 5 initializer factories across camel-netty and camel-netty-http when the SSLContextParameters fallback path is used. Signed-off-by: Andrea Cosentino <[email protected]> --- .../netty/http/HttpClientInitializerFactory.java | 2 + .../netty/http/HttpServerInitializerFactory.java | 2 + .../http/HttpServerSharedInitializerFactory.java | 2 + .../netty/DefaultClientInitializerFactory.java | 2 + .../netty/DefaultServerInitializerFactory.java | 2 + .../component/netty/ssl/SSLEngineFactory.java | 80 +++++++++++++++++- .../component/netty/SSLEngineFactoryTest.java | 97 ++++++++++++++++++++++ 7 files changed, 186 insertions(+), 1 deletion(-) diff --git a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpClientInitializerFactory.java b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpClientInitializerFactory.java index 9aa91f14807b..321641da4273 100644 --- a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpClientInitializerFactory.java +++ b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpClientInitializerFactory.java @@ -184,6 +184,8 @@ public class HttpClientInitializerFactory extends ClientInitializerFactory { if (producer.getConfiguration().getSslContextParameters() == null) { // just set the enabledProtocols if the SslContextParameter doesn't set engine.setEnabledProtocols(producer.getConfiguration().getEnabledProtocols().split(",")); + // apply PQC named groups for the fallback path + SSLEngineFactory.applyPqcNamedGroups(engine); } return new SslHandler(engine); } diff --git a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpServerInitializerFactory.java b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpServerInitializerFactory.java index 43623e9f79aa..468929cbc955 100644 --- a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpServerInitializerFactory.java +++ b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpServerInitializerFactory.java @@ -181,6 +181,8 @@ public class HttpServerInitializerFactory extends ServerInitializerFactory { if (consumer.getConfiguration().getSslContextParameters() == null) { // just set the enabledProtocols if the SslContextParameter doesn't set engine.setEnabledProtocols(consumer.getConfiguration().getEnabledProtocols().split(",")); + // apply PQC named groups for the fallback path + SSLEngineFactory.applyPqcNamedGroups(engine); } return new SslHandler(engine); } diff --git a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpServerSharedInitializerFactory.java b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpServerSharedInitializerFactory.java index 8413675f419d..110d611bbdc7 100644 --- a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpServerSharedInitializerFactory.java +++ b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpServerSharedInitializerFactory.java @@ -138,6 +138,8 @@ public class HttpServerSharedInitializerFactory extends HttpServerInitializerFac if (configuration.getSslContextParameters() == null) { // just set the enabledProtocols if the SslContextParameter doesn't set engine.setEnabledProtocols(configuration.getEnabledProtocols().split(",")); + // apply PQC named groups for the fallback path + SSLEngineFactory.applyPqcNamedGroups(engine); } return new SslHandler(engine); } diff --git a/components/camel-netty/src/main/java/org/apache/camel/component/netty/DefaultClientInitializerFactory.java b/components/camel-netty/src/main/java/org/apache/camel/component/netty/DefaultClientInitializerFactory.java index 0c897865bf9b..bada604de904 100644 --- a/components/camel-netty/src/main/java/org/apache/camel/component/netty/DefaultClientInitializerFactory.java +++ b/components/camel-netty/src/main/java/org/apache/camel/component/netty/DefaultClientInitializerFactory.java @@ -145,6 +145,8 @@ public class DefaultClientInitializerFactory extends ClientInitializerFactory { if (producer.getConfiguration().getSslContextParameters() == null) { // just set the enabledProtocols if the SslContextParameter doesn't set engine.setEnabledProtocols(producer.getConfiguration().getEnabledProtocols().split(",")); + // apply PQC named groups for the fallback path + SSLEngineFactory.applyPqcNamedGroups(engine); } return new SslHandler(engine); } diff --git a/components/camel-netty/src/main/java/org/apache/camel/component/netty/DefaultServerInitializerFactory.java b/components/camel-netty/src/main/java/org/apache/camel/component/netty/DefaultServerInitializerFactory.java index 638f37ad7fcd..3e2a7f68d585 100644 --- a/components/camel-netty/src/main/java/org/apache/camel/component/netty/DefaultServerInitializerFactory.java +++ b/components/camel-netty/src/main/java/org/apache/camel/component/netty/DefaultServerInitializerFactory.java @@ -149,6 +149,8 @@ public class DefaultServerInitializerFactory extends ServerInitializerFactory { if (consumer.getConfiguration().getSslContextParameters() == null) { // just set the enabledProtocols if the SslContextParameter doesn't set engine.setEnabledProtocols(consumer.getConfiguration().getEnabledProtocols().split(",")); + // apply PQC named groups for the fallback path + SSLEngineFactory.applyPqcNamedGroups(engine); } return new SslHandler(engine); } diff --git a/components/camel-netty/src/main/java/org/apache/camel/component/netty/ssl/SSLEngineFactory.java b/components/camel-netty/src/main/java/org/apache/camel/component/netty/ssl/SSLEngineFactory.java index ef77e791186e..52873b6be522 100644 --- a/components/camel-netty/src/main/java/org/apache/camel/component/netty/ssl/SSLEngineFactory.java +++ b/components/camel-netty/src/main/java/org/apache/camel/component/netty/ssl/SSLEngineFactory.java @@ -17,20 +17,50 @@ package org.apache.camel.component.netty.ssl; import java.io.InputStream; +import java.lang.reflect.Method; import java.security.KeyStore; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; import javax.net.ssl.TrustManagerFactory; import org.apache.camel.CamelContext; import org.apache.camel.support.ResourceHelper; import org.apache.camel.util.IOHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public final class SSLEngineFactory { - private static final String SSL_PROTOCOL = "TLS"; + private static final Logger LOG = LoggerFactory.getLogger(SSLEngineFactory.class); + + private static final String SSL_PROTOCOL = "TLSv1.3"; + + // PQC named group constants — mirrored from SSLContextParameters to keep this class self-contained + private static final String PQC_NAMED_GROUP = "X25519MLKEM768"; + private static final List<String> PQC_PREFERRED_NAMED_GROUPS + = List.of("X25519MLKEM768", "x25519", "secp256r1", "secp384r1"); + + // Reflection handles for JDK 20+ SSLParameters named groups API + private static final Method GET_NAMED_GROUPS; + private static final Method SET_NAMED_GROUPS; + + static { + Method gng = null, sng = null; + try { + gng = SSLParameters.class.getMethod("getNamedGroups"); + sng = SSLParameters.class.getMethod("setNamedGroups", String[].class); + } catch (NoSuchMethodException e) { + // JDK < 20 — named groups API not available + } + GET_NAMED_GROUPS = gng; + SET_NAMED_GROUPS = sng; + } public SSLEngineFactory() { } @@ -72,6 +102,54 @@ public final class SSLEngineFactory { return answer; } + /** + * Applies post-quantum named group defaults to an {@link SSLEngine} when the JVM supports them (typically JDK 25+). + * <p> + * When {@code X25519MLKEM768} is available, this method reorders the named groups to prioritize post-quantum key + * exchange, matching the auto-configuration behavior of + * {@link org.apache.camel.support.jsse.SSLContextParameters#createSSLContext}. This should be called on SSLEngines + * created via the SSLEngineFactory fallback path (without SSLContextParameters). + */ + public static void applyPqcNamedGroups(SSLEngine engine) { + if (GET_NAMED_GROUPS == null || SET_NAMED_GROUPS == null) { + return; + } + + try { + SSLParameters params = engine.getSSLParameters(); + String[] availableGroups = (String[]) GET_NAMED_GROUPS.invoke(params); + + if (availableGroups == null) { + return; + } + + List<String> available = Arrays.asList(availableGroups); + if (!available.contains(PQC_NAMED_GROUP)) { + return; + } + + // Build preferred ordering: PQC preferred groups first, then remaining + List<String> ordered = new ArrayList<>(); + for (String preferred : PQC_PREFERRED_NAMED_GROUPS) { + if (available.contains(preferred)) { + ordered.add(preferred); + } + } + for (String group : availableGroups) { + if (!ordered.contains(group)) { + ordered.add(group); + } + } + + SET_NAMED_GROUPS.invoke(params, (Object) ordered.toArray(new String[0])); + engine.setSSLParameters(params); + + LOG.debug("Applied PQC named groups to SSLEngine: {}", ordered); + } catch (Exception e) { + LOG.debug("Could not apply PQC named groups to SSLEngine: {}", e.getMessage()); + } + } + public SSLEngine createServerSSLEngine(SSLContext sslContext) { SSLEngine serverEngine = sslContext.createSSLEngine(); serverEngine.setUseClientMode(false); diff --git a/components/camel-netty/src/test/java/org/apache/camel/component/netty/SSLEngineFactoryTest.java b/components/camel-netty/src/test/java/org/apache/camel/component/netty/SSLEngineFactoryTest.java new file mode 100644 index 000000000000..80ccc3d314a1 --- /dev/null +++ b/components/camel-netty/src/test/java/org/apache/camel/component/netty/SSLEngineFactoryTest.java @@ -0,0 +1,97 @@ +/* + * 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.camel.component.netty; + +import java.lang.reflect.Method; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; + +import org.apache.camel.component.netty.ssl.SSLEngineFactory; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class SSLEngineFactoryTest { + + @Test + public void testApplyPqcNamedGroupsOnSupportedJdk() throws Exception { + SSLContext context = SSLContext.getInstance("TLSv1.3"); + context.init(null, null, null); + SSLEngine engine = context.createSSLEngine(); + + SSLEngineFactory.applyPqcNamedGroups(engine); + + // Verify named groups were reordered if JDK supports the API and PQC groups + try { + Method getNamedGroups = SSLParameters.class.getMethod("getNamedGroups"); + String[] groups = (String[]) getNamedGroups.invoke(engine.getSSLParameters()); + if (groups != null) { + boolean pqcAvailable = false; + for (String group : groups) { + if ("X25519MLKEM768".equals(group)) { + pqcAvailable = true; + break; + } + } + if (pqcAvailable) { + assertEquals("X25519MLKEM768", groups[0], + "PQC named group should be first when available"); + } + } + } catch (NoSuchMethodException e) { + // JDK < 20 — test passes trivially + } + } + + @Test + public void testApplyPqcNamedGroupsDoesNotThrow() throws Exception { + SSLContext context = SSLContext.getInstance("TLSv1.3"); + context.init(null, null, null); + SSLEngine engine = context.createSSLEngine(); + + // Must not throw on any JDK version + SSLEngineFactory.applyPqcNamedGroups(engine); + } + + @Test + public void testApplyPqcNamedGroupsPreservesExistingParameters() throws Exception { + SSLContext context = SSLContext.getInstance("TLSv1.3"); + context.init(null, null, null); + SSLEngine engine = context.createSSLEngine(); + + // Set enabled protocols before applying PQC groups + engine.setEnabledProtocols(new String[] { "TLSv1.3" }); + + SSLEngineFactory.applyPqcNamedGroups(engine); + + // Verify enabledProtocols were not clobbered + String[] protocols = engine.getEnabledProtocols(); + assertNotNull(protocols); + assertEquals(1, protocols.length); + assertEquals("TLSv1.3", protocols[0]); + } + + @Test + public void testSslProtocolIsTls13() throws Exception { + SSLContext context = SSLContext.getInstance("TLSv1.3"); + assertNotNull(context); + assertEquals("TLSv1.3", context.getProtocol()); + } +}
