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 f7c9ebd19d5 Make Netty client TLS code use hostname verification 
(#3429)
f7c9ebd19d5 is described below

commit f7c9ebd19d5ecae17493288e15e2e886b7b1ab36
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Wed Sep 2 09:49:12 2026 +0100

    Make Netty client TLS code use hostname verification (#3429)
    
    (cherry picked from commit 6ccfc087057f98c74c43f4cd550c3caf542c1f48)
---
 .../client/NettyHttpClientPipelineFactory.java     | 38 +++++++++++++--
 .../http/netty/client/NettyHttpConduit.java        |  9 ++--
 .../client/NettyHttpClientPipelineFactoryTest.java | 57 ++++++++++++++++++++++
 .../client/integration/SSLNettyClientTest.java     | 21 ++++++--
 .../org/apache/cxf/transport/https/SSLUtils.java   | 40 ++++++++++-----
 .../apache/cxf/transport/https/SSLUtilsTest.java   | 25 ++++++++++
 6 files changed, 166 insertions(+), 24 deletions(-)

diff --git 
a/rt/transports/http-netty/netty-client/src/main/java/org/apache/cxf/transport/http/netty/client/NettyHttpClientPipelineFactory.java
 
b/rt/transports/http-netty/netty-client/src/main/java/org/apache/cxf/transport/http/netty/client/NettyHttpClientPipelineFactory.java
index a39ff9ad518..c3b53152b8b 100644
--- 
a/rt/transports/http-netty/netty-client/src/main/java/org/apache/cxf/transport/http/netty/client/NettyHttpClientPipelineFactory.java
+++ 
b/rt/transports/http-netty/netty-client/src/main/java/org/apache/cxf/transport/http/netty/client/NettyHttpClientPipelineFactory.java
@@ -82,26 +82,39 @@ public class NettyHttpClientPipelineFactory extends 
ChannelInitializer<Channel>
     private final int readTimeout;
     private final int maxContentLength;
     private final boolean enableHttp2;
+    private final String peerHost;
+    private final int peerPort;
 
+    @Deprecated
     public NettyHttpClientPipelineFactory(TLSClientParameters 
clientParameters) {
         this(clientParameters, 0);
     }
 
+    @Deprecated
     public NettyHttpClientPipelineFactory(TLSClientParameters 
clientParameters, int readTimeout) {
         this(clientParameters, readTimeout, 
NettyHttpConduit.DEFAULT_MAX_RESPONSE_CONTENT_LENGTH);
     }
 
+    @Deprecated
     public NettyHttpClientPipelineFactory(TLSClientParameters 
clientParameters, int readTimeout,
                                           int maxResponseContentLength) {
         this(clientParameters, readTimeout, maxResponseContentLength, false);
     }
     
+    @Deprecated
     public NettyHttpClientPipelineFactory(TLSClientParameters 
clientParameters, int readTimeout,
             int maxResponseContentLength, boolean enableHttp2) {
+        this(clientParameters, readTimeout, maxResponseContentLength, 
enableHttp2, null, -1);
+    }
+
+    public NettyHttpClientPipelineFactory(TLSClientParameters 
clientParameters, int readTimeout,
+            int maxResponseContentLength, boolean enableHttp2, String 
peerHost, int peerPort) {
         this.tlsClientParameters = clientParameters;
         this.readTimeout = readTimeout;
         this.maxContentLength = maxResponseContentLength;
         this.enableHttp2 = enableHttp2;
+        this.peerHost = peerHost;
+        this.peerPort = peerPort;
     }
 
     @Override
@@ -200,9 +213,10 @@ public class NettyHttpClientPipelineFactory extends 
ChannelInitializer<Channel>
             final SSLEngine sslEngine;
 
             if (enableHttp2) {
-                final SSLContextInitParameters initParams = 
SSLUtils.getSSLContextInitParameters(tlsClientParameters);
+                final SSLContextInitParameters initParams =
+                    SSLUtils.getSSLContextInitParameters(tlsClientParameters, 
peerHost != null);
 
-                sslEngine = SslContextBuilder
+                final SslContext sslContext = SslContextBuilder
                     .forClient()
                     .sslProvider(SslContext.defaultClientProvider())
                     .keyManager(new SimpleKeyManagerFactory() {
@@ -255,10 +269,24 @@ public class NettyHttpClientPipelineFactory extends 
ChannelInitializer<Channel>
                             ApplicationProtocolNames.HTTP_1_1
                         )
                     )
-                    .build()
-                    .newEngine(channel.alloc());
+                    .build();
+
+                sslEngine = peerHost != null
+                    ? sslContext.newEngine(channel.alloc(), peerHost, peerPort)
+                    : sslContext.newEngine(channel.alloc());
             } else {
-                sslEngine = 
SSLUtils.createClientSSLEngine(tlsClientParameters);
+                if (peerHost != null) {
+                    sslEngine = 
SSLUtils.createClientSSLEngine(tlsClientParameters, peerHost, peerPort);
+                } else {
+                    sslEngine = 
SSLUtils.getSSLContext(tlsClientParameters).createSSLEngine();
+                    sslEngine.setUseClientMode(true);
+                }
+            }
+
+            if (peerHost == null && !tlsClientParameters.isDisableCNCheck()) {
+                LOG.warning("No peer host information was supplied to the 
pipeline factory: TLS "
+                    + "hostname verification cannot be performed. Use the 
constructor taking a "
+                    + "peer host and port, or set disableCNCheck to accept the 
legacy behavior.");
             }
 
             return new SslHandler(sslEngine);
diff --git 
a/rt/transports/http-netty/netty-client/src/main/java/org/apache/cxf/transport/http/netty/client/NettyHttpConduit.java
 
b/rt/transports/http-netty/netty-client/src/main/java/org/apache/cxf/transport/http/netty/client/NettyHttpConduit.java
index 9cbfc95d731..946324e8945 100644
--- 
a/rt/transports/http-netty/netty-client/src/main/java/org/apache/cxf/transport/http/netty/client/NettyHttpConduit.java
+++ 
b/rt/transports/http-netty/netty-client/src/main/java/org/apache/cxf/transport/http/netty/client/NettyHttpConduit.java
@@ -388,21 +388,22 @@ public class NettyHttpConduit extends 
HttpClientHTTPConduit implements BusLifeCy
 
         protected void connect(boolean output) {
             final NettyHttpClientPipelineFactory handler; 
+            final int port = url.getPort() != -1 ? url.getPort()
+                : "http".equals(url.getScheme()) ? 80 : 443;
             if ("https".equals(url.getScheme())) {
                 TLSClientParameters clientParameters = 
findTLSClientParameters();
                 handler = new NettyHttpClientPipelineFactory(clientParameters, 
entity.getReceiveTimeout(),
-                    entity.getMaxResponseContentLength(), enableHttp2);
+                    entity.getMaxResponseContentLength(), enableHttp2, 
url.getHost(), port);
             } else {
                 handler = new NettyHttpClientPipelineFactory(null, 
entity.getReceiveTimeout(),
-                    entity.getMaxResponseContentLength(), enableHttp2);
+                    entity.getMaxResponseContentLength(), enableHttp2, null, 
-1);
             }
 
             // Set handler
             bootstrap.handler(handler);
 
             ChannelFuture connFuture =
-                bootstrap.connect(new InetSocketAddress(url.getHost(), 
url.getPort() != -1 ? url.getPort()
-                                                            : 
"http".equals(url.getScheme()) ? 80 : 443));
+                bootstrap.connect(new InetSocketAddress(url.getHost(), port));
 
             // Setup the call back on the NettyHttpClientRequest
             ChannelFutureListener listener = new ChannelFutureListener() {
diff --git 
a/rt/transports/http-netty/netty-client/src/test/java/org/apache/cxf/transport/http/netty/client/NettyHttpClientPipelineFactoryTest.java
 
b/rt/transports/http-netty/netty-client/src/test/java/org/apache/cxf/transport/http/netty/client/NettyHttpClientPipelineFactoryTest.java
new file mode 100644
index 00000000000..2ef8e1257fe
--- /dev/null
+++ 
b/rt/transports/http-netty/netty-client/src/test/java/org/apache/cxf/transport/http/netty/client/NettyHttpClientPipelineFactoryTest.java
@@ -0,0 +1,57 @@
+/**
+ * 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.client;
+
+import org.apache.cxf.configuration.jsse.TLSClientParameters;
+
+import io.netty.channel.embedded.EmbeddedChannel;
+import io.netty.handler.ssl.SslHandler;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class NettyHttpClientPipelineFactoryTest {
+
+    @Test
+    public void testHttp11SslEngineBoundToPeer() {
+        assertSslEngineBoundToPeer(false);
+    }
+
+    @Test
+    public void testHttp2SslEngineBoundToPeer() {
+        assertSslEngineBoundToPeer(true);
+    }
+
+    private void assertSslEngineBoundToPeer(boolean enableHttp2) {
+        NettyHttpClientPipelineFactory factory = new 
NettyHttpClientPipelineFactory(
+            new TLSClientParameters(), 0, 
NettyHttpConduit.DEFAULT_MAX_RESPONSE_CONTENT_LENGTH,
+            enableHttp2, "api.example.com", 8443);
+        EmbeddedChannel channel = new EmbeddedChannel(factory);
+        try {
+            SslHandler sslHandler = channel.pipeline().get(SslHandler.class);
+            assertNotNull(sslHandler);
+            assertEquals("api.example.com", sslHandler.engine().getPeerHost());
+            assertEquals(8443, sslHandler.engine().getPeerPort());
+        } finally {
+            channel.finishAndReleaseAll();
+        }
+    }
+}
\ No newline at end of file
diff --git 
a/rt/transports/http-netty/netty-client/src/test/java/org/apache/cxf/transport/http/netty/client/integration/SSLNettyClientTest.java
 
b/rt/transports/http-netty/netty-client/src/test/java/org/apache/cxf/transport/http/netty/client/integration/SSLNettyClientTest.java
index a914779659c..30277653022 100644
--- 
a/rt/transports/http-netty/netty-client/src/test/java/org/apache/cxf/transport/http/netty/client/integration/SSLNettyClientTest.java
+++ 
b/rt/transports/http-netty/netty-client/src/test/java/org/apache/cxf/transport/http/netty/client/integration/SSLNettyClientTest.java
@@ -52,6 +52,7 @@ import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
 
 public class SSLNettyClientTest extends AbstractBusClientServerTestBase {
 
@@ -98,7 +99,7 @@ public class SSLNettyClientTest extends 
AbstractBusClientServerTestBase {
 
     @Test
     public void testInvocation() throws Exception {
-        setupTLS(g);
+        setupTLS(g, true);
         setAddress(g, address);
         String response = g.greetMe("test");
         assertEquals("Get a wrong response", "Hello test", response);
@@ -121,7 +122,20 @@ public class SSLNettyClientTest extends 
AbstractBusClientServerTestBase {
 
     }
 
-    private static void setupTLS(Greeter port)
+    @Test
+    public void testRejectsCertificateForWrongHostByDefault() throws Exception 
{
+        setupTLS(g, false);
+        setAddress(g, address);
+
+        try {
+            g.greetMe("test");
+            fail("The certificate for www.service.com must not be accepted for 
localhost");
+        } catch (Exception expected) {
+            // expected
+        }
+    }
+
+    private static void setupTLS(Greeter port, boolean disableCNCheck)
         throws FileNotFoundException, IOException, GeneralSecurityException {
         String keyStoreLoc =
             "/keys/clientstore.jks";
@@ -140,8 +154,7 @@ public class SSLNettyClientTest extends 
AbstractBusClientServerTestBase {
         TrustManager[] myTrustStoreKeyManagers = getTrustManagers(trustStore);
         tlsCP.setTrustManagers(myTrustStoreKeyManagers);
 
-
-        tlsCP.setDisableCNCheck(true);
+        tlsCP.setDisableCNCheck(disableCNCheck);
         httpConduit.setTlsClientParameters(tlsCP);
     }
 
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 79b0f932bc9..98de834f7d0 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
@@ -117,6 +117,12 @@ public final class SSLUtils {
     
     public static SSLContextInitParameters 
getSSLContextInitParameters(TLSParameterBase parameters) 
             throws GeneralSecurityException {
+        return getSSLContextInitParameters(parameters, false);
+    }
+
+    public static SSLContextInitParameters 
getSSLContextInitParameters(TLSParameterBase parameters,
+                                                                       boolean 
addHostnameVerifier)
+            throws GeneralSecurityException {
         
         final SSLContextInitParameters contextParameters = new 
SSLContextInitParameters();
 
@@ -130,6 +136,15 @@ public final class SSLUtils {
         if (trustManagers == null && parameters instanceof 
TLSClientParameters) {
             trustManagers = 
org.apache.cxf.configuration.jsse.SSLUtils.getDefaultTrustStoreManagers(LOG);
         }
+        if (trustManagers != null && addHostnameVerifier && parameters 
instanceof TLSClientParameters) {
+            trustManagers = Arrays.copyOf(trustManagers, trustManagers.length);
+            HostnameVerifier verifier = 
getHostnameVerifier((TLSClientParameters)parameters);
+            for (int i = 0; i < trustManagers.length; i++) {
+                if (trustManagers[i] instanceof X509TrustManager) {
+                    trustManagers[i] = new 
X509TrustManagerWrapper((X509TrustManager)trustManagers[i], verifier);
+                }
+            }
+        }
         
         contextParameters.setKeyManagers(configuredKeyManagers);
         contextParameters.setTrustManagers(trustManagers);
@@ -152,17 +167,8 @@ public final class SSLUtils {
         SSLContext ctx = provider == null ? SSLContext.getInstance(protocol) : 
SSLContext
             .getInstance(protocol, provider);
 
-        final SSLContextInitParameters initParams = 
getSSLContextInitParameters(parameters);
-        TrustManager[] tms = initParams.getTrustManagers();
-        if (tms != null && addHNV && parameters instanceof 
TLSClientParameters) {
-            HostnameVerifier hnv = 
getHostnameVerifier((TLSClientParameters)parameters);
-            for (int i = 0; i < tms.length; i++) {
-                if (tms[i] instanceof  X509TrustManager) {
-                    tms[i] = new 
X509TrustManagerWrapper((X509TrustManager)tms[i], hnv);
-                }
-            }
-        }
-        ctx.init(initParams.getKeyManagers(), tms, 
parameters.getSecureRandom());
+        final SSLContextInitParameters initParams = 
getSSLContextInitParameters(parameters, addHNV);
+        ctx.init(initParams.getKeyManagers(), initParams.getTrustManagers(), 
parameters.getSecureRandom());
 
         if (parameters instanceof TLSClientParameters && 
ctx.getClientSessionContext() != null) {
             
ctx.getClientSessionContext().setSessionTimeout(((TLSClientParameters)parameters).getSslCacheTimeout());
@@ -239,6 +245,10 @@ public final class SSLUtils {
         return serverEngine;
     }
 
+    /**
+     * @deprecated use {@link #createClientSSLEngine(TLSClientParameters, 
String, int)}
+     */
+    @Deprecated
     public static SSLEngine createClientSSLEngine(TLSClientParameters 
parameters) throws Exception {
         SSLContext sslContext = getSSLContext(parameters);
         SSLEngine clientEngine = sslContext.createSSLEngine();
@@ -246,6 +256,14 @@ public final class SSLUtils {
         return clientEngine;
     }
 
+    public static SSLEngine createClientSSLEngine(TLSClientParameters 
parameters,
+                                                   String peerHost, int 
peerPort) throws Exception {
+        SSLContext sslContext = getSSLContext(parameters, true);
+        SSLEngine clientEngine = sslContext.createSSLEngine(peerHost, 
peerPort);
+        clientEngine.setUseClientMode(true);
+        return clientEngine;
+    }
+
     /*
      * The classes below are used by the HttpClient implementation to allow 
use of the 
      * HostNameVerifier that is configured.  HttpClient does not provide a 
hook or
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 b9bff35aec1..79a1f223aaa 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
@@ -33,6 +33,7 @@ import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLEngine;
 import javax.net.ssl.X509TrustManager;
 
+import org.apache.cxf.configuration.jsse.TLSClientParameters;
 import org.apache.cxf.configuration.jsse.TLSServerParameters;
 import org.apache.cxf.configuration.security.ClientAuthentication;
 import org.apache.cxf.transport.https.SSLUtils.SSLEngineWrapper;
@@ -116,6 +117,30 @@ public class SSLUtilsTest {
         assertThat(wrapper.getSSLParameters(), is(not(nullValue())));
     }
 
+    @Test
+    public void testCreateClientSSLEngineBoundToPeer() throws Exception {
+        TLSClientParameters parameters = new TLSClientParameters();
+        SSLEngine clientEngine = SSLUtils.createClientSSLEngine(parameters, 
"api.example.com", 443);
+
+        assertThat(clientEngine.getPeerHost(), is("api.example.com"));
+        assertThat(clientEngine.getPeerPort(), is(443));
+        assertThat(clientEngine.getUseClientMode(), is(true));
+    }
+
+    @Test
+    public void testHostnameVerifierWrapsConfiguredTrustManager() throws 
Exception {
+        X509TrustManager trustManager = mock(X509TrustManager.class);
+        HostnameVerifier verifier = mock(HostnameVerifier.class);
+        TLSClientParameters parameters = new TLSClientParameters();
+        parameters.setTrustManagers(new X509TrustManager[] {trustManager});
+        parameters.setHostnameVerifier(verifier);
+
+        var initParameters = SSLUtils.getSSLContextInitParameters(parameters, 
true);
+
+        assertThat(initParameters.getTrustManagers()[0] instanceof 
X509TrustManagerWrapper, is(true));
+        assertThat(parameters.getTrustManagers()[0], is(trustManager));
+    }
+
     /**
      * Regression test: a plain (non-extended) X509TrustManager delegate must 
still get
      * hostname verification. JSSE endpoint identification is suppressed by

Reply via email to