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

acosentino pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 79fbb874aca8 CAMEL-23263 - Camel-Netty: PQC-capable SSL fallback with 
TLSv1.3 (#22296)
79fbb874aca8 is described below

commit 79fbb874aca8b53fa9f3483d957871d9d37a2ba1
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Mar 27 18:23:41 2026 +0100

    CAMEL-23263 - Camel-Netty: PQC-capable SSL fallback with TLSv1.3 (#22296)
    
    * 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]>
    
    * CAMEL-23263 - Camel-Netty: Make SSL fallback path PQC-capable with 
TLSv1.3 and named groups auto-configuration
    
    Address gnodet's review on PR #22296:
    
    - Improve PQC constants cross-reference comment to explicitly name
      SSLContextParameters.PQC_NAMED_GROUP and PQC_PREFERRED_NAMED_GROUPS
    - Deprecate unused createServerSSLEngine/createClientSSLEngine methods
      (zero callers) and apply PQC named groups in them for API consistency
    - Verify secondary preferred ordering in test (x25519 before secp256r1)
    
    Signed-off-by: Andrea Cosentino <[email protected]>
    
    ---------
    
    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      |  95 ++++++++++++++++++-
 .../component/netty/SSLEngineFactoryTest.java      | 102 +++++++++++++++++++++
 7 files changed, 206 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 1460decfcd4d..6eb72c7809d3 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 4d31ff93e468..ea071d6e8195 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
@@ -180,6 +180,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 1b18f3f61461..4d42846f0abb 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 a3d73b991c4f..78b85af20f9e 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..964e38cd1f1c 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,51 @@
 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 — keep in sync with 
SSLContextParameters.PQC_NAMED_GROUP
+    // and SSLContextParameters.PQC_PREFERRED_NAMED_GROUPS
+    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,16 +103,78 @@ 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());
+        }
+    }
+
+    /**
+     * @deprecated Unused — all initializer factories create SSLEngines 
directly via
+     *             {@code sslContext.createSSLEngine()}. Use {@link 
#applyPqcNamedGroups(SSLEngine)} on engines created
+     *             directly from the SSLContext instead.
+     */
+    @Deprecated(since = "4.19.0")
     public SSLEngine createServerSSLEngine(SSLContext sslContext) {
         SSLEngine serverEngine = sslContext.createSSLEngine();
         serverEngine.setUseClientMode(false);
         serverEngine.setNeedClientAuth(true);
+        applyPqcNamedGroups(serverEngine);
         return serverEngine;
     }
 
+    /**
+     * @deprecated Unused — all initializer factories create SSLEngines 
directly via
+     *             {@code sslContext.createSSLEngine()}. Use {@link 
#applyPqcNamedGroups(SSLEngine)} on engines created
+     *             directly from the SSLContext instead.
+     */
+    @Deprecated(since = "4.19.0")
     public SSLEngine createClientSSLEngine(SSLContext sslContext) {
         SSLEngine clientEngine = sslContext.createSSLEngine();
         clientEngine.setUseClientMode(true);
+        applyPqcNamedGroups(clientEngine);
         return clientEngine;
     }
 
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..a3300dd1f4d9
--- /dev/null
+++ 
b/components/camel-netty/src/test/java/org/apache/camel/component/netty/SSLEngineFactoryTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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 java.util.Arrays;
+import java.util.List;
+
+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;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+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) {
+                List<String> groupList = Arrays.asList(groups);
+                boolean pqcAvailable = groupList.contains("X25519MLKEM768");
+                if (pqcAvailable) {
+                    // Verify preferred ordering: X25519MLKEM768, x25519, 
secp256r1, secp384r1
+                    assertEquals("X25519MLKEM768", groups[0],
+                            "PQC named group should be first when available");
+                    int x25519Idx = groupList.indexOf("x25519");
+                    int secp256r1Idx = groupList.indexOf("secp256r1");
+                    if (x25519Idx >= 0 && secp256r1Idx >= 0) {
+                        assertTrue(x25519Idx < secp256r1Idx,
+                                "x25519 should appear before secp256r1 in 
preferred ordering");
+                    }
+                }
+            }
+        } 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());
+    }
+}

Reply via email to