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());
+    }
+}

Reply via email to