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