Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 15 additions & 11 deletions handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.security.Provider;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSessionContext;
Expand Down Expand Up @@ -176,8 +177,8 @@ public JdkSslClientContext(
long sessionCacheSize, long sessionTimeout) throws SSLException {
super(newSSLContext(provider, toX509CertificatesInternal(trustCertCollectionFile),
trustManagerFactory, null, null,
null, null, sessionCacheSize, sessionTimeout, null, KeyStore.getDefaultType(), null), true,
ciphers, cipherFilter, apn, ClientAuth.NONE, null, false);
null, null, sessionCacheSize, sessionTimeout, null, KeyStore.getDefaultType(),
null, false), true, ciphers, cipherFilter, apn, ClientAuth.NONE, null, false);
}

/**
Expand Down Expand Up @@ -260,7 +261,7 @@ public JdkSslClientContext(File trustCertCollectionFile, TrustManagerFactory tru
trustCertCollectionFile), trustManagerFactory,
toX509CertificatesInternal(keyCertChainFile), toPrivateKeyInternal(keyFile, keyPassword),
keyPassword, keyManagerFactory, sessionCacheSize, sessionTimeout,
null, KeyStore.getDefaultType(), null), true,
null, KeyStore.getDefaultType(), null, false), true,
ciphers, cipherFilter, apn, ClientAuth.NONE, null, false);
}

Expand All @@ -270,11 +271,11 @@ public JdkSslClientContext(File trustCertCollectionFile, TrustManagerFactory tru
KeyManagerFactory keyManagerFactory, Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
ApplicationProtocolConfig apn, String[] protocols, long sessionCacheSize, long sessionTimeout,
SecureRandom secureRandom, String keyStoreType, String endpointIdentificationAlgorithm,
ResumptionController resumptionController)
ResumptionController resumptionController, boolean sanPeerIdentityLookup)
throws SSLException {
super(newSSLContext(sslContextProvider, trustCertCollection, trustManagerFactory,
keyCertChain, key, keyPassword, keyManagerFactory, sessionCacheSize,
sessionTimeout, secureRandom, keyStoreType, resumptionController),
sessionTimeout, secureRandom, keyStoreType, resumptionController, sanPeerIdentityLookup),
true, ciphers, cipherFilter, toNegotiator(apn, false), ClientAuth.NONE, protocols, false,
endpointIdentificationAlgorithm, resumptionController);
}
Expand All @@ -285,7 +286,8 @@ private static SSLContext newSSLContext(Provider sslContextProvider,
PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory,
long sessionCacheSize, long sessionTimeout,
SecureRandom secureRandom, String keyStore,
ResumptionController resumptionController) throws SSLException {
ResumptionController resumptionController,
boolean sanPeerIdentityLookup) throws SSLException {
try {
if (trustCertCollection != null) {
trustManagerFactory = buildTrustManagerFactory(trustCertCollection, trustManagerFactory, keyStore);
Expand All @@ -297,8 +299,8 @@ private static SSLContext newSSLContext(Provider sslContextProvider,
SSLContext ctx = sslContextProvider == null ? SSLContext.getInstance(PROTOCOL)
: SSLContext.getInstance(PROTOCOL, sslContextProvider);
ctx.init(keyManagerFactory == null ? null : keyManagerFactory.getKeyManagers(),
trustManagerFactory == null ? null :
wrapIfNeeded(trustManagerFactory.getTrustManagers(), resumptionController),
trustManagerFactory == null ? null : wrapIfNeeded(
trustManagerFactory.getTrustManagers(), resumptionController, sanPeerIdentityLookup),
secureRandom);

SSLSessionContext sessCtx = ctx.getClientSessionContext();
Expand All @@ -317,9 +319,11 @@ private static SSLContext newSSLContext(Provider sslContextProvider,
}
}

private static TrustManager[] wrapIfNeeded(TrustManager[] tms, ResumptionController resumptionController) {
if (resumptionController != null) {
for (int i = 0; i < tms.length; i++) {
private static TrustManager[] wrapIfNeeded(TrustManager[] tms, ResumptionController resumptionController,
boolean sanPeerIdentityLookup) {
for (int i = 0; i < tms.length; i++) {
tms[i] = SanPeerIdentityTrustManager.wrapIfNeeded(tms[i], sanPeerIdentityLookup);
if (resumptionController != null) {
tms[i] = resumptionController.wrapIfNeeded(tms[i]);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ public OpenSslClientContext(File trustCertCollectionFile, TrustManagerFactory tr
OpenSslKeyMaterialProvider.validateKeyMaterialSupported(keyCertChain, key, keyPassword);
sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory,
keyCertChain, key, keyPassword, keyManagerFactory, keyStore,
sessionCacheSize, sessionTimeout, resumptionController);
sessionCacheSize, sessionTimeout, endpointIdentificationAlgorithm,
resumptionController, options);
success = true;
} finally {
if (!success) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ public final class ReferenceCountedOpenSslClientContext extends ReferenceCounted
try {
sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory,
keyCertChain, key, keyPassword, keyManagerFactory, keyStore,
sessionCacheSize, sessionTimeout, resumptionController);
sessionCacheSize, sessionTimeout, endpointIdentificationAlgorithm,
resumptionController, options);
success = true;
} finally {
if (!success) {
Expand All @@ -94,7 +95,9 @@ static OpenSslSessionContext newSessionContext(ReferenceCountedOpenSslContext th
X509Certificate[] keyCertChain, PrivateKey key,
String keyPassword, KeyManagerFactory keyManagerFactory,
String keyStore, long sessionCacheSize, long sessionTimeout,
ResumptionController resumptionController)
String endpointIdentificationAlgorithm,
ResumptionController resumptionController,
Map.Entry<SslContextOption<?>, Object>... options)
throws SSLException {
if (key == null && keyCertChain != null || key != null && keyCertChain == null) {
throw new IllegalArgumentException(
Expand Down Expand Up @@ -155,8 +158,11 @@ static OpenSslSessionContext newSessionContext(ReferenceCountedOpenSslContext th
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
}
final X509TrustManager manager = chooseTrustManager(
trustManagerFactory.getTrustManagers(), resumptionController);
final boolean sanPeerIdentityLookup = isSanPeerIdentityLookupEnabled(
endpointIdentificationAlgorithm, options);
final X509TrustManager manager = wrapTrustManagerIfNeeded(
chooseTrustManager(trustManagerFactory.getTrustManagers(), resumptionController),
sanPeerIdentityLookup);

// IMPORTANT: The callbacks set for verification must be static to prevent memory leak as
// otherwise the context can never be collected. This is because the JNI code holds
Expand Down Expand Up @@ -204,6 +210,37 @@ private static void setVerifyCallback(long ctx, OpenSslEngineMap engineMap, X509
}
}

private static boolean isSanPeerIdentityLookupEnabled(String endpointIdentificationAlgorithm,
Map.Entry<SslContextOption<?>, Object>[] options) {
if (endpointIdentificationAlgorithm == null) {
return false;
}
if (options == null) {
return false;
}
for (Map.Entry<SslContextOption<?>, Object> option : options) {
if (option == null) {
continue;
}
if (SanPeerIdentityConfig.SAN_PEER_IDENTITY_LOOKUP.equals(option.getKey())) {
return Boolean.TRUE.equals(option.getValue());
}
}
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?

Copy link
Copy Markdown
Author

@piyushk010 piyushk010 May 26, 2026

Choose a reason for hiding this comment

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

wrapTrustManagerIfNeeded() references X509ExtendedTrustManager, which in my understanding triggers maybe Netty’s Java compatibility/static-analysis checks because it is newer than the older Java 6 SSL APIs. The suppression is used to acknowledge that intentional usage.

In this case the safety comes from the runtime capability/type check we've done via useExtendedTrustManager(manager), which ensures the SAN-aware wrapper(SanPeerIdentityTrustManager) is only applied when the provided trust manager supports the extended engine-aware APIs required by newly implemented SanPeerIdentityTrustManager.

There are similar usages of @SuppressJava6Requirement around other X509ExtendedTrustManager integrations in the same class, such as setVerifyCallback() and ExtendedTrustManagerVerifyCallback().

private static X509TrustManager wrapTrustManagerIfNeeded(X509TrustManager manager,
boolean sanPeerIdentityLookup) {
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.

Copy link
Copy Markdown
Author

@piyushk010 piyushk010 May 26, 2026

Choose a reason for hiding this comment

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

useExtendedTrustManager(manager) ensures that the SAN-aware wrapper(SanPeerIdentityTrustManager) is only applied when the provided trust manager supports the engine-aware X509ExtendedTrustManager APIs. SanPeerIdentityTrustManager relies on SSLEngine-based callbacks to inspect and potentially override the peer identity used for endpoint verification, which if i understand correctly is not possible with X509TrustManager

return new SanPeerIdentityTrustManager((X509ExtendedTrustManager) manager);
}
return manager;
}

static final class OpenSslClientSessionContext extends OpenSslSessionContext {
OpenSslClientSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider) {
super(context, provider, SSL.SSL_SESS_CACHE_CLIENT, new OpenSslClientSessionCache(context.engineMap));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2026 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.ssl;

final class SanPeerIdentityConfig {
static final SslContextOption<Boolean> SAN_PEER_IDENTITY_LOOKUP =
new SslContextOption<Boolean>("SAN_PEER_IDENTITY_LOOKUP");
Comment on lines +19 to +20
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?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This option is not enabled inside Netty itself. It is intended to be set by the downstream caller that builds the client SslContextBuilder. In our case, that caller is BDP, which enables the option when constructing the client SSL context. look here.


private SanPeerIdentityConfig() {
}
}

Loading