Core: Expose HostnameVerificationPolicy in TLSConfigurer#15500
Core: Expose HostnameVerificationPolicy in TLSConfigurer#15500adutra wants to merge 7 commits intoapache:mainfrom
Conversation
|
FYI @bryanck |
ad082ad to
2858de7
Compare
Alex, why would we allow such a verification to be bypassed, do you have some real world use case for this ? |
| awssdk-s3accessgrants = { module = "software.amazon.s3.accessgrants:aws-s3-accessgrants-java-plugin", version.ref = "awssdk-s3accessgrants" } | ||
| azuresdk-bom = { module = "com.azure:azure-sdk-bom", version.ref = "azuresdk-bom" } | ||
| bson = { module = "org.mongodb:bson", version.ref = "bson-ver"} | ||
| bouncycastle-bcpkix = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "bouncycastle" } |
There was a problem hiding this comment.
Since this is new dependency, do we have to add the licence of this dependency to one of the LICENSE files in Iceberg?
There was a problem hiding this comment.
No, since it's used only in tests.
There was a problem hiding this comment.
Thanks for clarifying.
There was a problem hiding this comment.
that's good because bouncycastle complicates export rules, or at least due diligence
There was a problem hiding this comment.
I assumes the bouncycastle bcpkix is a transitive dep already pulled in by the mockerserver lib. is the purpose here just to upgrade the version?
There was a problem hiding this comment.
Very good question! Short answer: yes.
MockServer does bring BouncyCastle transitively — bcprov, bcpkix, and bcutil – but at version 1.72, which is somewhat old.
The problem is that other deps (Hadoop) bring bcprov 1.82, and Gradle resolves the conflict by upgrading bcprov to 1.82. But bcpkix and bcutil stay at 1.72, since nothing else declares a higher version. This creates a mismatch: bcpkix 1.72 + bcprov 1.82 = NoClassDefFoundError.
I only upgraded bcpkix here, because that is enough for running the TLS tests. If you prefer, I can also explicitly upgrade bcutil.
There was a problem hiding this comment.
it is probably good to just ensure the same version for bouncycastle family of jars. That is the current practice. E.g., there is just one version number for the two jetty jars in the toml file.
There was a problem hiding this comment.
You are right: I implemented your suggestion.
You could find yourself in the situation where the catalog server has TLS enabled, and its certificate shows a SAN of e.g. (Please note: I mentioned |
|
And I forgot to mention: |
|
@bryanck since you introduced |
Apache HttpClient 5.4 introduced a new component: `HostnameVerificationPolicy`, which determines whether hostname verification is done by the JSSE provider (at socket level, during TLS handshake), the HttpClient (after TLS handshake), or both. This change exposes `HostnameVerificationPolicy` in `TLSConfigurer`. This component is particularly useful when attempting to bypass hostname verification, e.g. by using the `NoopHostnameVerifier`. The default policy is set to `BOTH`, which produces the same result as before.
2858de7 to
6e3cfb8
Compare
|
Update: I was able to find the exact change that caused this regression: In 1.10 we use httpclient5 version 5.5, where the public DefaultClientTlsStrategy(
final SSLContext sslContext,
final String[] supportedProtocols,
final String[] supportedCipherSuites,
final SSLBufferMode sslBufferManagement,
final HostnameVerifier hostnameVerifier) {
this(sslContext, supportedProtocols, supportedCipherSuites, sslBufferManagement, HostnameVerificationPolicy.CLIENT, hostnameVerifier);
}But in 1.11 we upgraded httpclient5 to version 5.6, where the same constructor becomes: public DefaultClientTlsStrategy(
final SSLContext sslContext,
final String[] supportedProtocols,
final String[] supportedCipherSuites,
final SSLBufferMode sslBufferManagement,
final HostnameVerifier hostnameVerifier) {
this(sslContext, supportedProtocols, supportedCipherSuites, sslBufferManagement, null, hostnameVerifier);
}Passing So, this is imho clearly a regression, and in fact the default value for default HostnameVerificationPolicy hostnameVerificationPolicy() {
return HostnameVerificationPolicy.CLIENT;
}I will change that. @singhpk234 could you please add this to the 1.11 milestone? Now I'm really convinced it's a regression. |
|
@danielcweeks and @singhpk234 : as requested, I created an issue to better explain why I think we have a regression and how this PR fixes it: #15598. PTAL 🙏 |
I am wondering about the default value of HostnameVerificationPolicy.CLIENT. I know the purpose is maintain the same behavior. Unit test can configure Is |
| exclude group: 'junit' | ||
| } | ||
| testImplementation libs.awaitility | ||
| testImplementation libs.bouncycastle.bcpkix |
There was a problem hiding this comment.
I think it's worth at least adding a comment on why these dependencies are being added, since it's not obvious
Best to lock down and require relaxation in deployments than to unlock and regret it. More formally CWE-1188: Initialization of a Resource with an Insecure Default |
|
Can you send an email to dev@ to get broader feedback? It might be ok introduce a slight behavior change. Most production use cases probably should use DNS anyway. For IP address usage, users can add the IP address to the cert SAN. This is the only question I have for this PR. Exposing |
|
@adutra @stevenzwu Want to follow up if this is required for 1.11.0 release. Can you help driving this? Thanks. |
|
@aihuaxu this is needed for 1.11.0, as the original change was added in the 1.11 scope. Waiting for the dev thread to get more tractions. |
| return SSLContexts.createDefault(); | ||
| } | ||
|
|
||
| default HostnameVerificationPolicy hostnameVerificationPolicy() { |
There was a problem hiding this comment.
I wrote on the ML list a bit about this, but do we really need this to be customizable?
It seems to be that there are only really 2 options that make sense.
Either
hostnameVerifier is replaced and we should use "client" mode
or
hostnameVerifier is not replaced and we should be using "builtin" mode
| return HostnameVerificationPolicy.CLIENT; | ||
| } | ||
|
|
||
| default HostnameVerifier hostnameVerifier() { |
There was a problem hiding this comment.
@RussellSpitzer @adutra this is the method where I suggested adding Javadoc. or you think this is redundant and the intention is obvious from the API already?
/**
* If overwritten, only the custom verifier is executed and
* Java Secure Socket Extension (JSSE) built-in verifier won't be executed.
*/
I also like Russell's suggestion from email of making this method returning null so that we can get rid of the new method above.
There was a problem hiding this comment.
@stevenzwu @RussellSpitzer I implemented the suggested approach, PTAL.
I want to stress though that returning null instead of HttpsSupport.getDefaultHostnameVerifier() here is a behavioral change.
The built-in verifier and Apache's default verifier both do roughly the same verifications, but the latter is more strict/complete. They also kick in in different phases and throw different exceptions.
There was a problem hiding this comment.
I want to stress though that returning null instead of HttpsSupport.getDefaultHostnameVerifier() here is a behavioral change.
Not really a behavior change btw 1.10 and 1.11 since the original change from Bryant hasn't been released it.
And returning null is essentially the same as using the default built-in hostname verifier.
There was a problem hiding this comment.
Not really a behavior change btw 1.10 and 1.11 since the original change from Bryant hasn't been released it.
From what I see, TLSConfigurer has been released in 1.10.
stevenzwu
left a comment
There was a problem hiding this comment.
LGTM.
just a nit comment on the test code.
| assertThatThrownBy(() -> builtInVerifierClient.head(path, Map.of(), (unused) -> {})) | ||
| .rootCause() | ||
| .isInstanceOf(CertificateException.class) | ||
| .hasMessage("No subject alternative names matching IP address 127.0.0.1 found"); |
There was a problem hiding this comment.
Apparently this is wording JDK specific, May come up later if folks are testing on other architectures. But I think we are mostly testing on OpenJDK, Oracle JDK and the other OpenJDK based versions.
OpenJDK / Oracle JDK: "No subject alternative names matching IP address 127.0.0.1 found" — this is the message in sun.security.util.HostnameChecker.
IBM Semeru / J9: IBM's JSSE implementation has its own HostnameChecker equivalent with different wording. Historically it uses messages like "No name matching 127.0.0.1 found" or similar variations.
Eclipse Temurin / Azul Zulu: These are typically OpenJDK-based and share the message, but there's no guarantee across versions — the sun.security package is internal and its strings can change between releases without notice.
RussellSpitzer
left a comment
There was a problem hiding this comment.
Looks good to me, left a few nits on style
|
@steveloughran - @nastra - Any remaining comments on this one? |
1a8f7d3 to
e631a0f
Compare
e631a0f to
b5bb63a
Compare
|
original PR commit: but as Alex mentioned in the community sync, the library update was NOT part of 1.10.x This issue requires both the PR and the library update |
singhpk234
left a comment
There was a problem hiding this comment.
It would be nice to know when we a choosing JSSE host name verifier, how much can we now influence with java ops / system prop and what are the new cases we are opening to when are shifting from lib to this one.
What are you looking for in particular? I tried to do some Cursor assisted analysis and as far as I can tell neither of the verifiers are actually changeable via system properties or java opts. Neither of the OpenJDK HostnameChecker verifier nor the Apache DefaultHostnameVerifier use system.getProperty. Cursor did list a few things that would indirectly change behaviors but they seem to be all a result of the fact that JSSE will will do hostname verification during the TLS handshake rather than the DefaultHostnameVerifier which checks after the handshake. |
|
Thanks for analysis @RussellSpitzer, I was mostly curious about how jvm flags can influence host verification now, when JSSE is being used one such flag switching the default verification engine from Apache's library to JSSE exposes a knob that was previously irrelevant. specially what if JVM was running with this flag on will now come into effect in rest client
It was not meant to be a blocker of this pr, but more from curiously purpose and i think we should proceed if there is a concencus on the approach. Though one thing I think while researching this flag, i would recommend we mention this in JSSE as default in the release notes explictly! |
This property lives in
Exactly. If an attacker can set JVM launch flags, they can already disable TLS entirely, swap trust stores, etc. The game is already over. |
That's a good idea! Here is a suggestion for the release notes:
cc @aihuaxu |
Fixes #15598.
This change exposes
HostnameVerificationPolicyinTLSConfigurer. The default policy is set toCLIENT.I also added a unit test that intentionally uses a custom hostname verifier that is incompatible with JSSE hostname verification strategies. Thus, the test cannot pass unless the verification policy is set to
CLIENT.