Skip to content

DSP-24887: Enable SAN-aware peer identity lookup for endpoint verification on hostname#40

Open
piyushk010 wants to merge 1 commit into
dse-netty-4.1.128.2from
DSP-24887-4.1.128.2
Open

DSP-24887: Enable SAN-aware peer identity lookup for endpoint verification on hostname#40
piyushk010 wants to merge 1 commit into
dse-netty-4.1.128.2from
DSP-24887-4.1.128.2

Conversation

@piyushk010
Copy link
Copy Markdown

@piyushk010 piyushk010 commented May 18, 2026

No description provided.

Enable SAN-aware peer identity lookup for Netty client endpoint verification based
on hostname.

Added a new opt-in SAN-aware peer identity feature via `SAN_PEER_IDENTITY_LOOKUP`
in Netty SSL context configuration. Implemented `SanPeerIdentityTrustManager` to
dynamically select the verification identity based on certificate SAN types,
including optional reverse-DNS lookup for IP-based peers with DNS SAN certs.

Integrated the feature into both JDK and OpenSSL client TLS paths by wrapping
`X509ExtendedTrustManager` during SSL context/session creation. Updated
`SslContextBuilder`, `OpenSslClientContext`, `ReferenceCountedOpenSslClientContext`,
and `JdkSslClientContext` so the SAN-aware verification behavior is propagated
consistently across providers.
Copy link
Copy Markdown

@szymon-miezal szymon-miezal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have completed the first pass, the direction seems ok to me, I am yet to validate it end to end though.

Comment on lines +323 to +324
private static TrustManager[] wrapIfNeeded(TrustManager[] tms, boolean sanPeerIdentityLookup,
ResumptionController resumptionController) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would keep the order of existing arguments unchanged - add the boolean flag as last one.

import java.util.Collection;
import java.util.List;

final class SanPeerIdentityTrustManager extends X509ExtendedTrustManager {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this class need to extend X509ExtendedTrustManager?
I see that the constructor already takes an instance of X509ExtendedTrustManager, what is the point of inheritance then?
If the goal is to declare method it should rather implement an interface, perhaps X509TrustManager.

Comment on lines +29 to +30
private static final Integer DNS_SAN_TYPE = Integer.valueOf(2);
private static final Integer IP_SAN_TYPE = Integer.valueOf(7);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are these values taken from?
Do these need to be big integers?


@Override
public SSLSession getSession() {
return new DelegatingPeerHostSession(super.getSession(), peerHost);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need here the same null guard as in getHandshakeSession? If not, why?

Comment on lines +62 to +64
System.out.println("SanPeerIdentityTrustManager: lookup skipped: engine=" + (engine != null)
+ ", chainPresent=" + (chain != null)
+ ", leafPresent=" + (chain != null && chain.length > 0 && chain[0] != null));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Standard output loggers are a bad idea, please rework it to use the pattern used in other classes.

Comment on lines +98 to +142
private String resolvePeerIdentity(SSLEngine engine, X509Certificate leafCertificate) throws CertificateException {
String peerHost = engine.getPeerHost();
System.out.println("SanPeerIdentityTrustManager: resolving peerHost=" + peerHost
+ ", reverseLookupHosts=" + reverseLookupHosts);
if (peerHost == null || peerHost.isEmpty()) {
System.out.println("SanPeerIdentityTrustManager: aborted due to empty peerHost");
return null;
}

SanTypes sanTypes = readSanTypes(leafCertificate);
System.out.println("SanPeerIdentityTrustManager: SAN types for peerHost=" + peerHost
+ ", hasDnsSans=" + sanTypes.hasDnsSans
+ ", hasIpSans=" + sanTypes.hasIpSans);
if (!sanTypes.hasDnsSans && !sanTypes.hasIpSans) {
System.out.println("SanPeerIdentityTrustManager: aborted for peerHost=" + peerHost
+ " because certificate has no DNS/IP SANs");
return null;
}

if (sanTypes.hasIpSans && !sanTypes.hasDnsSans) {
System.out.println("SanPeerIdentityTrustManager: using original peerHost=" + peerHost
+ " because certificate only has IP SANs");
return peerHost;
}

if (sanTypes.hasDnsSans && !isIpAddress(peerHost)) {
System.out.println("SanPeerIdentityTrustManager: using original peerHost=" + peerHost
+ " because it is already a hostname and certificate has DNS SANs");
return peerHost;
}

if (sanTypes.hasDnsSans && reverseLookupHosts) {
String canonicalHost = reverseLookup(peerHost);
System.out.println("SanPeerIdentityTrustManager: reverse lookup for peerHost=" + peerHost
+ " returned canonicalHost=" + canonicalHost);
if (canonicalHost != null && !isIpAddress(canonicalHost)) {
System.out.println("SanPeerIdentityTrustManager: using reverse-looked-up canonicalHost="
+ canonicalHost + " for peerHost=" + peerHost);
return canonicalHost;
}
}

System.out.println("SanPeerIdentityTrustManager: falling back to original peerHost=" + peerHost);
return peerHost;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like logic that could easily be unit tested

private final X509ExtendedTrustManager delegate;
private final boolean reverseLookupHosts;

SanPeerIdentityTrustManager(X509ExtendedTrustManager delegate, boolean reverseLookupHosts) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we should assume the goal of this class is performing a DNS lookup (if needed), in other words, it should be only created if the passed reverseLookupHosts argument is true and then we don't need this parameter at all.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Furthermore I don't see any executions of SanPeerIdentityTrustManager with reverseLookupHosts=false.

}

@Override
public void checkClientTrusted(X509Certificate[] chain, String authType, java.net.Socket socket)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why Socket can't be imported?

Comment on lines +326 to +328
if (sanPeerIdentityLookup && tms[i] instanceof X509ExtendedTrustManager) {
tms[i] = new SanPeerIdentityTrustManager((X509ExtendedTrustManager) tms[i], true);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could move it to a wrapIfNeeded status method of SanPeerIdentityTrustManager?

return false;
}

@SuppressJava6Requirement(reason = "Usage guarded by java version check")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no java version check with wrapTrustManagerIfNeeded, what is the point of this annotation?

Comment on lines +19 to +20
static final SslContextOption<Boolean> SAN_PEER_IDENTITY_LOOKUP =
new SslContextOption<Boolean>("SAN_PEER_IDENTITY_LOOKUP");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are we setting these options?

if (!sanPeerIdentityLookup) {
return manager;
}
if (useExtendedTrustManager(manager)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please explain what's the intent behind the useExtendedTrustManager check.

*/
public <T> SslContextBuilder option(SslContextOption<T> option, T value) {
if (SanPeerIdentityConfig.SAN_PEER_IDENTITY_LOOKUP.equals(option)) {
sanPeerIdentityLookup = value != null && Boolean.TRUE.equals(value);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does setting the sanPeerIdentityLookup happen only when the value is true?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants