This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch 4.1.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/4.1.x-fixes by this push:
new ed8952fc6dc Wire through the TLS server parameter for Netty (#3420)
ed8952fc6dc is described below
commit ed8952fc6dca079a8b9a155fd042080c74e018f3
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Mon Aug 31 16:06:16 2026 +0100
Wire through the TLS server parameter for Netty (#3420)
(cherry picked from commit 4c765ba558c8d21300639ceca48efe76927770ac)
---
.../server/NettyHttpServletPipelineFactory.java | 10 ++
.../NettyHttpServletPipelineFactoryTest.java | 114 +++++++++++++++++++++
.../org/apache/cxf/transport/https/SSLUtils.java | 65 +++++++++++-
.../apache/cxf/transport/https/SSLUtilsTest.java | 58 +++++++++++
4 files changed, 246 insertions(+), 1 deletion(-)
diff --git
a/rt/transports/http-netty/netty-server/src/main/java/org/apache/cxf/transport/http/netty/server/NettyHttpServletPipelineFactory.java
b/rt/transports/http-netty/netty-server/src/main/java/org/apache/cxf/transport/http/netty/server/NettyHttpServletPipelineFactory.java
index 8b7f84d5c65..4134c3f44f7 100644
---
a/rt/transports/http-netty/netty-server/src/main/java/org/apache/cxf/transport/http/netty/server/NettyHttpServletPipelineFactory.java
+++
b/rt/transports/http-netty/netty-server/src/main/java/org/apache/cxf/transport/http/netty/server/NettyHttpServletPipelineFactory.java
@@ -233,6 +233,7 @@ public class NettyHttpServletPipelineFactory extends
ChannelInitializer<Channel>
private SslContext configureServerHttp2SSLOnDemand() throws Exception {
if (tlsServerParameters != null) {
final SSLContextInitParameters initParams =
SSLUtils.getSSLContextInitParameters(tlsServerParameters);
+ final SSLContext sslContext =
SSLUtils.getSSLContext(tlsServerParameters);
// Use only JDK provider for now, leaving OpenSsl as an option
final SslProvider provider = SslProvider.JDK;
@@ -264,6 +265,15 @@ public class NettyHttpServletPipelineFactory extends
ChannelInitializer<Channel>
ApplicationProtocolNames.HTTP_2,
ApplicationProtocolNames.HTTP_1_1
));
+
+ if (!tlsServerParameters.getIncludeProtocols().isEmpty()
+ || !tlsServerParameters.getExcludeProtocols().isEmpty()) {
+ builder.protocols(SSLUtils.getProtocolsToInclude(
+ tlsServerParameters.getIncludeProtocols(),
+ tlsServerParameters.getExcludeProtocols(),
+ sslContext.getDefaultSSLParameters().getProtocols(),
+ sslContext.getSupportedSSLParameters().getProtocols()));
+ }
final TrustManager[] trustManagers = initParams.getTrustManagers();
if (trustManagers != null && trustManagers.length > 0) {
diff --git
a/rt/transports/http-netty/netty-server/src/test/java/org/apache/cxf/transport/http/netty/server/NettyHttpServletPipelineFactoryTest.java
b/rt/transports/http-netty/netty-server/src/test/java/org/apache/cxf/transport/http/netty/server/NettyHttpServletPipelineFactoryTest.java
new file mode 100644
index 00000000000..47bac891f07
--- /dev/null
+++
b/rt/transports/http-netty/netty-server/src/test/java/org/apache/cxf/transport/http/netty/server/NettyHttpServletPipelineFactoryTest.java
@@ -0,0 +1,114 @@
+/**
+ * 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.netty.server;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.KeyStore;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+
+import org.apache.cxf.configuration.jsse.TLSServerParameters;
+
+import io.netty.buffer.UnpooledByteBufAllocator;
+import io.netty.handler.ssl.SslContext;
+import io.netty.util.concurrent.ImmediateEventExecutor;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class NettyHttpServletPipelineFactoryTest {
+ @Test
+ public void testHttp2UsesIncludedProtocols() throws Exception {
+ TLSServerParameters parameters = serverParameters();
+ parameters.setIncludeProtocols(Collections.singletonList("TLSv1.2"));
+
+ SslContext context = createHttp2Context(parameters);
+
+ assertArrayEquals(new String[] {"TLSv1.2"}, context.newEngine(
+ UnpooledByteBufAllocator.DEFAULT).getEnabledProtocols());
+ }
+
+ @Test
+ public void testHttp2UsesExcludedProtocols() throws Exception {
+ TLSServerParameters parameters = serverParameters();
+ parameters.setExcludeProtocols(Collections.singletonList("TLSv1.2"));
+
+ SslContext context = createHttp2Context(parameters);
+
+ assertFalse(Arrays.asList(context.newEngine(
+
UnpooledByteBufAllocator.DEFAULT).getEnabledProtocols()).contains("TLSv1.2"));
+ }
+
+ @Test
+ public void testHttp2PreservesUnconstrainedDefaults() throws Exception {
+ TLSServerParameters parameters = serverParameters();
+
+ SslContext context = createHttp2Context(parameters);
+
+
assertArrayEquals(SSLContext.getDefault().getDefaultSSLParameters().getProtocols(),
+
context.newEngine(UnpooledByteBufAllocator.DEFAULT).getEnabledProtocols());
+ }
+
+ @Test
+ public void testHttp2RejectsUnsatisfiableProtocols() throws Exception {
+ TLSServerParameters parameters = serverParameters();
+
parameters.setIncludeProtocols(Collections.singletonList("NoSuchProtocol"));
+
+ try {
+ createHttp2Context(parameters);
+ fail("Expected protocol configuration to be rejected");
+ } catch (InvocationTargetException ex) {
+ assertTrue(ex.getCause() instanceof
java.security.GeneralSecurityException);
+ }
+ }
+
+ private static TLSServerParameters serverParameters() throws Exception {
+ TLSServerParameters parameters = new TLSServerParameters();
+ KeyStore keyStore = KeyStore.getInstance("JKS");
+
keyStore.load(NettyHttpServletPipelineFactoryTest.class.getResourceAsStream("/keys/clientstore.jks"),
+ "cspass".toCharArray());
+ KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
+ KeyManagerFactory.getDefaultAlgorithm());
+ keyManagerFactory.init(keyStore, "cspass".toCharArray());
+ KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
+ parameters.setKeyManagers(keyManagers);
+ return parameters;
+ }
+
+ private static SslContext createHttp2Context(TLSServerParameters
parameters) throws Exception {
+ NettyHttpServletPipelineFactory factory = new
NettyHttpServletPipelineFactory(
+ parameters, false, 1024, new HashMap<>(), new
NettyHttpServerEngine(),
+ ImmediateEventExecutor.INSTANCE, true);
+ Method method = NettyHttpServletPipelineFactory.class
+ .getDeclaredMethod("configureServerHttp2SSLOnDemand");
+ method.setAccessible(true);
+ return (SslContext) method.invoke(factory);
+ }
+}
diff --git
a/rt/transports/http/src/main/java/org/apache/cxf/transport/https/SSLUtils.java
b/rt/transports/http/src/main/java/org/apache/cxf/transport/https/SSLUtils.java
index 131dfaa8bac..79b0f932bc9 100644
---
a/rt/transports/http/src/main/java/org/apache/cxf/transport/https/SSLUtils.java
+++
b/rt/transports/http/src/main/java/org/apache/cxf/transport/https/SSLUtils.java
@@ -25,6 +25,7 @@ import java.security.Principal;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;
@@ -67,6 +68,38 @@ public final class SSLUtils {
//Helper class
}
+ public static String[] getProtocolsToInclude(List<String> includeProtocols,
+ List<String> excludeProtocols,
+ String[] defaultProtocols,
+ String[] supportedProtocols)
+ throws GeneralSecurityException {
+ boolean hasInclude = includeProtocols != null &&
!includeProtocols.isEmpty();
+ boolean hasExclude = excludeProtocols != null &&
!excludeProtocols.isEmpty();
+ if (!hasInclude && !hasExclude) {
+ return defaultProtocols;
+ }
+
+ List<String> protocols = new ArrayList<>();
+ if (hasInclude) {
+ for (String supported : supportedProtocols) {
+ if (includeProtocols.contains(supported)) {
+ protocols.add(supported);
+ }
+ }
+ } else {
+ protocols.addAll(Arrays.asList(defaultProtocols));
+ }
+ if (hasExclude) {
+ protocols.removeAll(excludeProtocols);
+ }
+ if (protocols.isEmpty()) {
+ throw new GeneralSecurityException(
+ "No TLS protocol remains enabled after applying the configured"
+ + " includeProtocols/excludeProtocols constraints");
+ }
+ return protocols.toArray(new String[0]);
+ }
+
public static HostnameVerifier getHostnameVerifier(TLSClientParameters
tlsClientParameters) {
HostnameVerifier verifier;
@@ -172,7 +205,37 @@ public final class SSLUtils {
SSLEngine serverEngine = sslContext.createSSLEngine();
serverEngine.setUseClientMode(false);
-
serverEngine.setNeedClientAuth(parameters.getClientAuthentication().isRequired());
+
+ List<String> includeProtocols = parameters.getIncludeProtocols();
+ List<String> excludeProtocols = parameters.getExcludeProtocols();
+ if (!includeProtocols.isEmpty() || !excludeProtocols.isEmpty()) {
+ serverEngine.setEnabledProtocols(
+ getProtocolsToInclude(
+ includeProtocols,
+ excludeProtocols,
+ serverEngine.getEnabledProtocols(),
+ serverEngine.getSupportedProtocols()));
+ }
+
+ String[] cipherSuites =
+
org.apache.cxf.configuration.jsse.SSLUtils.getCiphersuitesToInclude(
+ parameters.getCipherSuites(),
+ parameters.getCipherSuitesFilter(),
+ serverEngine.getEnabledCipherSuites(),
+ serverEngine.getSupportedCipherSuites(),
+ LOG);
+ serverEngine.setEnabledCipherSuites(cipherSuites);
+
+ org.apache.cxf.configuration.security.ClientAuthentication clientAuth =
+ parameters.getClientAuthentication();
+ if (clientAuth != null) {
+ if (clientAuth.isSetWant()) {
+ serverEngine.setWantClientAuth(clientAuth.isWant());
+ }
+ if (clientAuth.isSetRequired()) {
+ serverEngine.setNeedClientAuth(clientAuth.isRequired());
+ }
+ }
return serverEngine;
}
diff --git
a/rt/transports/http/src/test/java/org/apache/cxf/transport/https/SSLUtilsTest.java
b/rt/transports/http/src/test/java/org/apache/cxf/transport/https/SSLUtilsTest.java
index 4c56afd0f50..b9bff35aec1 100644
---
a/rt/transports/http/src/test/java/org/apache/cxf/transport/https/SSLUtilsTest.java
+++
b/rt/transports/http/src/test/java/org/apache/cxf/transport/https/SSLUtilsTest.java
@@ -19,10 +19,13 @@
package org.apache.cxf.transport.https;
+import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
import javax.net.ssl.ExtendedSSLSession;
import javax.net.ssl.HostnameVerifier;
@@ -30,6 +33,8 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.X509TrustManager;
+import org.apache.cxf.configuration.jsse.TLSServerParameters;
+import org.apache.cxf.configuration.security.ClientAuthentication;
import org.apache.cxf.transport.https.SSLUtils.SSLEngineWrapper;
import org.apache.cxf.transport.https.SSLUtils.X509TrustManagerWrapper;
@@ -51,7 +56,42 @@ import static org.mockito.Mockito.when;
public class SSLUtilsTest {
+
private SSLEngine engine;
+
+ @Test
+ public void testProtocolConstraints() throws Exception {
+ String[] protocols = SSLUtils.getProtocolsToInclude(
+ Arrays.asList("TLSv1.3", "TLSv1"),
+ Collections.singletonList("TLSv1.3"),
+ new String[] {"TLSv1.3", "TLSv1.2"},
+ new String[] {"TLSv1", "TLSv1.2", "TLSv1.3"});
+
+ assertThat(protocols, is(new String[] {"TLSv1"}));
+ }
+
+ @Test(expected = GeneralSecurityException.class)
+ public void testProtocolConstraintsRejectEmptyResult() throws Exception {
+ SSLUtils.getProtocolsToInclude(
+ Collections.singletonList("TLSv1.3"),
+ Collections.emptyList(),
+ new String[] {"TLSv1.2"},
+ new String[] {"TLSv1.2"});
+ }
+
+ @Test
+ public void testProtocolConstraintsUseConfiguredContext() throws Exception
{
+ SSLContext context = SSLContext.getInstance("TLSv1.2");
+ context.init(null, null, null);
+
+ String[] protocols = SSLUtils.getProtocolsToInclude(
+ Collections.emptyList(),
+ Collections.emptyList(),
+ context.getDefaultSSLParameters().getProtocols(),
+ context.getSupportedSSLParameters().getProtocols());
+
+ assertThat(protocols, is(new String[] {"TLSv1.2"}));
+ }
@Before
public void setUp() throws NoSuchAlgorithmException {
@@ -125,4 +165,22 @@ public class SSLUtilsTest {
wrapper.checkServerTrusted(new X509Certificate[0], "RSA", mockEngine);
verify(passingVerifier).verify(eq("service.example.com"), any());
}
+
+ @Test
+ public void testServerEngineHonorsConfiguredConstraints() throws Exception
{
+ TLSServerParameters parameters = new TLSServerParameters();
+ parameters.setExcludeProtocols(Arrays.asList("SSLv3", "TLSv1",
"TLSv1.1"));
+ ClientAuthentication clientAuth = new ClientAuthentication();
+ clientAuth.setWant(Boolean.TRUE);
+ parameters.setClientAuthentication(clientAuth);
+
+ SSLEngine serverEngine = SSLUtils.createServerSSLEngine(parameters);
+
+ assertThat(serverEngine.getUseClientMode(), is(false));
+ assertThat(serverEngine.getWantClientAuth(), is(true));
+ List<String> enabled =
Arrays.asList(serverEngine.getEnabledProtocols());
+ assertThat(enabled.contains("SSLv3"), is(false));
+ assertThat(enabled.contains("TLSv1"), is(false));
+ assertThat(enabled.contains("TLSv1.1"), is(false));
+ }
}