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 0f67ae74860c CAMEL-23154 - Post-Quantum Cryptography (PQC) readiness: 
Add namedGro… (#21864)
0f67ae74860c is described below

commit 0f67ae74860cb0f729f716f7bfe4f77723c519cf
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Mar 9 13:39:22 2026 +0100

    CAMEL-23154 - Post-Quantum Cryptography (PQC) readiness: Add namedGro… 
(#21864)
    
    * CAMEL-23154 - Post-Quantum Cryptography (PQC) readiness: Add namedGroups 
support to BaseSSLContextParameters
    
    Add namedGroups and namedGroupsFilter fields to BaseSSLContextParameters,
    following the existing cipherSuites/cipherSuitesFilter pattern. This allows
    users to configure TLS named groups (key exchange algorithms) including
    post-quantum hybrid groups such as X25519MLKEM768.
    
    Changes:
    - Add NamedGroupsParameters class mirroring CipherSuitesParameters
    - Add namedGroups (NamedGroupsParameters) and namedGroupsFilter
      (FilterParameters) fields with getters/setters
    - Update SSLEngine, SSLSocket, and SSLServerSocket configurers to call
      SSLParameters.setNamedGroups() with configured or filtered values
    - Add configureNamedGroups() helper shared by all three configurers
    - Add debug log messages for named groups configuration
    
    Signed-off-by: Andrea Cosentino <[email protected]>
    
    * CAMEL-23154: Fix named groups implementation - add tests, docs, and XML 
support
    
    - Refactor configureNamedGroups to resolveNamedGroups returning String[]
      instead of Object target + instanceof dispatch, avoiding potential
      setSSLParameters overriding cipher suites and protocols
    - Add NamedGroupsParametersDefinition for Spring XML configuration support
    - Wire namedGroups/namedGroupsFilter in 
AbstractBaseSSLContextParametersFactoryBean
    - Add testNamedGroups, testNamedGroupsFilter, and
      testNamedGroupsDoNotAffectCipherSuitesOrProtocols tests
    - Document namedGroups and namedGroupsFilter in JSSE configuration docs
    
    * CAMEL-23154: Regenerate JAXB index for NamedGroupsParametersDefinition
    
    ---------
    
    Signed-off-by: Andrea Cosentino <[email protected]>
    Co-authored-by: Guillaume Nodet <[email protected]>
---
 .../apache/camel/catalog/schemas/camel-spring.xsd  |  11 ++
 .../support/jsse/BaseSSLContextParameters.java     | 202 +++++++++++++++++++++
 .../camel/support/jsse/NamedGroupsParameters.java  |  66 +++++++
 .../org/apache/camel/core/xml/util/jsse/jaxb.index |   1 +
 ...bstractBaseSSLContextParametersFactoryBean.java |  31 ++++
 .../util/jsse/NamedGroupsParametersDefinition.java |  46 +++++
 .../support/jsse/SSLContextParametersTest.java     | 159 ++++++++++++++++
 .../ROOT/pages/camel-configuration-utilities.adoc  |  35 ++++
 8 files changed, 551 insertions(+)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
index 9dda3d0ef604..235f087ea21e 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
@@ -3477,6 +3477,11 @@ configuration.
       <xs:element maxOccurs="unbounded" minOccurs="0" name="exclude" 
nillable="true" type="xs:string"/>
     </xs:sequence>
   </xs:complexType>
+  <xs:complexType name="namedGroupsParameters">
+    <xs:sequence>
+      <xs:element maxOccurs="unbounded" minOccurs="0" name="namedGroup" 
nillable="true" type="xs:string"/>
+    </xs:sequence>
+  </xs:complexType>
   <xs:complexType name="sniHostNames">
     <xs:sequence>
       <xs:element maxOccurs="unbounded" minOccurs="0" name="sniHostName" 
type="xs:string"/>
@@ -18046,6 +18051,8 @@ converter. The default logging level is DEBUG. Default 
value: DEBUG
           <xs:element minOccurs="0" name="cipherSuitesFilter" 
type="tns:filterParameters"/>
           <xs:element minOccurs="0" name="secureSocketProtocols" 
type="tns:secureSocketProtocolsParameters"/>
           <xs:element minOccurs="0" name="secureSocketProtocolsFilter" 
type="tns:filterParameters"/>
+          <xs:element minOccurs="0" name="namedGroups" 
type="tns:namedGroupsParameters"/>
+          <xs:element minOccurs="0" name="namedGroupsFilter" 
type="tns:filterParameters"/>
           <xs:element minOccurs="0" name="sniHostNames" 
type="tns:sniHostNames"/>
         </xs:all>
         <xs:attribute name="sessionTimeout" type="xs:string"/>
@@ -18060,6 +18067,8 @@ converter. The default logging level is DEBUG. Default 
value: DEBUG
           <xs:element minOccurs="0" name="cipherSuitesFilter" 
type="tns:filterParameters"/>
           <xs:element minOccurs="0" name="secureSocketProtocols" 
type="tns:secureSocketProtocolsParameters"/>
           <xs:element minOccurs="0" name="secureSocketProtocolsFilter" 
type="tns:filterParameters"/>
+          <xs:element minOccurs="0" name="namedGroups" 
type="tns:namedGroupsParameters"/>
+          <xs:element minOccurs="0" name="namedGroupsFilter" 
type="tns:filterParameters"/>
           <xs:element minOccurs="0" name="keyManagers" 
type="tns:keyManagersParametersFactoryBean"/>
           <xs:element minOccurs="0" name="trustManagers" 
type="tns:trustManagersParametersFactoryBean"/>
           <xs:element minOccurs="0" name="secureRandom" 
type="tns:secureRandomParametersFactoryBean"/>
@@ -18130,6 +18139,8 @@ An optional certificate alias to use. This is useful 
when the keystore has multi
           <xs:element minOccurs="0" name="cipherSuitesFilter" 
type="tns:filterParameters"/>
           <xs:element minOccurs="0" name="secureSocketProtocols" 
type="tns:secureSocketProtocolsParameters"/>
           <xs:element minOccurs="0" name="secureSocketProtocolsFilter" 
type="tns:filterParameters"/>
+          <xs:element minOccurs="0" name="namedGroups" 
type="tns:namedGroupsParameters"/>
+          <xs:element minOccurs="0" name="namedGroupsFilter" 
type="tns:filterParameters"/>
         </xs:all>
         <xs:attribute name="sessionTimeout" type="xs:string"/>
         <xs:attribute name="clientAuthentication" type="xs:string"/>
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/support/jsse/BaseSSLContextParameters.java
 
b/core/camel-api/src/main/java/org/apache/camel/support/jsse/BaseSSLContextParameters.java
index c7fe6d8ce233..3c1a464f7d04 100644
--- 
a/core/camel-api/src/main/java/org/apache/camel/support/jsse/BaseSSLContextParameters.java
+++ 
b/core/camel-api/src/main/java/org/apache/camel/support/jsse/BaseSSLContextParameters.java
@@ -83,6 +83,12 @@ public abstract class BaseSSLContextParameters extends 
JsseParameters {
 
     private static final String SSL_SERVER_SOCKET_PROTOCOL_LOG_MSG = 
createProtocolLogMessage("SSLServerSocket");
 
+    private static final String SSL_ENGINE_NAMED_GROUP_LOG_MSG = 
createNamedGroupLogMessage("SSLEngine");
+
+    private static final String SSL_SOCKET_NAMED_GROUP_LOG_MSG = 
createNamedGroupLogMessage("SSLSocket");
+
+    private static final String SSL_SERVER_SOCKET_NAMED_GROUP_LOG_MSG = 
createNamedGroupLogMessage("SSLServerSocket");
+
     /**
      * The optional explicitly configured cipher suites for this configuration.
      */
@@ -103,6 +109,18 @@ public abstract class BaseSSLContextParameters extends 
JsseParameters {
      */
     private FilterParameters secureSocketProtocolsFilter;
 
+    /**
+     * The optional explicitly configured named groups (key exchange groups) 
for this configuration. Named groups
+     * control which key exchange algorithms are available during the TLS 
handshake, including post-quantum hybrid
+     * groups such as X25519MLKEM768.
+     */
+    private NamedGroupsParameters namedGroups;
+
+    /**
+     * The optional named groups filter configuration for this configuration.
+     */
+    private FilterParameters namedGroupsFilter;
+
     /**
      * The optional {@link SSLSessionContext} timeout time for {@link 
javax.net.ssl.SSLSession}s in seconds.
      */
@@ -210,6 +228,62 @@ public abstract class BaseSSLContextParameters extends 
JsseParameters {
         this.secureSocketProtocolsFilter = secureSocketProtocolsFilter;
     }
 
+    /**
+     * Returns the optional explicitly configured named groups for this 
configuration. These options are used in the
+     * configuration of {@link SSLEngine}, {@link SSLSocketFactory} and {@link 
SSLServerSocketFactory} depending on the
+     * context in which they are applied.
+     * <p/>
+     * Named groups control which key exchange algorithms are available during 
the TLS handshake, including post-quantum
+     * hybrid groups such as {@code X25519MLKEM768}.
+     * <p/>
+     * These values override any filters supplied in {@link 
#setNamedGroupsFilter(FilterParameters)}
+     */
+    public NamedGroupsParameters getNamedGroups() {
+        return namedGroups;
+    }
+
+    /**
+     * Sets the optional explicitly configured named groups for this 
configuration. These options are used in the
+     * configuration of {@link SSLEngine}, {@link SSLSocketFactory} and {@link 
SSLServerSocketFactory} depending on the
+     * context in which they are applied.
+     * <p/>
+     * Named groups control which key exchange algorithms are available during 
the TLS handshake, including post-quantum
+     * hybrid groups such as {@code X25519MLKEM768}.
+     * <p/>
+     * These values override any filters supplied in {@link 
#setNamedGroupsFilter(FilterParameters)}
+     *
+     * @param namedGroups the named groups configuration
+     */
+    public void setNamedGroups(NamedGroupsParameters namedGroups) {
+        this.namedGroups = namedGroups;
+    }
+
+    /**
+     * Returns the optional named groups filter for this configuration. These 
options are used in the configuration of
+     * {@link SSLEngine}, {@link SSLSocketFactory} and {@link 
SSLServerSocketFactory} depending on the context in which
+     * they are applied.
+     * <p/>
+     * These values are ignored if {@link 
#setNamedGroups(NamedGroupsParameters)} is called with a non {@code null}
+     * argument.
+     */
+    public FilterParameters getNamedGroupsFilter() {
+        return namedGroupsFilter;
+    }
+
+    /**
+     * Sets the optional named groups filter for this JSSE configuration. 
These options are used in the configuration of
+     * {@link SSLEngine}, {@link SSLSocketFactory} and {@link 
SSLServerSocketFactory} depending on the context in which
+     * they are applied.
+     * <p/>
+     * These values are ignored if {@link 
#setNamedGroups(NamedGroupsParameters)} is called with a non {@code null}
+     * argument.
+     *
+     * @param namedGroupsFilter the filter configuration
+     */
+    public void setNamedGroupsFilter(FilterParameters namedGroupsFilter) {
+        this.namedGroupsFilter = namedGroupsFilter;
+    }
+
     /**
      * Returns the optional {@link SSLSessionContext} timeout time for {@link 
javax.net.ssl.SSLSession}s in seconds.
      */
@@ -314,6 +388,17 @@ public abstract class BaseSSLContextParameters extends 
JsseParameters {
             enabledSecureSocketProtocolsPatterns = null;
         }
 
+        final List<String> enabledNamedGroups = this.getNamedGroups() == null
+                ? null : 
this.parsePropertyValues(this.getNamedGroups().getNamedGroup());
+
+        final Patterns enabledNamedGroupsPatterns;
+
+        if (this.getNamedGroupsFilter() != null) {
+            enabledNamedGroupsPatterns = 
this.getNamedGroupsFilter().getPatterns();
+        } else {
+            enabledNamedGroupsPatterns = null;
+        }
+
         //
 
         final boolean allowPassthrough = getAllowPassthrough();
@@ -364,6 +449,23 @@ public abstract class BaseSSLContextParameters extends 
JsseParameters {
                 engine.setEnabledProtocols(
                         filteredSecureSocketProtocols.toArray(new String[0]));
 
+                String[] namedGroups = resolveNamedGroups(
+                        engine.getSSLParameters().getNamedGroups(),
+                        enabledNamedGroups, enabledNamedGroupsPatterns);
+                if (namedGroups != null) {
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug(SSL_ENGINE_NAMED_GROUP_LOG_MSG,
+                                engine,
+                                enabledNamedGroups,
+                                enabledNamedGroupsPatterns,
+                                engine.getSSLParameters().getNamedGroups(),
+                                namedGroups);
+                    }
+                    SSLParameters params = engine.getSSLParameters();
+                    params.setNamedGroups(namedGroups);
+                    engine.setSSLParameters(params);
+                }
+
                 return engine;
             }
         };
@@ -479,6 +581,17 @@ public abstract class BaseSSLContextParameters extends 
JsseParameters {
             enabledSecureSocketProtocolsPatterns = null;
         }
 
+        final List<String> enabledNamedGroups = this.getNamedGroups() == null
+                ? null : 
this.parsePropertyValues(this.getNamedGroups().getNamedGroup());
+
+        final Patterns enabledNamedGroupsPatterns;
+
+        if (this.getNamedGroupsFilter() != null) {
+            enabledNamedGroupsPatterns = 
this.getNamedGroupsFilter().getPatterns();
+        } else {
+            enabledNamedGroupsPatterns = null;
+        }
+
         //
 
         final boolean allowPassthrough = getAllowPassthrough();
@@ -533,6 +646,24 @@ public abstract class BaseSSLContextParameters extends 
JsseParameters {
 
                 socket.setEnabledProtocols(
                         filteredSecureSocketProtocols.toArray(new String[0]));
+
+                String[] namedGroups = resolveNamedGroups(
+                        socket.getSSLParameters().getNamedGroups(),
+                        enabledNamedGroups, enabledNamedGroupsPatterns);
+                if (namedGroups != null) {
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug(SSL_SOCKET_NAMED_GROUP_LOG_MSG,
+                                socket,
+                                enabledNamedGroups,
+                                enabledNamedGroupsPatterns,
+                                socket.getSSLParameters().getNamedGroups(),
+                                namedGroups);
+                    }
+                    SSLParameters params = socket.getSSLParameters();
+                    params.setNamedGroups(namedGroups);
+                    socket.setSSLParameters(params);
+                }
+
                 return socket;
             }
         };
@@ -578,6 +709,17 @@ public abstract class BaseSSLContextParameters extends 
JsseParameters {
             enabledSecureSocketProtocolsPatterns = null;
         }
 
+        final List<String> enabledNamedGroups = this.getNamedGroups() == null
+                ? null : 
this.parsePropertyValues(this.getNamedGroups().getNamedGroup());
+
+        final Patterns enabledNamedGroupsPatterns;
+
+        if (this.getNamedGroupsFilter() != null) {
+            enabledNamedGroupsPatterns = 
this.getNamedGroupsFilter().getPatterns();
+        } else {
+            enabledNamedGroupsPatterns = null;
+        }
+
         //
 
         final boolean allowPassthrough = getAllowPassthrough();
@@ -627,6 +769,24 @@ public abstract class BaseSSLContextParameters extends 
JsseParameters {
 
                 socket.setEnabledProtocols(
                         filteredSecureSocketProtocols.toArray(new String[0]));
+
+                String[] namedGroups = resolveNamedGroups(
+                        socket.getSSLParameters().getNamedGroups(),
+                        enabledNamedGroups, enabledNamedGroupsPatterns);
+                if (namedGroups != null) {
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug(SSL_SERVER_SOCKET_NAMED_GROUP_LOG_MSG,
+                                socket,
+                                enabledNamedGroups,
+                                enabledNamedGroupsPatterns,
+                                socket.getSSLParameters().getNamedGroups(),
+                                namedGroups);
+                    }
+                    SSLParameters params = socket.getSSLParameters();
+                    params.setNamedGroups(namedGroups);
+                    socket.setSSLParameters(params);
+                }
+
                 return socket;
             }
         };
