You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ac...@apache.org on 2026/03/27 14:02:10 UTC

(camel) branch CAMEL-23263 created (now 84b8992d4c22)

This is an automated email from the ASF dual-hosted git repository.

acosentino pushed a change to branch CAMEL-23263
in repository https://gitbox.apache.org/repos/asf/camel.git


      at 84b8992d4c22 CAMEL-23263 - Camel-Netty: Make SSL fallback path PQC-capable with TLSv1.3 and named groups auto-configuration

This branch includes the following new commits:

     new 84b8992d4c22 CAMEL-23263 - Camel-Netty: Make SSL fallback path PQC-capable with TLSv1.3 and named groups auto-configuration

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(camel) 01/01: CAMEL-23263 - Camel-Netty: Make SSL fallback path PQC-capable with TLSv1.3 and named groups auto-configuration

Posted by ac...@apache.org.
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 <an...@gmail.com>
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 <an...@gmail.com>
---
 .../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());
+    }
+}