Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ public static TLSClientParameters createTLSClientParametersFromType(TLSClientPar
if (params.isSetCertAlias()) {
ret.setCertAlias(params.getCertAlias());
}
if (params.isSetNamedGroups()) {
ret.setNamedGroups(params.getNamedGroups().getNamedGroup());
}
if (iparams != null && iparams.isSetKeyManagersRef() && !usingDefaults) {
ret.setKeyManagers(iparams.getKeyManagersRef());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public class TLSParameterBase {
protected SecureRandom secureRandom;
protected String protocol;
protected String certAlias;
protected List<String> namedGroups = new ArrayList<>();
/**
* Set the JSSE provider. If not set,
* it uses system default.
Expand Down Expand Up @@ -187,4 +188,24 @@ public final void setCertAlias(String ctAlias) {
public String getCertAlias() {
return certAlias;
}

/**
* Sets the ordered list of TLS named groups (key-exchange groups) to offer
* during the handshake, e.g. {@code "X25519MLKEM768"}, {@code "x25519"}.
* Applied via {@code SSLParameters.setNamedGroups()} which requires JDK 20+;
* silently ignored on older JDKs.
*/
public final void setNamedGroups(List<String> groups) {
namedGroups = groups;
}

/**
* Returns the configured TLS named groups list.
*/
public List<String> getNamedGroups() {
if (namedGroups == null) {
namedGroups = new ArrayList<>();
}
return namedGroups;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ public TLSServerParametersConfig(TLSServerParametersType params)
if (params.isSetSniHostCheck()) {
this.setSniHostCheck(params.isSniHostCheck());
}
if (params.isSetNamedGroups()) {
this.setNamedGroups(params.getNamedGroups().getNamedGroup());
}
if (iparams != null && iparams.isSetKeyManagersRef()) {
this.setKeyManagers(iparams.getKeyManagersRef());
}
Expand Down
32 changes: 32 additions & 0 deletions core/src/main/resources/schemas/configuration/security.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,20 @@
<xs:element name="includeProtocol" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="NamedGroups">
<xs:annotation>
<xs:documentation>
This structure holds an ordered list of TLS named groups (key-exchange groups) to offer
during the handshake. Examples: "X25519MLKEM768" (PQC hybrid, JDK 27+ / BC JSSE 1.81+),
"x25519", "secp256r1". When set, the list is applied via SSLParameters.setNamedGroups()
(requires JDK 20+; silently ignored on older JDKs).
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="namedGroup" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="SecureRandomParameters">
<xs:annotation>
Expand Down Expand Up @@ -512,6 +526,15 @@
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="namedGroups" type="tns:NamedGroups" minOccurs="0">
<xs:annotation>
<xs:documentation>
This element contains the ordered list of TLS named groups to offer during
the handshake (e.g. "X25519MLKEM768", "x25519", "secp256r1").
Requires JDK 20+; silently ignored on older JDKs.
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:all>
<xs:attribute name="useHttpsURLConnectionDefaultSslSocketFactory" type="pt:ParameterizedBoolean" default="false">
<xs:annotation>
Expand Down Expand Up @@ -651,6 +674,15 @@
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="namedGroups" type="tns:NamedGroups" minOccurs="0">
<xs:annotation>
<xs:documentation>
This element contains the ordered list of TLS named groups to offer during
the handshake (e.g. "X25519MLKEM768", "x25519", "secp256r1").
Requires JDK 20+; silently ignored on older JDKs.
</xs:documentation>
</xs:annotation>
</xs:element>
</xs:all>
<xs:attribute name="jsseProvider" type="xs:string">
<xs:annotation>
Expand Down
2 changes: 1 addition & 1 deletion parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
<cxf.assertj.version>4.0.0-M1</cxf.assertj.version>
<cxf.atmosphere.version.range>[3.0, 4.0)</cxf.atmosphere.version.range>
<cxf.atmosphere.version>3.1.0</cxf.atmosphere.version>
<cxf.bcprov.version>1.81</cxf.bcprov.version>
<cxf.bcprov.version>1.84</cxf.bcprov.version>
<cxf.brave.version>6.3.1</cxf.brave.version>
<cxf.cdi.api.version>4.1.0</cxf.cdi.api.version>
<cxf.classgraph.version>4.8.25</cxf.classgraph.version>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* 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.cxf.transport.http_jetty;

import javax.net.ssl.SSLEngine;

import org.eclipse.jetty.alpn.server.ALPNServerConnection;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.ssl.ALPNProcessor;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.io.ssl.SslHandshakeListener;

/**
* Jetty {@link ALPNProcessor.Server} for BouncyCastle JSSE ({@code bctls-jdk18on}).
*
* <p>Jetty's built-in {@code JDK9ServerALPNProcessor} only matches engines whose
* module is {@code java.base}, so it rejects BC JSSE's {@code ProvSSLEngine} and
* throws {@code IllegalStateException: No suitable ALPNProcessor}. This processor
* bridges the gap: it claims BC JSSE engines and wires up the same
* {@link SSLEngine#setHandshakeApplicationProtocolSelector} callback that the
* built-in processor uses, delegating to {@link ALPNServerConnection#select}.
*
* <p>A {@link SslHandshakeListener} is also registered so that if the client does not
* advertise any ALPN protocols, {@link ALPNServerConnection#unsupported()} is called
* on handshake success to fall back to the server's default protocol (HTTP/1.1).
*
* <p>Registered via {@code META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Server}.
*/
public class BCJsseServerALPNProcessor implements ALPNProcessor.Server {

@Override
public boolean appliesTo(SSLEngine sslEngine) {
return sslEngine.getClass().getName().startsWith("org.bouncycastle.");
}

@Override
public void configure(SSLEngine sslEngine, Connection connection) {
ALPNServerConnection alpnConn = (ALPNServerConnection) connection;

// Called when the client includes an ALPN extension in its ClientHello.
sslEngine.setHandshakeApplicationProtocolSelector((engine, protocols) -> {
try {
alpnConn.select(protocols);
return alpnConn.getProtocol();
} catch (Throwable t) {
return null;
}
});

// When the client sends no ALPN extension the selector above is never invoked,
// leaving getProtocol() null. The SslHandshakeListener fires on handshake
// completion and calls unsupported() to fall back to the default protocol.
SslConnection.SslEndPoint sslEndPoint = (SslConnection.SslEndPoint) alpnConn.getEndPoint();
sslEndPoint.getSslConnection().addHandshakeListener(new SslHandshakeListener() {
@Override
public void handshakeSucceeded(Event event) {
if (alpnConn.getProtocol() == null) {
alpnConn.unsupported();
}
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@

import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;

import jakarta.annotation.PostConstruct;
import jakarta.servlet.RequestDispatcher;
Expand Down Expand Up @@ -731,6 +733,19 @@ protected void doStart() throws Exception {
public void checkKeyStore() {
//we'll handle this later
}
@Override
public SSLParameters customize(SSLParameters sslParams) {
sslParams = super.customize(sslParams);
org.apache.cxf.transport.https.SSLUtils.applyNamedGroups(
sslParams, tlsServerParameters.getNamedGroups());
return sslParams;
}
@Override
public void customize(SSLEngine sslEngine) {
super.customize(sslEngine);
org.apache.cxf.transport.https.SSLUtils.applyNamedGroupsBC(
sslEngine, tlsServerParameters.getNamedGroups());
}
};
decorateCXFJettySslSocketConnector(sslcf);
}
Expand Down Expand Up @@ -857,7 +872,21 @@ public Connection upgradeConnection(Connector c, EndPoint endPoint,
protected SSLContext createSSLContext(SslContextFactory scf) throws Exception {
// The full SSL context is provided by SSLContextServerParameters
if (tlsServerParameters instanceof SSLContextServerParameters sslContextServerParameters) {
return sslContextServerParameters.getSslContext();
SSLContext context = sslContextServerParameters.getSslContext();
// Without setting included cipher suites here, Jetty's default
// SslContextFactory filter drops suite names it does not
// recognise (e.g. BouncyCastle JSSE names), causing
// handshake_failure even if the SSLContext supports them.
String[] supported =
SSLUtils.getServerSupportedCipherSuites(context);
String[] included = SSLUtils.getCiphersuitesToInclude(
tlsServerParameters.getCipherSuites(),
tlsServerParameters.getCipherSuitesFilter(),
context.getServerSocketFactory().getDefaultCipherSuites(),
supported,
LOG);
scf.setIncludeCipherSuites(included);
return context;
}

String proto = tlsServerParameters.getSecureSocketProtocol() == null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.apache.cxf.transport.http_jetty.BCJsseServerALPNProcessor
Original file line number Diff line number Diff line change
Expand Up @@ -433,10 +433,14 @@ protected void setupConnection(Message message, Address address, HTTPClientPolic
if (clientParameters.getSecureSocketProtocol() != null) {
String protocol = clientParameters.getSecureSocketProtocol();
SSLParameters params = new SSLParameters(cipherSuites, new String[] {protocol});
org.apache.cxf.transport.https.SSLUtils.applyNamedGroups(
params, clientParameters.getNamedGroups());
cb.sslParameters(params);
} else {
final SSLParameters params = new SSLParameters(cipherSuites,
TLSClientParameters.getPreferredClientProtocols());
org.apache.cxf.transport.https.SSLUtils.applyNamedGroups(
params, clientParameters.getNamedGroups());
cb.sslParameters(params);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.apache.cxf.transport.https;

import java.lang.reflect.Method;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
Expand Down Expand Up @@ -531,4 +532,43 @@ public javax.security.cert.X509Certificate[] getPeerCertificateChain() throws SS
}
};

/**
* Applies the given named groups to {@code SSLParameters} via reflection.
* {@code SSLParameters.setNamedGroups()} requires JDK 20+; this method
* silently skips the call on older JDKs.
*/
public static void applyNamedGroups(SSLParameters params, List<String> namedGroups) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if we should consider multi-release jar and have clean JDK-20+ implementation?

if (namedGroups == null || namedGroups.isEmpty()) {
return;
}
try {
Method m = SSLParameters.class.getMethod("setNamedGroups", String[].class);
m.invoke(params, (Object) namedGroups.toArray(new String[0]));
} catch (NoSuchMethodException e) {
LOG.fine("SSLParameters.setNamedGroups() not available on this JDK; namedGroups ignored");
} catch (ReflectiveOperationException e) {
LOG.warning("Failed to apply TLS namedGroups: " + e.getMessage());
}
}

// Reflection-based BC JSSE path: avoids a compile-time dependency on bctls-jdk18on.
public static void applyNamedGroupsBC(SSLEngine sslEngine, List<String> namedGroups) {
if (namedGroups == null || namedGroups.isEmpty()) {
return;
}
try {
Class<?> bcEngineClass = Class.forName("org.bouncycastle.jsse.BCSSLEngine");
if (!bcEngineClass.isInstance(sslEngine)) {
return;
}
Class<?> bcParamsClass = Class.forName("org.bouncycastle.jsse.BCSSLParameters");
Object bcParams = bcEngineClass.getMethod("getParameters").invoke(sslEngine);
bcParamsClass.getMethod("setNamedGroups", String[].class)
.invoke(bcParams, (Object) namedGroups.toArray(new String[0]));
bcEngineClass.getMethod("setParameters", bcParamsClass).invoke(sslEngine, bcParams);
} catch (ReflectiveOperationException e) {
LOG.fine("Could not apply named groups via BC JSSE engine: " + e.getMessage());
}
}

}
29 changes: 29 additions & 0 deletions systests/transports/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,23 @@
</systemPropertyVariables>
</configuration>
</execution>
<execution>
<!-- Runs PQCTLSTest with jdk.tls.namedGroups including X25519MLKEM768,
overriding the parent pom's BouncyCastle workaround that restricts
named groups to classical-only. Requires JDK 27+ (JEP 527). -->
<id>pqc-tls-test</id>
<goals>
<goal>test</goal>
</goals>
<configuration>
<includes>
<include>**/pqc/PQCTLSTest.java</include>
</includes>
<systemPropertyVariables>
<jdk.tls.namedGroups>X25519MLKEM768,x25519,secp256r1,secp384r1,secp521r1</jdk.tls.namedGroups>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
Expand Down Expand Up @@ -245,6 +262,18 @@
<scope>test</scope>
<classifier>keys</classifier>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>${cxf.bcprov.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bctls-jdk18on</artifactId>
<version>${cxf.bcprov.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
Expand Down
Loading
Loading