@@ -768,6 +928,40 @@ public abstract class BaseSSLContextParameters extends 
JsseParameters {
         return matches;
     }
 
+    /**
+     * Resolves the named groups to configure based on explicit values or 
filter patterns. Returns {@code null} if no
+     * named groups configuration is needed (both parameters are {@code null}).
+     *
+     * @param  currentNamedGroups         the currently available named groups 
from the SSL object
+     * @param  enabledNamedGroups         the optional explicit named groups 
list
+     * @param  enabledNamedGroupsPatterns the optional filter patterns
+     *
+     * @return                            the filtered named groups array, or 
{@code null} if no configuration is needed
+     */
+    private String[] resolveNamedGroups(
+            String[] currentNamedGroups, List<String> enabledNamedGroups,
+            Patterns enabledNamedGroupsPatterns) {
+
+        if (enabledNamedGroups == null && enabledNamedGroupsPatterns == null) {
+            return null;
+        }
+
+        if (currentNamedGroups == null) {
+            currentNamedGroups = new String[0];
+        }
+
+        Collection<String> filteredNamedGroups;
+        if (enabledNamedGroups != null) {
+            filteredNamedGroups = new ArrayList<>(enabledNamedGroups);
+        } else {
+            filteredNamedGroups = this.filter(
+                    null, Arrays.asList(currentNamedGroups),
+                    enabledNamedGroupsPatterns.getIncludes(), 
enabledNamedGroupsPatterns.getExcludes());
+        }
+
+        return filteredNamedGroups.toArray(new String[0]);
+    }
+
     /**
      * Configures a {@code T} based on the related configuration options.
      */
