Andrea Cosentino created CAMEL-25649:
----------------------------------------
Summary: camel-netty - hostnameVerification never verifies: the
SSLEngine is created without a peer, so enabling it fails every handshake
Key: CAMEL-25649
URL: https://issues.apache.org/jira/browse/CAMEL-25649
Project: Camel
Issue Type: Bug
Components: camel-netty
Reporter: Andrea Cosentino
h3. Summary
{{camel-netty}}'s {{hostnameVerification}} option cannot verify anything. When
it is enabled the TLS handshake fails for *every* certificate, matching or not,
with:
{noformat}
javax.net.ssl.SSLHandshakeException: ...
caused by java.security.cert.CertificateException: Hostname or IP address is
undefined.
{noformat}
So the option has never worked since it was added in CAMEL-16315 (3.7.4 /
3.9.0). An operator who turns it on to secure a TLS producer gets a hard
connection failure and has no working way to opt in.
h3. Cause
{{DefaultClientInitializerFactory.configureClientSSLOnDemand()}} creates the
engine with the *no-arg* factory method and then asks for host name
verification:
{code:java}
SSLEngine engine = sslContext.createSSLEngine(); // no peer host, no
peer port
engine.setUseClientMode(true);
if (producer.getConfiguration().isHostnameVerification()) {
SSLParameters sslParams = engine.getSSLParameters();
sslParams.setEndpointIdentificationAlgorithm("HTTPS");
engine.setSSLParameters(sslParams);
}
{code}
The JDK's identity check reads the host from the session rather than from the
connection: {{X509TrustManagerImpl.checkIdentity}} calls
{{SSLSession.getPeerHost()}} and hands the result to {{HostnameChecker.match}},
whose first statement is
{code:java}
if (expectedName == null) {
throw new CertificateException("Hostname or IP address is undefined.");
}
{code}
{{getPeerHost()}} is only populated when the engine is created with
{{createSSLEngine(host, port)}}. With the no-arg form it is {{null}}, so the
check cannot run and the handshake is aborted instead.
h3. Reproduction
A client/server {{SSLEngine}} pair driven in memory against a certificate whose
only SAN is {{DNS:localhost}}, on JDK 21.0.10:
|| client engine || peerHost || result ||
| {{createSSLEngine()}} - what camel-netty does | {{null}} |
{{CertificateException: Hostname or IP address is undefined.}} |
| {{createSSLEngine("localhost", 443)}} - what camel-netty-http does |
{{localhost}} | handshake completes |
| {{createSSLEngine("other.example", 443)}} | {{other.example}} |
{{CertificateException: No subject alternative DNS name matching other.example
found.}} |
The third row is the control: it shows verification really is active in the
second row rather than passing vacuously.
h3. Why camel-netty-http is not affected
CAMEL-14070 gave {{camel-netty-http}} SNI support in 4.x by creating its client
engine as {{createSSLEngine(uri.getHost(), uri.getPort())}}. When CAMEL-20812
later copied the host name verification block into
{{HttpClientInitializerFactory}}, it landed on an engine that already carried
the peer, so there it works. The original in {{camel-netty}} never did - which
is also why CAMEL-20812's reporter described the {{camel-netty}} implementation
as the working one.
h3. Proposed fix
Create the client engine with the configured peer when verification is enabled,
as {{camel-netty-http}} does. Keeping the no-arg form when the option is off
confines the change to operators who opted in - and for them the current
behaviour is a hard failure, so there is no regression to cause. Attaching the
peer also makes the JDK send SNI, which is why it is better not to do it on the
default path.
h3. Consumer side - separate question
{{DefaultServerInitializerFactory}} applies the same block to the consumer's
engine, also created with the no-arg form:
* With {{clientMode=true}} the consumer *is* a TCP client, so the same fix
applies.
* In server mode, {{endpointIdentificationAlgorithm=HTTPS}} asks the JDK to
match the *client's* certificate against a host name the server does not have.
Whenever a client certificate is actually presented this fails the handshake
the same way; and matching a client certificate against the client's address is
not a meaningful control. The honest options are to not apply the algorithm in
server mode, or to document the option as client-side only.
Same block is in {{camel-netty-http}}'s {{HttpServerInitializerFactory}} and
{{HttpServerSharedInitializerFactory}}.
This is a functional defect rather than an exposure: the broken state fails
closed, and the insecure default ({{hostnameVerification=false}}) is
pre-existing, documented, and already carries {{security = "insecure:ssl"}}.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)