This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cxf.git


The following commit(s) were added to refs/heads/main by this push:
     new 4c765ba558c Wire through the TLS server parameter for Netty (#3420)
4c765ba558c is described below

commit 4c765ba558c8d21300639ceca48efe76927770ac
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Mon Aug 31 16:06:16 2026 +0100

    Wire through the TLS server parameter for Netty (#3420)
---
 .../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 f1085557b8a..cee024b09a6 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
@@ -26,6 +26,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;
@@ -68,6 +69,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;
 
@@ -173,7 +206,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));
+    }
 }

Reply via email to