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)

Reply via email to