@@ -1106,4 +1300,12 @@ public abstract class BaseSSLContextParameters extends 
JsseParameters {
                + "\t and default protocol patterns [{}]." + LS
                + "\t Resulting enabled protocols are [{}].";
     }
+
+    private static String createNamedGroupLogMessage(String entityName) {
+        return "Configuring " + entityName + " [{}] with " + LS
+               + "\t explicitly set named groups [{}]," + LS
+               + "\t named group patterns [{}]," + LS
+               + "\t available named groups [{}]," + LS
+               + "\t Resulting enabled named groups are [{}].";
+    }
 }
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/support/jsse/NamedGroupsParameters.java
 
b/core/camel-api/src/main/java/org/apache/camel/support/jsse/NamedGroupsParameters.java
new file mode 100644
index 000000000000..760778618d72
--- /dev/null
+++ 
b/core/camel-api/src/main/java/org/apache/camel/support/jsse/NamedGroupsParameters.java
@@ -0,0 +1,66 @@
+/*
+ * 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.support.jsse;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a list of TLS/SSL named groups (also known as elliptic curves or 
key exchange groups) for use in TLS
+ * handshakes. Named groups control which key exchange algorithms are 
available during the TLS handshake, including
+ * post-quantum hybrid groups such as {@code X25519MLKEM768}.
+ */
+public class NamedGroupsParameters {
+    private List<String> namedGroup;
+
+    /**
+     * Returns a live reference to the list of named group names.
+     *
+     * @return a reference to the list, never {@code null}
+     */
+    public List<String> getNamedGroup() {
+        if (this.namedGroup == null) {
+            this.namedGroup = new ArrayList<>();
+        }
+        return this.namedGroup;
+    }
+
+    public void addNamedGroup(String group) {
+        if (this.namedGroup == null) {
+            this.namedGroup = new ArrayList<>();
+        }
+        this.namedGroup.add(group.trim());
+    }
+
+    /**
+     * Sets the named groups. It creates a copy of the given list.
+     *
+     * @param namedGroup named groups
+     */
+    public void setNamedGroup(List<String> namedGroup) {
+        this.namedGroup = namedGroup == null ? null : new 
ArrayList<>(namedGroup);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("NamedGroupsParameters[namedGroup=");
+        builder.append(getNamedGroup());
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git 
a/core/camel-core-xml/src/generated/resources/org/apache/camel/core/xml/util/jsse/jaxb.index
 
b/core/camel-core-xml/src/generated/resources/org/apache/camel/core/xml/util/jsse/jaxb.index
index 8d84387108de..ecefb19403b1 100644
--- 
a/core/camel-core-xml/src/generated/resources/org/apache/camel/core/xml/util/jsse/jaxb.index
+++ 
b/core/camel-core-xml/src/generated/resources/org/apache/camel/core/xml/util/jsse/jaxb.index
@@ -1,5 +1,6 @@
 # Generated by camel build tools - do NOT edit this file!
 CipherSuitesParametersDefinition
 FilterParametersDefinition
+NamedGroupsParametersDefinition
 SNIHostNamesDefinition
 SecureSocketProtocolsParametersDefinition
diff --git 
a/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/util/jsse/AbstractBaseSSLContextParametersFactoryBean.java
 
b/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/util/jsse/AbstractBaseSSLContextParametersFactoryBean.java
index baf2463dd1f9..ff0d4d944dc9 100644
--- 
a/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/util/jsse/AbstractBaseSSLContextParametersFactoryBean.java
+++ 
b/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/util/jsse/AbstractBaseSSLContextParametersFactoryBean.java
@@ -23,6 +23,7 @@ import org.apache.camel.spi.Metadata;
 import org.apache.camel.support.jsse.BaseSSLContextParameters;
 import org.apache.camel.support.jsse.CipherSuitesParameters;
 import org.apache.camel.support.jsse.FilterParameters;
+import org.apache.camel.support.jsse.NamedGroupsParameters;
 import org.apache.camel.support.jsse.SecureSocketProtocolsParameters;
 
 @XmlTransient
@@ -37,6 +38,10 @@ public abstract class 
AbstractBaseSSLContextParametersFactoryBean<T extends Base
 
     private FilterParametersDefinition secureSocketProtocolsFilter;
 
+    private NamedGroupsParametersDefinition namedGroups;
+
+    private FilterParametersDefinition namedGroupsFilter;
+
     @XmlAttribute
     @Metadata(description = "The optional SSLSessionContext timeout time for 
javax.net.ssl.SSLSession in seconds.")
     private String sessionTimeout;
@@ -83,6 +88,16 @@ public abstract class 
AbstractBaseSSLContextParametersFactoryBean<T extends Base
             
newInstance.setSecureSocketProtocolsFilter(createFilterParameters(secureSocketProtocolsFilter));
         }
 
+        if (namedGroups != null) {
+            NamedGroupsParameters namedGroupsInstance = new 
NamedGroupsParameters();
+            namedGroupsInstance.setNamedGroup(namedGroups.getNamedGroup());
+            newInstance.setNamedGroups(namedGroupsInstance);
+        }
+
+        if (namedGroupsFilter != null) {
+            
newInstance.setNamedGroupsFilter(createFilterParameters(namedGroupsFilter));
+        }
+
         if (sessionTimeout != null) {
             newInstance.setSessionTimeout(sessionTimeout);
         }
@@ -131,6 +146,22 @@ public abstract class 
AbstractBaseSSLContextParametersFactoryBean<T extends Base
         this.secureSocketProtocolsFilter = secureSocketProtocolsFilter;
     }
 
+    public NamedGroupsParametersDefinition getNamedGroups() {
+        return namedGroups;
+    }
+
+    public void setNamedGroups(NamedGroupsParametersDefinition namedGroups) {
+        this.namedGroups = namedGroups;
+    }
+
+    public FilterParametersDefinition getNamedGroupsFilter() {
+        return namedGroupsFilter;
+    }
+
+    public void setNamedGroupsFilter(FilterParametersDefinition 
namedGroupsFilter) {
+        this.namedGroupsFilter = namedGroupsFilter;
+    }
+
     public String getSessionTimeout() {
         return sessionTimeout;
     }
diff --git 
a/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/util/jsse/NamedGroupsParametersDefinition.java
 
b/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/util/jsse/NamedGroupsParametersDefinition.java
new file mode 100644
index 000000000000..eecfd6ffe275
--- /dev/null
+++ 
b/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/util/jsse/NamedGroupsParametersDefinition.java
@@ -0,0 +1,46 @@
+/*
+ * 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.core.xml.util.jsse;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import jakarta.xml.bind.annotation.XmlAccessType;
+import jakarta.xml.bind.annotation.XmlAccessorType;
+import jakarta.xml.bind.annotation.XmlType;
+
+import org.apache.camel.spi.Metadata;
+
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "namedGroupsParameters", propOrder = { "namedGroup" })
+public class NamedGroupsParametersDefinition {
+
+    @Metadata(description = "List of TLS/SSL named groups (key exchange 
groups)")
+    private List<String> namedGroup;
+
+    /**
+     * Returns a live reference to the list of named group names.
+     *
+     * @return a reference to the list, never {@code null}
+     */
+    public List<String> getNamedGroup() {
+        if (this.namedGroup == null) {
+            this.namedGroup = new ArrayList<>();
+        }
+        return this.namedGroup;
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/support/jsse/SSLContextParametersTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/support/jsse/SSLContextParametersTest.java
index 0df49fd146f3..0ad671cf15a3 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/support/jsse/SSLContextParametersTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/support/jsse/SSLContextParametersTest.java
@@ -741,6 +741,165 @@ public class SSLContextParametersTest extends 
AbstractJsseParametersTest {
         }
     }
 
+    @Test
+    public void testNamedGroups() throws Exception {
+        SSLContext controlContext = SSLContext.getInstance("TLSv1.3");
+        controlContext.init(null, null, null);
+        SSLEngine controlEngine = controlContext.createSSLEngine();
+        String[] controlNamedGroups = 
controlEngine.getSSLParameters().getNamedGroups();
+
+        // default - no named groups configured, should keep defaults
+        SSLContextParameters scp = new SSLContextParameters();
+        SSLContext context = scp.createSSLContext(null);
+
+        SSLEngine engine = context.createSSLEngine();
+        SSLSocket socket = (SSLSocket) 
context.getSocketFactory().createSocket();
+        SSLServerSocket serverSocket = (SSLServerSocket) 
context.getServerSocketFactory().createServerSocket();
+
+        assertArrayEquals(controlNamedGroups, 
engine.getSSLParameters().getNamedGroups());
+        assertArrayEquals(controlNamedGroups, 
socket.getSSLParameters().getNamedGroups());
+        assertArrayEquals(controlNamedGroups, 
serverSocket.getSSLParameters().getNamedGroups());
+
+        // empty ngp - sets empty list
+        NamedGroupsParameters ngp = new NamedGroupsParameters();
+        scp.setNamedGroups(ngp);
+        context = scp.createSSLContext(null);
+        engine = context.createSSLEngine();
+        socket = (SSLSocket) context.getSocketFactory().createSocket();
+        serverSocket = (SSLServerSocket) 
context.getServerSocketFactory().createServerSocket();
+
+        assertEquals(0, engine.getSSLParameters().getNamedGroups().length);
+        assertEquals(0, socket.getSSLParameters().getNamedGroups().length);
+        assertEquals(0, 
serverSocket.getSSLParameters().getNamedGroups().length);
+
+        // explicit named group
+        ngp.setNamedGroup(Collections.singletonList(controlNamedGroups[0]));
+        context = scp.createSSLContext(null);
+        engine = context.createSSLEngine();
+        socket = (SSLSocket) context.getSocketFactory().createSocket();
+        serverSocket = (SSLServerSocket) 
context.getServerSocketFactory().createServerSocket();
+
+        assertEquals(1, engine.getSSLParameters().getNamedGroups().length);
+        assertEquals(controlNamedGroups[0], 
engine.getSSLParameters().getNamedGroups()[0]);
+        assertEquals(1, socket.getSSLParameters().getNamedGroups().length);
+        assertEquals(controlNamedGroups[0], 
socket.getSSLParameters().getNamedGroups()[0]);
+        assertEquals(1, 
serverSocket.getSSLParameters().getNamedGroups().length);
+        assertEquals(controlNamedGroups[0], 
serverSocket.getSSLParameters().getNamedGroups()[0]);
+
+        // explicit named groups override filter
+        FilterParameters filter = new FilterParameters();
+        filter.getInclude().add(".*");
+        scp.setNamedGroupsFilter(filter);
+        context = scp.createSSLContext(null);
+        engine = context.createSSLEngine();
+        socket = (SSLSocket) context.getSocketFactory().createSocket();
+        serverSocket = (SSLServerSocket) 
context.getServerSocketFactory().createServerSocket();
+
+        assertEquals(1, engine.getSSLParameters().getNamedGroups().length);
+        assertEquals(controlNamedGroups[0], 
engine.getSSLParameters().getNamedGroups()[0]);
+        assertEquals(1, socket.getSSLParameters().getNamedGroups().length);
+        assertEquals(controlNamedGroups[0], 
socket.getSSLParameters().getNamedGroups()[0]);
+        assertEquals(1, 
serverSocket.getSSLParameters().getNamedGroups().length);
+        assertEquals(controlNamedGroups[0], 
serverSocket.getSSLParameters().getNamedGroups()[0]);
+    }
+
+    @Test
+    public void testNamedGroupsFilter() throws Exception {
+        SSLContext controlContext = SSLContext.getInstance("TLSv1.3");
+        controlContext.init(null, null, null);
+        SSLEngine controlEngine = controlContext.createSSLEngine();
+        String[] controlNamedGroups = 
controlEngine.getSSLParameters().getNamedGroups();
+
+        // default - no filter, keeps defaults
+        SSLContextParameters scp = new SSLContextParameters();
+        SSLContext context = scp.createSSLContext(null);
+
+        SSLEngine engine = context.createSSLEngine();
+        SSLSocket socket = (SSLSocket) 
context.getSocketFactory().createSocket();
+        SSLServerSocket serverSocket = (SSLServerSocket) 
context.getServerSocketFactory().createServerSocket();
+
+        assertArrayEquals(controlNamedGroups, 
engine.getSSLParameters().getNamedGroups());
+        assertArrayEquals(controlNamedGroups, 
socket.getSSLParameters().getNamedGroups());
+        assertArrayEquals(controlNamedGroups, 
serverSocket.getSSLParameters().getNamedGroups());
+
+        // empty filter - no includes means no groups match
+        FilterParameters filter = new FilterParameters();
+        scp.setNamedGroupsFilter(filter);
+        context = scp.createSSLContext(null);
+        engine = context.createSSLEngine();
+        socket = (SSLSocket) context.getSocketFactory().createSocket();
+        serverSocket = (SSLServerSocket) 
context.getServerSocketFactory().createServerSocket();
+
+        assertEquals(0, engine.getSSLParameters().getNamedGroups().length);
+        assertEquals(0, socket.getSSLParameters().getNamedGroups().length);
+        assertEquals(0, 
serverSocket.getSSLParameters().getNamedGroups().length);
+
+        // include all
+        filter.getInclude().add(".*");
+        context = scp.createSSLContext(null);
+        engine = context.createSSLEngine();
+        socket = (SSLSocket) context.getSocketFactory().createSocket();
+        serverSocket = (SSLServerSocket) 
context.getServerSocketFactory().createServerSocket();
+
+        assertArrayEquals(controlNamedGroups, 
engine.getSSLParameters().getNamedGroups());
+        assertArrayEquals(controlNamedGroups, 
socket.getSSLParameters().getNamedGroups());
+        assertArrayEquals(controlNamedGroups, 
serverSocket.getSSLParameters().getNamedGroups());
+
+        // include all but exclude all (excludes win)
+        filter.getExclude().add(".*");
+        context = scp.createSSLContext(null);
+        engine = context.createSSLEngine();
+        socket = (SSLSocket) context.getSocketFactory().createSocket();
+        serverSocket = (SSLServerSocket) 
context.getServerSocketFactory().createServerSocket();
+
+        assertEquals(0, engine.getSSLParameters().getNamedGroups().length);
+        assertEquals(0, socket.getSSLParameters().getNamedGroups().length);
+        assertEquals(0, 
serverSocket.getSSLParameters().getNamedGroups().length);
+
+        // include only x* groups (e.g. x25519, x448)
+        filter.getInclude().clear();
+        filter.getExclude().clear();
+        filter.getInclude().add("x.*");
+        context = scp.createSSLContext(null);
+        engine = context.createSSLEngine();
+        socket = (SSLSocket) context.getSocketFactory().createSocket();
+        serverSocket = (SSLServerSocket) 
context.getServerSocketFactory().createServerSocket();
+
+        assertTrue(engine.getSSLParameters().getNamedGroups().length >= 1);
+        for (String group : engine.getSSLParameters().getNamedGroups()) {
+            assertTrue(group.startsWith("x"), "Expected group starting with 
'x' but got: " + group);
+        }
+        assertTrue(socket.getSSLParameters().getNamedGroups().length >= 1);
+        for (String group : socket.getSSLParameters().getNamedGroups()) {
+            assertTrue(group.startsWith("x"), "Expected group starting with 
'x' but got: " + group);
+        }
+        assertTrue(serverSocket.getSSLParameters().getNamedGroups().length >= 
1);
+        for (String group : serverSocket.getSSLParameters().getNamedGroups()) {
+            assertTrue(group.startsWith("x"), "Expected group starting with 
'x' but got: " + group);
+        }
+    }
+
+    @Test
+    public void testNamedGroupsDoNotAffectCipherSuitesOrProtocols() throws 
Exception {
+        SSLContext controlContext = SSLContext.getInstance("TLSv1.3");
+        controlContext.init(null, null, null);
+        SSLEngine controlEngine = controlContext.createSSLEngine();
+
+        // setting named groups should not change cipher suites or protocols
+        SSLContextParameters scp = new SSLContextParameters();
+        NamedGroupsParameters ngp = new NamedGroupsParameters();
+        ngp.setNamedGroup(Collections.singletonList("x25519"));
+        scp.setNamedGroups(ngp);
+
+        SSLContext context = scp.createSSLContext(null);
+        SSLEngine engine = context.createSSLEngine();
+
+        assertArrayEquals(controlEngine.getEnabledCipherSuites(), 
engine.getEnabledCipherSuites());
+        assertArrayEquals(controlEngine.getEnabledProtocols(), 
engine.getEnabledProtocols());
+        assertEquals(1, engine.getSSLParameters().getNamedGroups().length);
+        assertEquals("x25519", engine.getSSLParameters().getNamedGroups()[0]);
+    }
+
     @Test
     public void testSessionTimeout() throws Exception {
         SSLContextParameters scp = new SSLContextParameters();
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-configuration-utilities.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-configuration-utilities.adoc
index 295ad0a67378..2db76abada0a 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-configuration-utilities.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-configuration-utilities.adoc
@@ -46,6 +46,8 @@ contain the following elements:
 * cipherSuitesFilter (element)
 * secureSocketProtocols (element)
 * secureSocketProtocolsFilter (element)
+* namedGroups (element)
+* namedGroupsFilter (element)
 * keyManagers (element)
 * trustManagers (element)
 * secureRandom (element)
@@ -113,6 +115,23 @@ are:
 Includes .*
 ----
 
+namedGroups::
+This optional property represents a collection of explicitly named
+TLS named groups (key exchange algorithms) to enable on both the client
+and server side as well as in the SSLEngine. Named groups control which
+key exchange algorithms are available during the TLS handshake, including
+post-quantum hybrid groups such as `X25519MLKEM768`. These values take
+precedence over filters supplied in namedGroupsFilter. The utility
+attempts to enable the listed named groups regardless of whether the
+JSSE provider actually supports them or not. No default filtering is
+applied to named groups.
+namedGroupsFilter::
+This optional property represents a collection of include and exclude
+patterns for named groups to enable on both the client and server side
+as well as in the SSLEngine. The patterns are applied over only the
+available named groups. The excludes patterns have precedence over the
+includes patterns. No default filtering is applied to named groups.
+
 keyManagers::
 This optional property configures the source of key material for
 providing identity of client and server side connections as well as in
@@ -271,6 +290,8 @@ SSLContextServerParameters contain the following elements:
 * cipherSuitesFilter (element)
 * secureSocketProtocols (element)
 * secureSocketProtocolsFilter (element)
+* namedGroups (element)
+* namedGroupsFilter (element)
 
 clientAuthentication::
 This optional property indicates if the server side does not request,
@@ -300,6 +321,14 @@ secureSocketProtocolsFilter::
 This optional property overrides the value of this
 setting in the SSLContextParameters. This option has no effect on the
 SSLEngine configuration.
+namedGroups::
+This optional property overrides the value of this
+setting in the SSLContextParameters. This option has no effect on the
+SSLEngine configuration.
+namedGroupsFilter::
+This optional property overrides the value of this
+setting in the SSLContextParameters. This option has no effect on the
+SSLEngine configuration.
 
 === SSLContextClientParameters
 
@@ -311,6 +340,8 @@ SSLContextClientParameters contains the following elements:
 * cipherSuitesFilter (element)
 * secureSocketProtocols (element)
 * secureSocketProtocolsFilter (element)
+* namedGroups (element)
+* namedGroupsFilter (element)
 
 sniHostNames::
 Contains a list of sniHostName elements which provides a list
@@ -325,6 +356,10 @@ secureSocketProtocols::
 See above
 secureSocketProtocolsFilter::
 See above
+namedGroups::
+See above
+namedGroupsFilter::
+See above
 
 == Examples
 


Reply via email to