oscerd opened a new pull request, #27820:
URL: https://github.com/apache/camel/pull/27820

   ## Description
   
   [CAMEL-25649](https://issues.apache.org/jira/browse/CAMEL-25649): 
`camel-netty`'s `hostnameVerification` option cannot verify anything. When it 
is enabled the TLS handshake fails for **every** certificate, matching or not:
   
   ```
   javax.net.ssl.SSLHandshakeException: (certificate_unknown) Hostname or IP 
address is undefined.
     caused by java.security.cert.CertificateException: Hostname or IP address 
is undefined.
   ```
   
   `DefaultClientInitializerFactory` created the engine with the no-arg 
`sslContext.createSSLEngine()` and then asked for host name verification. The 
JDK matches against `SSLSession.getPeerHost()`, which is only populated when 
the engine is created **with** the peer — with the no-arg form it stays `null`, 
and `HostnameChecker.match` opens with
   
   ```java
   if (expectedName == null) {
       throw new CertificateException("Hostname or IP address is undefined.");
   }
   ```
   
   so the check never runs and the handshake is aborted instead. The option has 
been in this state since CAMEL-16315 (3.7.4 / 3.9.0).
   
   `camel-netty-http` is not affected: CAMEL-14070 gave its client engine 
`createSSLEngine(uri.getHost(), uri.getPort())` for SNI in 2019, so when 
CAMEL-20812 copied the verification block in, it landed on an engine that 
already carried the peer. The original in `camel-netty` never did — which is 
also why CAMEL-20812's reporter described the `camel-netty` implementation as 
the working one.
   
   ## Change
   
   Create the client engine with the configured host and port when 
`hostnameVerification` is enabled, as `camel-netty-http` does.
   
   The peer is attached **only** when the option is on. Attaching it also makes 
the JDK send the host as an SNI server name, and the default path should not 
change what goes on the wire. For routes that did set the option there is no 
regression to cause, because today they cannot connect at all.
   
   The consumer gets the same treatment when `clientMode=true`, where it really 
is a TCP client.
   
   ### Left out on purpose — the server side
   
   `DefaultServerInitializerFactory` applies the same block to a consumer 
acting as a server. There `endpointIdentificationAlgorithm=HTTPS` asks the JDK 
to match the **client's** certificate against a host name the server does not 
have, so whenever a client certificate is actually presented it fails the 
handshake the same way — and matching a client certificate against the client's 
address is not a meaningful control. I left that code as it is rather than 
remove a security-looking line unilaterally; the same block is in 
`camel-netty-http`'s `HttpServerInitializerFactory` and 
`HttpServerSharedInitializerFactory`. Happy to follow up whichever way 
reviewers prefer: drop the algorithm in server mode, or document the option as 
client-side only. CAMEL-25649 records the question.
   
   Also filed [CAMEL-25650](https://issues.apache.org/jira/browse/CAMEL-25650) 
(unassigned) for the structural reason this keeps recurring: nothing in 
`org.apache.camel.support.jsse` ever sets `endpointIdentificationAlgorithm`, so 
every component has to remember on its own and an operator cannot compensate 
from `sslContextParameters`.
   
   ## Tests
   
   New `NettySSLHostnameVerificationTest`, with a `keystore-localhost.jks` 
whose only subject alternative name is `DNS:localhost` (documented in 
`keystore-localhost.txt`, following the recipe the module already keeps in 
`keystore.txt`). The existing `keystore.jks` is `CN=CommonName` with no SANs, 
so it cannot test a match.
   
   - `localhost` with `hostnameVerification=true` connects and gets the reply.
   - `127.0.0.1` with `hostnameVerification=true` fails, and the assertion 
requires the message to name `127.0.0.1` — not just "it threw". Without that, 
the test would pass for the wrong reason: before the fix it also throws, with 
"Hostname or IP address is undefined".
   - `127.0.0.1` with the option off connects, so the option is what makes the 
difference.
   
   Revert-check — restoring the no-arg engine while keeping the tests:
   
   | test | result with the fix reverted |
   |---|---|
   | `connectsWhenTheCertificateMatchesTheHost` | **error**, 
`SSLHandshakeException: (certificate_unknown) Hostname or IP address is 
undefined.` |
   | `failsWhenTheCertificateDoesNotMatchTheHost` | **failure** on all 3 
surefire runs: `got: Hostname or IP address is undefined.` |
   | 
`connectsToAHostTheCertificateDoesNotListWhenHostnameVerificationIsDisabled` | 
passes (the default path is untouched) |
   
   The behaviour was also confirmed outside Camel, with a client/server 
`SSLEngine` pair driven in memory on JDK 21.0.10:
   
   | client engine | `getPeerHost()` | result |
   |---|---|---|
   | `createSSLEngine()` | `null` | `CertificateException: Hostname or IP 
address is undefined.` |
   | `createSSLEngine("localhost", 443)` | `localhost` | handshake completes |
   | `createSSLEngine("other.example", 443)` | `other.example` | 
`CertificateException: No subject alternative DNS name matching other.example 
found.` |
   
   | Module | Tests |
   |---|---|
   | camel-netty | 133 (8 skipped) |
   
   `mvn -DskipTests install` in `components/camel-netty` regenerates no files 
beyond the edits in this PR. The catalog copy of the component page is a 
verbatim mirror and was re-synced (`diff` between the two is empty). I did not 
run the full reactor — the box is loaded — so I am relying on CI for that; 
nothing here touches `@UriParam` metadata.
   
   ## Docs
   
   - Component page: a new "Verifying the host name of the server" section — 
what the option checks, that an IP address needs its own subject alternative 
name, the two JDK failure messages, the SNI side effect, and that it does not 
apply to a consumer acting as a server. Mirrored to the catalog.
   - Upgrade guide 4.24: the option now verifies instead of failing. The 
default is unchanged.
   
   ## Backports
   
   Worth backporting to camel-4.22.x and camel-4.18.x after the merge — the 
option is equally broken there. The upgrade-guide entry stays on `main` only.
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to