This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new e4dc3321370c CAMEL-25674: camel-netty-http - hostnameVerification no
longer breaks client certificates on a consumer (#28871)
e4dc3321370c is described below
commit e4dc3321370c649ab36cfcd4af907dca0a5af5a7
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 2 23:00:06 2026 +0100
CAMEL-25674: camel-netty-http - hostnameVerification no longer breaks
client certificates on a consumer (#28871)
* CAMEL-25674: camel-netty-http - hostnameVerification no longer breaks
client certificates on a consumer
HttpServerInitializerFactory set endpointIdentificationAlgorithm=HTTPS on
the
server engine when hostnameVerification=true. The JDK does not support host
name verification on the server side, so every handshake in which the client
presented a certificate failed with "Endpoint Identification Algorithm HTTPS
is not supported on the server side", and nothing was checked otherwise.
The consumer now ignores the option, as camel-netty does since CAMEL-25649
and
as HttpServerSharedInitializerFactory always did. The producer still
verifies
the host.
* CAMEL-25674: camel-netty-http - check that the rejected handshake fails
with an SSLException
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../camel/catalog/docs/netty-http-component.adoc | 11 ++
.../src/main/docs/netty-http-component.adoc | 11 ++
.../netty/http/HttpServerInitializerFactory.java | 9 +-
...NettyHttpSSLServerHostnameVerificationTest.java | 132 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 10 ++
5 files changed, 167 insertions(+), 6 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/netty-http-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/netty-http-component.adoc
index 2373c69dce3a..5b098510360b 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/netty-http-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/netty-http-component.adoc
@@ -175,6 +175,17 @@ invoking the route, unless the endpoint is restricted to
the `OPTIONS` method wi
`httpMethodRestrict`. All other HTTP methods are authenticated when
`oauthProfile` is set.
====
+=== Verifying the host name of the server
+
+Set `hostnameVerification=true` on a producer that calls an `https` URL to
check that the certificate of the server
+matches the host it connects to, with the same rules as an HTTPS client: a
connection to an IP address needs a
+certificate that lists that address as a subject alternative name of its own.
A certificate that does not match makes
+the call fail with an `SSLHandshakeException`.
+
+The option applies to the producer only. On a consumer it is ignored, because
the JDK does not support host name
+verification on the server side of a handshake. To control which clients may
connect to a consumer, use client
+authentication (`needClientAuth=true`) with a trust store that holds only the
certificates you accept.
+
=== Access to Netty types
This component uses the
diff --git
a/components/camel-netty-http/src/main/docs/netty-http-component.adoc
b/components/camel-netty-http/src/main/docs/netty-http-component.adoc
index 2373c69dce3a..5b098510360b 100644
--- a/components/camel-netty-http/src/main/docs/netty-http-component.adoc
+++ b/components/camel-netty-http/src/main/docs/netty-http-component.adoc
@@ -175,6 +175,17 @@ invoking the route, unless the endpoint is restricted to
the `OPTIONS` method wi
`httpMethodRestrict`. All other HTTP methods are authenticated when
`oauthProfile` is set.
====
+=== Verifying the host name of the server
+
+Set `hostnameVerification=true` on a producer that calls an `https` URL to
check that the certificate of the server
+matches the host it connects to, with the same rules as an HTTPS client: a
connection to an IP address needs a
+certificate that lists that address as a subject alternative name of its own.
A certificate that does not match makes
+the call fail with an `SSLHandshakeException`.
+
+The option applies to the producer only. On a consumer it is ignored, because
the JDK does not support host name
+verification on the server side of a handshake. To control which clients may
connect to a consumer, use client
+authentication (`needClientAuth=true`) with a trust store that holds only the
certificates you accept.
+
=== Access to Netty types
This component uses the
diff --git
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpServerInitializerFactory.java
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpServerInitializerFactory.java
index f303bde010ac..e42b50a5cf44 100644
---
a/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpServerInitializerFactory.java
+++
b/components/camel-netty-http/src/main/java/org/apache/camel/component/netty/http/HttpServerInitializerFactory.java
@@ -20,7 +20,6 @@ import java.util.List;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLParameters;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
@@ -178,11 +177,9 @@ public class HttpServerInitializerFactory extends
ServerInitializerFactory {
// REQUIRE or WANT) when needClientAuth is not enabled
engine.setNeedClientAuth(true);
}
- if (consumer.getConfiguration().isHostnameVerification()) {
- SSLParameters sslParams = engine.getSSLParameters();
- sslParams.setEndpointIdentificationAlgorithm("HTTPS");
- engine.setSSLParameters(sslParams);
- }
+ // hostnameVerification is not applied: the JDK does not support
host name verification on the server side
+ // of a handshake, and rejects every client certificate with
"Endpoint Identification Algorithm HTTPS is not
+ // supported on the server side" when asked to
(HttpServerSharedInitializerFactory does not apply it either)
if (consumer.getConfiguration().getSslContextParameters() == null)
{
// just set the enabledProtocols if the SslContextParameter
doesn't set
engine.setEnabledProtocols(consumer.getConfiguration().getEnabledProtocols().split(","));
diff --git
a/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpSSLServerHostnameVerificationTest.java
b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpSSLServerHostnameVerificationTest.java
new file mode 100644
index 000000000000..df5a234629ed
--- /dev/null
+++
b/components/camel-netty-http/src/test/java/org/apache/camel/component/netty/http/NettyHttpSSLServerHostnameVerificationTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.component.netty.http;
+
+import javax.net.ssl.SSLException;
+
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.support.jsse.KeyManagersParameters;
+import org.apache.camel.support.jsse.KeyStoreParameters;
+import org.apache.camel.support.jsse.SSLContextParameters;
+import org.apache.camel.support.jsse.TrustManagersParameters;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A consumer that asks for client certificates completes the handshake when
{@code hostnameVerification=true}.
+ * <p/>
+ * The JDK does not support host name verification on the server side of a
handshake: when the option was passed to the
+ * server engine, it rejected every client certificate with
+ * {@code Endpoint Identification Algorithm HTTPS is not supported on the
server side}.
+ */
+@DisabledIfSystemProperty(named = "java.vendor", matches = ".*ibm.*")
+public class NettyHttpSSLServerHostnameVerificationTest extends
BaseNettyTestSupport {
+
+ @BindToRegistry("clientCertParameters")
+ public SSLContextParameters loadClientCertParameters() {
+ SSLContextParameters answer = loadTrustOnlyParameters();
+ KeyManagersParameters kmp = new KeyManagersParameters();
+ kmp.setKeyPassword("changeit");
+ kmp.setKeyStore(localhostKeyStore());
+ answer.setKeyManagers(kmp);
+ return answer;
+ }
+
+ @BindToRegistry("trustOnlyParameters")
+ public SSLContextParameters loadTrustOnlyParameters() {
+ TrustManagersParameters tmp = new TrustManagersParameters();
+ tmp.setKeyStore(localhostKeyStore());
+
+ SSLContextParameters answer = new SSLContextParameters();
+ answer.setTrustManagers(tmp);
+ return answer;
+ }
+
+ private static KeyStoreParameters localhostKeyStore() {
+ KeyStoreParameters ksp = new KeyStoreParameters();
+ ksp.setResource("jsse/localhost.p12");
+ ksp.setPassword("changeit");
+ return ksp;
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+
from("netty-http:https://localhost:{{port}}/hello?sslContextParameters=#clientCertParameters"
+ + "&needClientAuth=true&hostnameVerification=true")
+ .transform().constant("Bye World");
+ }
+ };
+ }
+
+ @Test
+ public void testClientCertificateAccepted() {
+ String out = template.requestBody(
+
"netty-http:https://localhost:{{port}}/hello?sslContextParameters=#clientCertParameters",
+ "Hello World", String.class);
+
+ assertEquals("Bye World", out);
+ }
+
+ /**
+ * The consumer still requires a client certificate, so the handshake in
the test above did not pass because the
+ * client authentication was dropped.
+ */
+ @Test
+ public void testNoClientCertificateRejected() {
+ CamelExecutionException e = assertThrows(CamelExecutionException.class,
+ () -> template.requestBody(
+
"netty-http:https://localhost:{{port}}/hello?sslContextParameters=#trustOnlyParameters",
+ "Hello World", String.class));
+
+ // with TLS 1.3 the client may finish its side of the handshake before
the server rejects it, so the failure is
+ // an SSLException somewhere in the chain rather than always an
SSLHandshakeException at the root
+ boolean sslFailure = false;
+ for (Throwable cause = e; cause != null && !sslFailure; cause =
cause.getCause()) {
+ sslFailure = cause instanceof SSLException;
+ }
+ assertTrue(sslFailure, "the server should have rejected the handshake,
but got: " + e);
+ }
+
+ /**
+ * The option still applies to the producer: localhost.p12 lists only
DNS:localhost as subject alternative name, so
+ * it does not match 127.0.0.1.
+ */
+ @Test
+ public void testProducerRejectsCertificateNotMatchingTheHost() {
+ CamelExecutionException e = assertThrows(CamelExecutionException.class,
+ () -> template.requestBody(
+
"netty-http:https://127.0.0.1:{{port}}/hello?sslContextParameters=#clientCertParameters"
+ + "&hostnameVerification=true",
+ "Hello World", String.class));
+
+ Throwable cause = e;
+ while (cause.getCause() != null) {
+ cause = cause.getCause();
+ }
+ assertTrue(cause.getMessage() != null &&
cause.getMessage().contains("127.0.0.1"),
+ "the host name check should have rejected the certificate, but
got: " + cause.getMessage());
+ }
+}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 36c0651d3a54..17bb72c75f9b 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -3352,6 +3352,16 @@ anything there - the JDK does not support host name
verification on the server s
client certificate with `Endpoint Identification Algorithm HTTPS is not
supported on the server side` - so a consumer
that asks for client certificates and had the option set now completes the
handshake instead of failing it.
+=== camel-netty-http - hostnameVerification is ignored on a consumer
+
+A `netty-http` consumer no longer passes `hostnameVerification=true` to its
TLS engine, as `netty` consumers already
+do not (see above). The JDK does not support host name verification on the
server side of a handshake: a client that
+presented a certificate failed the handshake with
+`Endpoint Identification Algorithm HTTPS is not supported on the server side`,
and a client that presented none was not
+checked at all. A consumer with `needClientAuth=true` and the option set now
accepts trusted client certificates
+instead of rejecting all of them. Consumers that share a server through
`nettySharedHttpServer` never applied the
+option, and are unchanged. The option still verifies the host on a
`netty-http` producer.
+
=== camel-alibaba-oss - a listed object that cannot be got is skipped
When the consumer lists the bucket, a failure to get one of the listed objects
(for example access denied, or an