This is an automated email from the ASF dual-hosted git repository. acosentino pushed a commit to branch 23159 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 63549f01db8acc5ffec74310d46f9e94d410d6fd Author: Andrea Cosentino <[email protected]> AuthorDate: Mon Mar 9 14:50:32 2026 +0100 CAMEL-23159 - Post-Quantum Cryptography (PQC) readiness: Add signatureSchemes support to BaseSSLContextParameters and SSLConfigurationProperties for PQC readiness Add full TLS signature schemes configuration support mirroring the existing named groups pattern, enabling PQC signature algorithms like ML-DSA to be configured for TLS handshakes across all Camel components. Core API (BaseSSLContextParameters): - Add SignatureSchemesParameters POJO with List<String> signatureScheme - Add signatureSchemes and signatureSchemesFilter fields - Add resolveSignatureSchemes() helper for filter-based resolution - Wire into all 3 SSL configurers (SSLEngine, SSLSocket, SSLServerSocket) calling SSLParameters.setSignatureSchemes() - Add SignatureSchemesParametersDefinition JAXB XML binding - Wire into AbstractBaseSSLContextParametersFactoryBean Camel Main (SSLConfigurationProperties): - Add camel.ssl.signatureSchemes property (comma-separated list) - Add camel.ssl.signatureSchemesInclude/Exclude filter properties - Add mapping logic in BaseMainSupport.createSSLContextParameters() - Update generated configurer, metadata, and documentation Tests: - 3 new JSSE-level tests in SSLContextParametersTest (explicit config, filter, isolation) - 4 new Main config tests in MainSSLTest (property-based, fluent, filter property-based, fluent filter) Signed-off-by: Andrea Cosentino <[email protected]> --- .../main/camel-main-configuration-metadata.json | 3 + .../apache/camel/catalog/schemas/camel-spring.xsd | 11 ++ .../support/jsse/BaseSSLContextParameters.java | 202 +++++++++++++++++++++ .../support/jsse/SignatureSchemesParameters.java | 65 +++++++ .../org/apache/camel/core/xml/util/jsse/jaxb.index | 1 + ...bstractBaseSSLContextParametersFactoryBean.java | 31 ++++ .../jsse/SignatureSchemesParametersDefinition.java | 46 +++++ .../support/jsse/SSLContextParametersTest.java | 145 +++++++++++++++ .../main/SSLConfigurationPropertiesConfigurer.java | 21 +++ .../camel-main-configuration-metadata.json | 3 + core/camel-main/src/main/docs/main.adoc | 5 +- .../org/apache/camel/main/BaseMainSupport.java | 22 +++ .../camel/main/SSLConfigurationProperties.java | 89 +++++++++ .../java/org/apache/camel/main/MainSSLTest.java | 128 +++++++++++++ 14 files changed, 771 insertions(+), 1 deletion(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json index 5e5d6ba74821..1b156fda5539 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json @@ -351,6 +351,9 @@ { "name": "camel.ssl.secureRandomProvider", "required": false, "description": "To use a specific provider for creating SecureRandom. The list of available providers returned by java.security.Security.getProviders() or null to use the highest priority provider implementing the secure socket protocol.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.secureSocketProtocol", "required": false, "description": "The protocol for the secure sockets created by the SSLContext. See https:\/\/docs.oracle.com\/en\/java\/javase\/17\/docs\/specs\/security\/standard-names.html", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "TLSv1.3", "secret": false }, { "name": "camel.ssl.sessionTimeout", "required": false, "description": "Timeout in seconds to use for SSLContext. The default is 24 hours.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "integer", "javaType": "int", "defaultValue": 86400, "secret": false }, + { "name": "camel.ssl.signatureSchemes", "required": false, "description": "List of TLS\/SSL signature schemes. Multiple names can be separated by comma. Signature schemes control which signature algorithms are available during the TLS handshake, including post-quantum signature algorithms such as ML-DSA.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, + { "name": "camel.ssl.signatureSchemesExclude", "required": false, "description": "Filters TLS\/SSL signature schemes. This filter is used for excluding signature schemes that match the naming pattern. Multiple names can be separated by comma. Notice that if the signatureSchemes option has been configured then the include\/exclude filters are not in use.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, + { "name": "camel.ssl.signatureSchemesInclude", "required": false, "description": "Filters TLS\/SSL signature schemes. This filter is used for including signature schemes that match the naming pattern. Multiple names can be separated by comma. Notice that if the signatureSchemes option has been configured then the include\/exclude filters are not in use.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.trustAllCertificates", "required": false, "description": "Allows to trust all SSL certificates without performing certificate validation. This can be used in development environment but may expose the system to security risks. Notice that if the trustAllCertificates option is set to true then the trustStore\/trustStorePassword options are not in use.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "boolean", "javaType": "boolean", "def [...] { "name": "camel.ssl.trustStore", "required": false, "description": "The trust store to load. The trust store is by default loaded from classpath. If you must load from file system, then use file: as prefix. file:nameOfFile (to refer to the file system) classpath:nameOfFile (to refer to the classpath; default) http:uri (to load the resource using HTTP) ref:nameOfBean (to lookup an existing KeyStore instance from the registry, for example for testing and development).", "sourceType": [...] { "name": "camel.ssl.trustStorePassword", "required": false, "description": "Sets the SSL Truststore password.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, 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 235f087ea21e..5b7f7fd8ba57 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 @@ -3492,6 +3492,11 @@ configuration. <xs:element maxOccurs="unbounded" minOccurs="0" name="secureSocketProtocol" nillable="true" type="xs:string"/> </xs:sequence> </xs:complexType> + <xs:complexType name="signatureSchemesParameters"> + <xs:sequence> + <xs:element maxOccurs="unbounded" minOccurs="0" name="signatureScheme" nillable="true" type="xs:string"/> + </xs:sequence> + </xs:complexType> <xs:complexType name="output"> <xs:complexContent> <xs:extension base="tns:processorDefinition"> @@ -18053,6 +18058,8 @@ converter. The default logging level is DEBUG. Default value: DEBUG <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="signatureSchemes" type="tns:signatureSchemesParameters"/> + <xs:element minOccurs="0" name="signatureSchemesFilter" type="tns:filterParameters"/> <xs:element minOccurs="0" name="sniHostNames" type="tns:sniHostNames"/> </xs:all> <xs:attribute name="sessionTimeout" type="xs:string"/> @@ -18069,6 +18076,8 @@ converter. The default logging level is DEBUG. Default value: DEBUG <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="signatureSchemes" type="tns:signatureSchemesParameters"/> + <xs:element minOccurs="0" name="signatureSchemesFilter" 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"/> @@ -18141,6 +18150,8 @@ An optional certificate alias to use. This is useful when the keystore has multi <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="signatureSchemes" type="tns:signatureSchemesParameters"/> + <xs:element minOccurs="0" name="signatureSchemesFilter" 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 3c1a464f7d04..0356459cd8f2 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 @@ -89,6 +89,13 @@ public abstract class BaseSSLContextParameters extends JsseParameters { private static final String SSL_SERVER_SOCKET_NAMED_GROUP_LOG_MSG = createNamedGroupLogMessage("SSLServerSocket"); + private static final String SSL_ENGINE_SIGNATURE_SCHEME_LOG_MSG = createSignatureSchemeLogMessage("SSLEngine"); + + private static final String SSL_SOCKET_SIGNATURE_SCHEME_LOG_MSG = createSignatureSchemeLogMessage("SSLSocket"); + + private static final String SSL_SERVER_SOCKET_SIGNATURE_SCHEME_LOG_MSG + = createSignatureSchemeLogMessage("SSLServerSocket"); + /** * The optional explicitly configured cipher suites for this configuration. */ @@ -121,6 +128,18 @@ public abstract class BaseSSLContextParameters extends JsseParameters { */ private FilterParameters namedGroupsFilter; + /** + * The optional explicitly configured signature schemes for this configuration. Signature schemes control which + * signature algorithms are available during the TLS handshake, including post-quantum signature algorithms such as + * ML-DSA. + */ + private SignatureSchemesParameters signatureSchemes; + + /** + * The optional signature schemes filter configuration for this configuration. + */ + private FilterParameters signatureSchemesFilter; + /** * The optional {@link SSLSessionContext} timeout time for {@link javax.net.ssl.SSLSession}s in seconds. */ @@ -284,6 +303,62 @@ public abstract class BaseSSLContextParameters extends JsseParameters { this.namedGroupsFilter = namedGroupsFilter; } + /** + * Returns the optional explicitly configured signature schemes 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/> + * Signature schemes control which signature algorithms are available during the TLS handshake, including + * post-quantum signature algorithms such as ML-DSA. + * <p/> + * These values override any filters supplied in {@link #setSignatureSchemesFilter(FilterParameters)} + */ + public SignatureSchemesParameters getSignatureSchemes() { + return signatureSchemes; + } + + /** + * Sets the optional explicitly configured signature schemes 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/> + * Signature schemes control which signature algorithms are available during the TLS handshake, including + * post-quantum signature algorithms such as ML-DSA. + * <p/> + * These values override any filters supplied in {@link #setSignatureSchemesFilter(FilterParameters)} + * + * @param signatureSchemes the signature schemes configuration + */ + public void setSignatureSchemes(SignatureSchemesParameters signatureSchemes) { + this.signatureSchemes = signatureSchemes; + } + + /** + * Returns the optional signature schemes 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 #setSignatureSchemes(SignatureSchemesParameters)} is called with a non + * {@code null} argument. + */ + public FilterParameters getSignatureSchemesFilter() { + return signatureSchemesFilter; + } + + /** + * Sets the optional signature schemes 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 #setSignatureSchemes(SignatureSchemesParameters)} is called with a non + * {@code null} argument. + * + * @param signatureSchemesFilter the filter configuration + */ + public void setSignatureSchemesFilter(FilterParameters signatureSchemesFilter) { + this.signatureSchemesFilter = signatureSchemesFilter; + } + /** * Returns the optional {@link SSLSessionContext} timeout time for {@link javax.net.ssl.SSLSession}s in seconds. */ @@ -399,6 +474,17 @@ public abstract class BaseSSLContextParameters extends JsseParameters { enabledNamedGroupsPatterns = null; } + final List<String> enabledSignatureSchemes = this.getSignatureSchemes() == null + ? null : this.parsePropertyValues(this.getSignatureSchemes().getSignatureScheme()); + + final Patterns enabledSignatureSchemesPatterns; + + if (this.getSignatureSchemesFilter() != null) { + enabledSignatureSchemesPatterns = this.getSignatureSchemesFilter().getPatterns(); + } else { + enabledSignatureSchemesPatterns = null; + } + // final boolean allowPassthrough = getAllowPassthrough(); @@ -466,6 +552,23 @@ public abstract class BaseSSLContextParameters extends JsseParameters { engine.setSSLParameters(params); } + String[] signatureSchemes = resolveSignatureSchemes( + engine.getSSLParameters().getSignatureSchemes(), + enabledSignatureSchemes, enabledSignatureSchemesPatterns); + if (signatureSchemes != null) { + if (LOG.isDebugEnabled()) { + LOG.debug(SSL_ENGINE_SIGNATURE_SCHEME_LOG_MSG, + engine, + enabledSignatureSchemes, + enabledSignatureSchemesPatterns, + engine.getSSLParameters().getSignatureSchemes(), + signatureSchemes); + } + SSLParameters params = engine.getSSLParameters(); + params.setSignatureSchemes(signatureSchemes); + engine.setSSLParameters(params); + } + return engine; } }; @@ -592,6 +695,17 @@ public abstract class BaseSSLContextParameters extends JsseParameters { enabledNamedGroupsPatterns = null; } + final List<String> enabledSignatureSchemes = this.getSignatureSchemes() == null + ? null : this.parsePropertyValues(this.getSignatureSchemes().getSignatureScheme()); + + final Patterns enabledSignatureSchemesPatterns; + + if (this.getSignatureSchemesFilter() != null) { + enabledSignatureSchemesPatterns = this.getSignatureSchemesFilter().getPatterns(); + } else { + enabledSignatureSchemesPatterns = null; + } + // final boolean allowPassthrough = getAllowPassthrough(); @@ -664,6 +778,23 @@ public abstract class BaseSSLContextParameters extends JsseParameters { socket.setSSLParameters(params); } + String[] signatureSchemes = resolveSignatureSchemes( + socket.getSSLParameters().getSignatureSchemes(), + enabledSignatureSchemes, enabledSignatureSchemesPatterns); + if (signatureSchemes != null) { + if (LOG.isDebugEnabled()) { + LOG.debug(SSL_SOCKET_SIGNATURE_SCHEME_LOG_MSG, + socket, + enabledSignatureSchemes, + enabledSignatureSchemesPatterns, + socket.getSSLParameters().getSignatureSchemes(), + signatureSchemes); + } + SSLParameters params = socket.getSSLParameters(); + params.setSignatureSchemes(signatureSchemes); + socket.setSSLParameters(params); + } + return socket; } }; @@ -720,6 +851,17 @@ public abstract class BaseSSLContextParameters extends JsseParameters { enabledNamedGroupsPatterns = null; } + final List<String> enabledSignatureSchemes = this.getSignatureSchemes() == null + ? null : this.parsePropertyValues(this.getSignatureSchemes().getSignatureScheme()); + + final Patterns enabledSignatureSchemesPatterns; + + if (this.getSignatureSchemesFilter() != null) { + enabledSignatureSchemesPatterns = this.getSignatureSchemesFilter().getPatterns(); + } else { + enabledSignatureSchemesPatterns = null; + } + // final boolean allowPassthrough = getAllowPassthrough(); @@ -787,6 +929,23 @@ public abstract class BaseSSLContextParameters extends JsseParameters { socket.setSSLParameters(params); } + String[] signatureSchemes = resolveSignatureSchemes( + socket.getSSLParameters().getSignatureSchemes(), + enabledSignatureSchemes, enabledSignatureSchemesPatterns); + if (signatureSchemes != null) { + if (LOG.isDebugEnabled()) { + LOG.debug(SSL_SERVER_SOCKET_SIGNATURE_SCHEME_LOG_MSG, + socket, + enabledSignatureSchemes, + enabledSignatureSchemesPatterns, + socket.getSSLParameters().getSignatureSchemes(), + signatureSchemes); + } + SSLParameters params = socket.getSSLParameters(); + params.setSignatureSchemes(signatureSchemes); + socket.setSSLParameters(params); + } + return socket; } }; @@ -962,6 +1121,41 @@ public abstract class BaseSSLContextParameters extends JsseParameters { return filteredNamedGroups.toArray(new String[0]); } + /** + * Resolves the signature schemes to configure based on explicit values or filter patterns. Returns {@code null} if + * no signature schemes configuration is needed (both parameters are {@code null}). + * + * @param currentSignatureSchemes the currently available signature schemes from the SSL object + * @param enabledSignatureSchemes the optional explicit signature schemes list + * @param enabledSignatureSchemesPatterns the optional filter patterns + * + * @return the filtered signature schemes array, or {@code null} if no configuration + * is needed + */ + private String[] resolveSignatureSchemes( + String[] currentSignatureSchemes, List<String> enabledSignatureSchemes, + Patterns enabledSignatureSchemesPatterns) { + + if (enabledSignatureSchemes == null && enabledSignatureSchemesPatterns == null) { + return null; + } + + if (currentSignatureSchemes == null) { + currentSignatureSchemes = new String[0]; + } + + Collection<String> filteredSignatureSchemes; + if (enabledSignatureSchemes != null) { + filteredSignatureSchemes = new ArrayList<>(enabledSignatureSchemes); + } else { + filteredSignatureSchemes = this.filter( + null, Arrays.asList(currentSignatureSchemes), + enabledSignatureSchemesPatterns.getIncludes(), enabledSignatureSchemesPatterns.getExcludes()); + } + + return filteredSignatureSchemes.toArray(new String[0]); + } + /** * Configures a {@code T} based on the related configuration options. */ @@ -1308,4 +1502,12 @@ public abstract class BaseSSLContextParameters extends JsseParameters { + "\t available named groups [{}]," + LS + "\t Resulting enabled named groups are [{}]."; } + + private static String createSignatureSchemeLogMessage(String entityName) { + return "Configuring " + entityName + " [{}] with " + LS + + "\t explicitly set signature schemes [{}]," + LS + + "\t signature scheme patterns [{}]," + LS + + "\t available signature schemes [{}]," + LS + + "\t Resulting enabled signature schemes are [{}]."; + } } diff --git a/core/camel-api/src/main/java/org/apache/camel/support/jsse/SignatureSchemesParameters.java b/core/camel-api/src/main/java/org/apache/camel/support/jsse/SignatureSchemesParameters.java new file mode 100644 index 000000000000..bb13c65825b3 --- /dev/null +++ b/core/camel-api/src/main/java/org/apache/camel/support/jsse/SignatureSchemesParameters.java @@ -0,0 +1,65 @@ +/* + * 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 signature schemes for use in TLS handshakes. Signature schemes control which signature + * algorithms are available during the TLS handshake, including post-quantum signature algorithms such as ML-DSA. + */ +public class SignatureSchemesParameters { + private List<String> signatureScheme; + + /** + * Returns a live reference to the list of signature scheme names. + * + * @return a reference to the list, never {@code null} + */ + public List<String> getSignatureScheme() { + if (this.signatureScheme == null) { + this.signatureScheme = new ArrayList<>(); + } + return this.signatureScheme; + } + + public void addSignatureScheme(String scheme) { + if (this.signatureScheme == null) { + this.signatureScheme = new ArrayList<>(); + } + this.signatureScheme.add(scheme.trim()); + } + + /** + * Sets the signature schemes. It creates a copy of the given list. + * + * @param signatureScheme signature schemes + */ + public void setSignatureScheme(List<String> signatureScheme) { + this.signatureScheme = signatureScheme == null ? null : new ArrayList<>(signatureScheme); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("SignatureSchemesParameters[signatureScheme="); + builder.append(getSignatureScheme()); + 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 ecefb19403b1..3fd51d5143a2 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 @@ -4,3 +4,4 @@ FilterParametersDefinition NamedGroupsParametersDefinition SNIHostNamesDefinition SecureSocketProtocolsParametersDefinition +SignatureSchemesParametersDefinition 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 ff0d4d944dc9..25cea709bf5b 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 @@ -25,6 +25,7 @@ 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; +import org.apache.camel.support.jsse.SignatureSchemesParameters; @XmlTransient public abstract class AbstractBaseSSLContextParametersFactoryBean<T extends BaseSSLContextParameters> @@ -42,6 +43,10 @@ public abstract class AbstractBaseSSLContextParametersFactoryBean<T extends Base private FilterParametersDefinition namedGroupsFilter; + private SignatureSchemesParametersDefinition signatureSchemes; + + private FilterParametersDefinition signatureSchemesFilter; + @XmlAttribute @Metadata(description = "The optional SSLSessionContext timeout time for javax.net.ssl.SSLSession in seconds.") private String sessionTimeout; @@ -98,6 +103,16 @@ public abstract class AbstractBaseSSLContextParametersFactoryBean<T extends Base newInstance.setNamedGroupsFilter(createFilterParameters(namedGroupsFilter)); } + if (signatureSchemes != null) { + SignatureSchemesParameters signatureSchemesInstance = new SignatureSchemesParameters(); + signatureSchemesInstance.setSignatureScheme(signatureSchemes.getSignatureScheme()); + newInstance.setSignatureSchemes(signatureSchemesInstance); + } + + if (signatureSchemesFilter != null) { + newInstance.setSignatureSchemesFilter(createFilterParameters(signatureSchemesFilter)); + } + if (sessionTimeout != null) { newInstance.setSessionTimeout(sessionTimeout); } @@ -162,6 +177,22 @@ public abstract class AbstractBaseSSLContextParametersFactoryBean<T extends Base this.namedGroupsFilter = namedGroupsFilter; } + public SignatureSchemesParametersDefinition getSignatureSchemes() { + return signatureSchemes; + } + + public void setSignatureSchemes(SignatureSchemesParametersDefinition signatureSchemes) { + this.signatureSchemes = signatureSchemes; + } + + public FilterParametersDefinition getSignatureSchemesFilter() { + return signatureSchemesFilter; + } + + public void setSignatureSchemesFilter(FilterParametersDefinition signatureSchemesFilter) { + this.signatureSchemesFilter = signatureSchemesFilter; + } + public String getSessionTimeout() { return sessionTimeout; } diff --git a/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/util/jsse/SignatureSchemesParametersDefinition.java b/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/util/jsse/SignatureSchemesParametersDefinition.java new file mode 100644 index 000000000000..9ccb7a46321c --- /dev/null +++ b/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/util/jsse/SignatureSchemesParametersDefinition.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 = "signatureSchemesParameters", propOrder = { "signatureScheme" }) +public class SignatureSchemesParametersDefinition { + + @Metadata(description = "List of TLS/SSL signature schemes") + private List<String> signatureScheme; + + /** + * Returns a live reference to the list of signature scheme names. + * + * @return a reference to the list, never {@code null} + */ + public List<String> getSignatureScheme() { + if (this.signatureScheme == null) { + this.signatureScheme = new ArrayList<>(); + } + return this.signatureScheme; + } +} 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 0ad671cf15a3..903e69c5d6a5 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 @@ -900,6 +900,151 @@ public class SSLContextParametersTest extends AbstractJsseParametersTest { assertEquals("x25519", engine.getSSLParameters().getNamedGroups()[0]); } + @Test + public void testSignatureSchemes() throws Exception { + SSLContext controlContext = SSLContext.getInstance("TLSv1.3"); + controlContext.init(null, null, null); + SSLEngine controlEngine = controlContext.createSSLEngine(); + String[] controlSignatureSchemes = controlEngine.getSSLParameters().getSignatureSchemes(); + + // default - no signature schemes 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(controlSignatureSchemes, engine.getSSLParameters().getSignatureSchemes()); + assertArrayEquals(controlSignatureSchemes, socket.getSSLParameters().getSignatureSchemes()); + assertArrayEquals(controlSignatureSchemes, serverSocket.getSSLParameters().getSignatureSchemes()); + + // empty ssp - sets empty list + SignatureSchemesParameters ssp = new SignatureSchemesParameters(); + scp.setSignatureSchemes(ssp); + context = scp.createSSLContext(null); + engine = context.createSSLEngine(); + socket = (SSLSocket) context.getSocketFactory().createSocket(); + serverSocket = (SSLServerSocket) context.getServerSocketFactory().createServerSocket(); + + assertEquals(0, engine.getSSLParameters().getSignatureSchemes().length); + assertEquals(0, socket.getSSLParameters().getSignatureSchemes().length); + assertEquals(0, serverSocket.getSSLParameters().getSignatureSchemes().length); + + // explicit signature scheme + ssp.setSignatureScheme(Collections.singletonList("rsa_pss_rsae_sha256")); + context = scp.createSSLContext(null); + engine = context.createSSLEngine(); + socket = (SSLSocket) context.getSocketFactory().createSocket(); + serverSocket = (SSLServerSocket) context.getServerSocketFactory().createServerSocket(); + + assertEquals(1, engine.getSSLParameters().getSignatureSchemes().length); + assertEquals("rsa_pss_rsae_sha256", engine.getSSLParameters().getSignatureSchemes()[0]); + assertEquals(1, socket.getSSLParameters().getSignatureSchemes().length); + assertEquals("rsa_pss_rsae_sha256", socket.getSSLParameters().getSignatureSchemes()[0]); + assertEquals(1, serverSocket.getSSLParameters().getSignatureSchemes().length); + assertEquals("rsa_pss_rsae_sha256", serverSocket.getSSLParameters().getSignatureSchemes()[0]); + + // explicit signature schemes override filter + FilterParameters filter = new FilterParameters(); + filter.getInclude().add(".*"); + scp.setSignatureSchemesFilter(filter); + context = scp.createSSLContext(null); + engine = context.createSSLEngine(); + socket = (SSLSocket) context.getSocketFactory().createSocket(); + serverSocket = (SSLServerSocket) context.getServerSocketFactory().createServerSocket(); + + assertEquals(1, engine.getSSLParameters().getSignatureSchemes().length); + assertEquals("rsa_pss_rsae_sha256", engine.getSSLParameters().getSignatureSchemes()[0]); + assertEquals(1, socket.getSSLParameters().getSignatureSchemes().length); + assertEquals("rsa_pss_rsae_sha256", socket.getSSLParameters().getSignatureSchemes()[0]); + assertEquals(1, serverSocket.getSSLParameters().getSignatureSchemes().length); + assertEquals("rsa_pss_rsae_sha256", serverSocket.getSSLParameters().getSignatureSchemes()[0]); + } + + @Test + public void testSignatureSchemesFilter() throws Exception { + // Note: SSLParameters.getSignatureSchemes() returns null by default (unlike getNamedGroups()), + // so filters operate on explicitly provided schemes rather than JDK defaults. + + // default - no filter, keeps defaults (null) + 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(); + + assertNull(engine.getSSLParameters().getSignatureSchemes()); + assertNull(socket.getSSLParameters().getSignatureSchemes()); + assertNull(serverSocket.getSSLParameters().getSignatureSchemes()); + + // empty filter - no includes means no schemes match (empty array) + FilterParameters filter = new FilterParameters(); + scp.setSignatureSchemesFilter(filter); + context = scp.createSSLContext(null); + engine = context.createSSLEngine(); + socket = (SSLSocket) context.getSocketFactory().createSocket(); + serverSocket = (SSLServerSocket) context.getServerSocketFactory().createServerSocket(); + + assertEquals(0, engine.getSSLParameters().getSignatureSchemes().length); + assertEquals(0, socket.getSSLParameters().getSignatureSchemes().length); + assertEquals(0, serverSocket.getSSLParameters().getSignatureSchemes().length); + + // explicit schemes override filter - filter ignored when schemes are set + SignatureSchemesParameters ssp = new SignatureSchemesParameters(); + List<String> allSchemes = new LinkedList<>(); + allSchemes.add("ecdsa_secp256r1_sha256"); + allSchemes.add("ecdsa_secp384r1_sha384"); + allSchemes.add("rsa_pss_rsae_sha256"); + allSchemes.add("ed25519"); + ssp.setSignatureScheme(allSchemes); + scp.setSignatureSchemes(ssp); + + filter.getInclude().add("ecdsa_.*"); + context = scp.createSSLContext(null); + engine = context.createSSLEngine(); + + // explicit schemes take precedence over filter + assertEquals(4, engine.getSSLParameters().getSignatureSchemes().length); + + // clear explicit schemes, keep filter - now filter applies to empty JDK defaults + scp.setSignatureSchemes(null); + filter.getInclude().clear(); + filter.getInclude().add(".*"); + context = scp.createSSLContext(null); + engine = context.createSSLEngine(); + socket = (SSLSocket) context.getSocketFactory().createSocket(); + serverSocket = (SSLServerSocket) context.getServerSocketFactory().createServerSocket(); + + // JDK defaults are null → filtering null gives empty array + assertEquals(0, engine.getSSLParameters().getSignatureSchemes().length); + assertEquals(0, socket.getSSLParameters().getSignatureSchemes().length); + assertEquals(0, serverSocket.getSSLParameters().getSignatureSchemes().length); + } + + @Test + public void testSignatureSchemesDoNotAffectOtherSettings() throws Exception { + SSLContext controlContext = SSLContext.getInstance("TLSv1.3"); + controlContext.init(null, null, null); + SSLEngine controlEngine = controlContext.createSSLEngine(); + + // setting signature schemes should not change cipher suites, protocols, or named groups + SSLContextParameters scp = new SSLContextParameters(); + SignatureSchemesParameters ssp = new SignatureSchemesParameters(); + ssp.setSignatureScheme(Collections.singletonList("rsa_pss_rsae_sha256")); + scp.setSignatureSchemes(ssp); + + SSLContext context = scp.createSSLContext(null); + SSLEngine engine = context.createSSLEngine(); + + assertArrayEquals(controlEngine.getEnabledCipherSuites(), engine.getEnabledCipherSuites()); + assertArrayEquals(controlEngine.getEnabledProtocols(), engine.getEnabledProtocols()); + assertArrayEquals(controlEngine.getSSLParameters().getNamedGroups(), engine.getSSLParameters().getNamedGroups()); + assertEquals(1, engine.getSSLParameters().getSignatureSchemes().length); + assertEquals("rsa_pss_rsae_sha256", engine.getSSLParameters().getSignatureSchemes()[0]); + } + @Test public void testSessionTimeout() throws Exception { SSLContextParameters scp = new SSLContextParameters(); diff --git a/core/camel-main/src/generated/java/org/apache/camel/main/SSLConfigurationPropertiesConfigurer.java b/core/camel-main/src/generated/java/org/apache/camel/main/SSLConfigurationPropertiesConfigurer.java index bd5547568b98..375dd286459a 100644 --- a/core/camel-main/src/generated/java/org/apache/camel/main/SSLConfigurationPropertiesConfigurer.java +++ b/core/camel-main/src/generated/java/org/apache/camel/main/SSLConfigurationPropertiesConfigurer.java @@ -42,6 +42,9 @@ public class SSLConfigurationPropertiesConfigurer extends org.apache.camel.suppo map.put("SecureRandomProvider", java.lang.String.class); map.put("SecureSocketProtocol", java.lang.String.class); map.put("SessionTimeout", int.class); + map.put("SignatureSchemes", java.lang.String.class); + map.put("SignatureSchemesExclude", java.lang.String.class); + map.put("SignatureSchemesInclude", java.lang.String.class); map.put("TrustAllCertificates", boolean.class); map.put("TrustStore", java.lang.String.class); map.put("TrustStorePassword", java.lang.String.class); @@ -90,6 +93,12 @@ public class SSLConfigurationPropertiesConfigurer extends org.apache.camel.suppo case "secureSocketProtocol": target.setSecureSocketProtocol(property(camelContext, java.lang.String.class, value)); return true; case "sessiontimeout": case "sessionTimeout": target.setSessionTimeout(property(camelContext, int.class, value)); return true; + case "signatureschemes": + case "signatureSchemes": target.setSignatureSchemes(property(camelContext, java.lang.String.class, value)); return true; + case "signatureschemesexclude": + case "signatureSchemesExclude": target.setSignatureSchemesExclude(property(camelContext, java.lang.String.class, value)); return true; + case "signatureschemesinclude": + case "signatureSchemesInclude": target.setSignatureSchemesInclude(property(camelContext, java.lang.String.class, value)); return true; case "trustallcertificates": case "trustAllCertificates": target.setTrustAllCertificates(property(camelContext, boolean.class, value)); return true; case "truststore": @@ -146,6 +155,12 @@ public class SSLConfigurationPropertiesConfigurer extends org.apache.camel.suppo case "secureSocketProtocol": return java.lang.String.class; case "sessiontimeout": case "sessionTimeout": return int.class; + case "signatureschemes": + case "signatureSchemes": return java.lang.String.class; + case "signatureschemesexclude": + case "signatureSchemesExclude": return java.lang.String.class; + case "signatureschemesinclude": + case "signatureSchemesInclude": return java.lang.String.class; case "trustallcertificates": case "trustAllCertificates": return boolean.class; case "truststore": @@ -198,6 +213,12 @@ public class SSLConfigurationPropertiesConfigurer extends org.apache.camel.suppo case "secureSocketProtocol": return target.getSecureSocketProtocol(); case "sessiontimeout": case "sessionTimeout": return target.getSessionTimeout(); + case "signatureschemes": + case "signatureSchemes": return target.getSignatureSchemes(); + case "signatureschemesexclude": + case "signatureSchemesExclude": return target.getSignatureSchemesExclude(); + case "signatureschemesinclude": + case "signatureSchemesInclude": return target.getSignatureSchemesInclude(); case "trustallcertificates": case "trustAllCertificates": return target.isTrustAllCertificates(); case "truststore": diff --git a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json index 5e5d6ba74821..1b156fda5539 100644 --- a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json +++ b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json @@ -351,6 +351,9 @@ { "name": "camel.ssl.secureRandomProvider", "required": false, "description": "To use a specific provider for creating SecureRandom. The list of available providers returned by java.security.Security.getProviders() or null to use the highest priority provider implementing the secure socket protocol.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.secureSocketProtocol", "required": false, "description": "The protocol for the secure sockets created by the SSLContext. See https:\/\/docs.oracle.com\/en\/java\/javase\/17\/docs\/specs\/security\/standard-names.html", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "TLSv1.3", "secret": false }, { "name": "camel.ssl.sessionTimeout", "required": false, "description": "Timeout in seconds to use for SSLContext. The default is 24 hours.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "integer", "javaType": "int", "defaultValue": 86400, "secret": false }, + { "name": "camel.ssl.signatureSchemes", "required": false, "description": "List of TLS\/SSL signature schemes. Multiple names can be separated by comma. Signature schemes control which signature algorithms are available during the TLS handshake, including post-quantum signature algorithms such as ML-DSA.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, + { "name": "camel.ssl.signatureSchemesExclude", "required": false, "description": "Filters TLS\/SSL signature schemes. This filter is used for excluding signature schemes that match the naming pattern. Multiple names can be separated by comma. Notice that if the signatureSchemes option has been configured then the include\/exclude filters are not in use.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, + { "name": "camel.ssl.signatureSchemesInclude", "required": false, "description": "Filters TLS\/SSL signature schemes. This filter is used for including signature schemes that match the naming pattern. Multiple names can be separated by comma. Notice that if the signatureSchemes option has been configured then the include\/exclude filters are not in use.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.ssl.trustAllCertificates", "required": false, "description": "Allows to trust all SSL certificates without performing certificate validation. This can be used in development environment but may expose the system to security risks. Notice that if the trustAllCertificates option is set to true then the trustStore\/trustStorePassword options are not in use.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "boolean", "javaType": "boolean", "def [...] { "name": "camel.ssl.trustStore", "required": false, "description": "The trust store to load. The trust store is by default loaded from classpath. If you must load from file system, then use file: as prefix. file:nameOfFile (to refer to the file system) classpath:nameOfFile (to refer to the classpath; default) http:uri (to load the resource using HTTP) ref:nameOfBean (to lookup an existing KeyStore instance from the registry, for example for testing and development).", "sourceType": [...] { "name": "camel.ssl.trustStorePassword", "required": false, "description": "Sets the SSL Truststore password.", "sourceType": "org.apache.camel.main.SSLConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, diff --git a/core/camel-main/src/main/docs/main.adoc b/core/camel-main/src/main/docs/main.adoc index 9d52d82ad48b..eb81817ede0f 100644 --- a/core/camel-main/src/main/docs/main.adoc +++ b/core/camel-main/src/main/docs/main.adoc @@ -299,7 +299,7 @@ The camel.trace supports 14 options, which are listed below. === Camel SSL configurations -The camel.ssl supports 23 options, which are listed below. +The camel.ssl supports 26 options, which are listed below. [width="100%",cols="2,5,^1,2",options="header"] |=== @@ -324,6 +324,9 @@ The camel.ssl supports 23 options, which are listed below. | *camel.ssl.secureRandomProvider* | To use a specific provider for creating SecureRandom. The list of available providers returned by java.security.Security.getProviders() or null to use the highest priority provider implementing the secure socket protocol. | | String | *camel.ssl.secureSocketProtocol* | The protocol for the secure sockets created by the SSLContext. See \https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html | TLSv1.3 | String | *camel.ssl.sessionTimeout* | Timeout in seconds to use for SSLContext. The default is 24 hours. | 86400 | int +| *camel.ssl.signatureSchemes* | List of TLS/SSL signature schemes. Multiple names can be separated by comma. Signature schemes control which signature algorithms are available during the TLS handshake, including post-quantum signature algorithms such as ML-DSA. | | String +| *camel.ssl.signatureSchemes{zwsp}Exclude* | Filters TLS/SSL signature schemes. This filter is used for excluding signature schemes that match the naming pattern. Multiple names can be separated by comma. Notice that if the signatureSchemes option has been configured then the include/exclude filters are not in use. | | String +| *camel.ssl.signatureSchemes{zwsp}Include* | Filters TLS/SSL signature schemes. This filter is used for including signature schemes that match the naming pattern. Multiple names can be separated by comma. Notice that if the signatureSchemes option has been configured then the include/exclude filters are not in use. | | String | *camel.ssl.trustAllCertificates* | Allows to trust all SSL certificates without performing certificate validation. This can be used in development environment but may expose the system to security risks. Notice that if the trustAllCertificates option is set to true then the trustStore/trustStorePassword options are not in use. | false | boolean | *camel.ssl.trustStore* | The trust store to load. The trust store is by default loaded from classpath. If you must load from file system, then use file: as prefix. file:nameOfFile (to refer to the file system) classpath:nameOfFile (to refer to the classpath; default) http:uri (to load the resource using HTTP) ref:nameOfBean (to lookup an existing KeyStore instance from the registry, for example for testing and development). | | String | *camel.ssl.trustStorePassword* | Sets the SSL Truststore password. | | String diff --git a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java index 05b3ec116032..e73e1565752c 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java @@ -101,6 +101,7 @@ import org.apache.camel.support.jsse.NamedGroupsParameters; import org.apache.camel.support.jsse.SSLContextParameters; import org.apache.camel.support.jsse.SSLContextServerParameters; import org.apache.camel.support.jsse.SecureRandomParameters; +import org.apache.camel.support.jsse.SignatureSchemesParameters; import org.apache.camel.support.jsse.TrustAllTrustManager; import org.apache.camel.support.jsse.TrustManagersParameters; import org.apache.camel.support.scan.PackageScanHelper; @@ -2220,6 +2221,27 @@ public abstract class BaseMainSupport extends BaseService { } sslContextParameters.setNamedGroupsFilter(fp); } + if (sslConfig.getSignatureSchemes() != null) { + SignatureSchemesParameters ssp = new SignatureSchemesParameters(); + for (String s : sslConfig.getSignatureSchemes().split(",")) { + ssp.addSignatureScheme(s); + } + sslContextParameters.setSignatureSchemes(ssp); + } + if (sslConfig.getSignatureSchemesInclude() != null || sslConfig.getSignatureSchemesExclude() != null) { + FilterParameters fp = new FilterParameters(); + if (sslConfig.getSignatureSchemesInclude() != null) { + for (String s : sslConfig.getSignatureSchemesInclude().split(",")) { + fp.addInclude(s); + } + } + if (sslConfig.getSignatureSchemesExclude() != null) { + for (String s : sslConfig.getSignatureSchemesExclude().split(",")) { + fp.addExclude(s); + } + } + sslContextParameters.setSignatureSchemesFilter(fp); + } sslContextParameters.setKeyManagers(kmp); sslContextParameters.setTrustManagers(tmp); sslContextParameters.setServerParameters(scsp); diff --git a/core/camel-main/src/main/java/org/apache/camel/main/SSLConfigurationProperties.java b/core/camel-main/src/main/java/org/apache/camel/main/SSLConfigurationProperties.java index 4d6395d14120..77b84bd3b5a7 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/SSLConfigurationProperties.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/SSLConfigurationProperties.java @@ -50,6 +50,12 @@ public class SSLConfigurationProperties implements BootstrapCloseable { private String namedGroupsInclude; @Metadata(label = "advanced") private String namedGroupsExclude; + @Metadata(label = "advanced") + private String signatureSchemes; + @Metadata(label = "advanced") + private String signatureSchemesInclude; + @Metadata(label = "advanced") + private String signatureSchemesExclude; @Metadata private String keyStore; @Metadata(label = "advanced") @@ -237,6 +243,52 @@ public class SSLConfigurationProperties implements BootstrapCloseable { this.namedGroupsExclude = namedGroupsExclude; } + public String getSignatureSchemes() { + return signatureSchemes; + } + + /** + * List of TLS/SSL signature schemes. Multiple names can be separated by comma. + * <p> + * Signature schemes control which signature algorithms are available during the TLS handshake, including + * post-quantum signature algorithms such as ML-DSA. + */ + public void setSignatureSchemes(String signatureSchemes) { + this.signatureSchemes = signatureSchemes; + } + + public String getSignatureSchemesInclude() { + return signatureSchemesInclude; + } + + /** + * Filters TLS/SSL signature schemes. + * <p> + * This filter is used for including signature schemes that match the naming pattern. Multiple names can be + * separated by comma. + * <p> + * Notice that if the signatureSchemes option has been configured then the include/exclude filters are not in use. + */ + public void setSignatureSchemesInclude(String signatureSchemesInclude) { + this.signatureSchemesInclude = signatureSchemesInclude; + } + + public String getSignatureSchemesExclude() { + return signatureSchemesExclude; + } + + /** + * Filters TLS/SSL signature schemes. + * <p> + * This filter is used for excluding signature schemes that match the naming pattern. Multiple names can be + * separated by comma. + * <p> + * Notice that if the signatureSchemes option has been configured then the include/exclude filters are not in use. + */ + public void setSignatureSchemesExclude(String signatureSchemesExclude) { + this.signatureSchemesExclude = signatureSchemesExclude; + } + public String getKeyStore() { return keyStore; } @@ -514,6 +566,43 @@ public class SSLConfigurationProperties implements BootstrapCloseable { return this; } + /** + * List of TLS/SSL signature schemes. Multiple names can be separated by comma. + * <p> + * Signature schemes control which signature algorithms are available during the TLS handshake, including + * post-quantum signature algorithms such as ML-DSA. + */ + public SSLConfigurationProperties withSignatureSchemes(String signatureSchemes) { + this.signatureSchemes = signatureSchemes; + return this; + } + + /** + * Filters TLS/SSL signature schemes. + * <p> + * This filter is used for including signature schemes that match the naming pattern. Multiple names can be + * separated by comma. + * <p> + * Notice that if the signatureSchemes option has been configured then the include/exclude filters are not in use. + */ + public SSLConfigurationProperties withSignatureSchemesInclude(String signatureSchemesInclude) { + this.signatureSchemesInclude = signatureSchemesInclude; + return this; + } + + /** + * Filters TLS/SSL signature schemes. + * <p> + * This filter is used for excluding signature schemes that match the naming pattern. Multiple names can be + * separated by comma. + * <p> + * Notice that if the signatureSchemes option has been configured then the include/exclude filters are not in use. + */ + public SSLConfigurationProperties withSignatureSchemesExclude(String signatureSchemesExclude) { + this.signatureSchemesExclude = signatureSchemesExclude; + return this; + } + /** * The keystore to load. * diff --git a/core/camel-main/src/test/java/org/apache/camel/main/MainSSLTest.java b/core/camel-main/src/test/java/org/apache/camel/main/MainSSLTest.java index 50353a31899a..21f22e7f1cb6 100644 --- a/core/camel-main/src/test/java/org/apache/camel/main/MainSSLTest.java +++ b/core/camel-main/src/test/java/org/apache/camel/main/MainSSLTest.java @@ -26,6 +26,7 @@ import org.apache.camel.support.jsse.KeyStoreParameters; import org.apache.camel.support.jsse.NamedGroupsParameters; import org.apache.camel.support.jsse.SSLContextParameters; import org.apache.camel.support.jsse.SSLContextServerParameters; +import org.apache.camel.support.jsse.SignatureSchemesParameters; import org.apache.camel.support.jsse.TrustAllTrustManager; import org.apache.camel.support.jsse.TrustManagersParameters; import org.junit.jupiter.api.Assertions; @@ -290,4 +291,131 @@ public class MainSSLTest { main.stop(); } + + @Test + public void testMainSSLSignatureSchemes() { + Main main = new Main(); + + main.addInitialProperty("camel.ssl.enabled", "true"); + main.addInitialProperty("camel.ssl.keyStore", "server.jks"); + main.addInitialProperty("camel.ssl.keystorePassword", "security"); + main.addInitialProperty("camel.ssl.signatureSchemes", "ed25519,rsa_pss_rsae_sha256,ecdsa_secp256r1_sha256"); + + main.start(); + + CamelContext context = main.getCamelContext(); + assertNotNull(context); + + SSLContextParameters sslParams = context.getSSLContextParameters(); + assertNotNull(sslParams); + + SignatureSchemesParameters ssp = sslParams.getSignatureSchemes(); + assertNotNull(ssp); + + List<String> schemes = ssp.getSignatureScheme(); + Assertions.assertEquals(3, schemes.size()); + Assertions.assertEquals("ed25519", schemes.get(0)); + Assertions.assertEquals("rsa_pss_rsae_sha256", schemes.get(1)); + Assertions.assertEquals("ecdsa_secp256r1_sha256", schemes.get(2)); + + assertNull(sslParams.getSignatureSchemesFilter()); + + main.stop(); + } + + @Test + public void testMainSSLSignatureSchemesFluent() { + Main main = new Main(); + + main.configure().sslConfig() + .withEnabled(true) + .withKeyStore("server.jks") + .withKeystorePassword("security") + .withSignatureSchemes("ed25519,rsa_pss_rsae_sha256"); + + main.start(); + + CamelContext context = main.getCamelContext(); + assertNotNull(context); + + SSLContextParameters sslParams = context.getSSLContextParameters(); + assertNotNull(sslParams); + + SignatureSchemesParameters ssp = sslParams.getSignatureSchemes(); + assertNotNull(ssp); + + List<String> schemes = ssp.getSignatureScheme(); + Assertions.assertEquals(2, schemes.size()); + Assertions.assertEquals("ed25519", schemes.get(0)); + Assertions.assertEquals("rsa_pss_rsae_sha256", schemes.get(1)); + + main.stop(); + } + + @Test + public void testMainSSLSignatureSchemesFilter() { + Main main = new Main(); + + main.addInitialProperty("camel.ssl.enabled", "true"); + main.addInitialProperty("camel.ssl.keyStore", "server.jks"); + main.addInitialProperty("camel.ssl.keystorePassword", "security"); + main.addInitialProperty("camel.ssl.signatureSchemesInclude", "ecdsa_.*,ed.*"); + main.addInitialProperty("camel.ssl.signatureSchemesExclude", "ed448"); + + main.start(); + + CamelContext context = main.getCamelContext(); + assertNotNull(context); + + SSLContextParameters sslParams = context.getSSLContextParameters(); + assertNotNull(sslParams); + + assertNull(sslParams.getSignatureSchemes()); + + FilterParameters fp = sslParams.getSignatureSchemesFilter(); + assertNotNull(fp); + + List<String> includes = fp.getInclude(); + Assertions.assertEquals(2, includes.size()); + Assertions.assertEquals("ecdsa_.*", includes.get(0)); + Assertions.assertEquals("ed.*", includes.get(1)); + + List<String> excludes = fp.getExclude(); + Assertions.assertEquals(1, excludes.size()); + Assertions.assertEquals("ed448", excludes.get(0)); + + main.stop(); + } + + @Test + public void testMainSSLSignatureSchemesFilterFluent() { + Main main = new Main(); + + main.configure().sslConfig() + .withEnabled(true) + .withKeyStore("server.jks") + .withKeystorePassword("security") + .withSignatureSchemesInclude("ecdsa_.*") + .withSignatureSchemesExclude("ed448"); + + main.start(); + + CamelContext context = main.getCamelContext(); + assertNotNull(context); + + SSLContextParameters sslParams = context.getSSLContextParameters(); + assertNotNull(sslParams); + + assertNull(sslParams.getSignatureSchemes()); + + FilterParameters fp = sslParams.getSignatureSchemesFilter(); + assertNotNull(fp); + + Assertions.assertEquals(1, fp.getInclude().size()); + Assertions.assertEquals("ecdsa_.*", fp.getInclude().get(0)); + Assertions.assertEquals(1, fp.getExclude().size()); + Assertions.assertEquals("ed448", fp.getExclude().get(0)); + + main.stop(); + } }
