Skip to content

Commit 43d26a1

Browse files
committed
DSP-24887: Enable SAN-aware peer identity lookup for verifications
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.
1 parent 945df02 commit 43d26a1

8 files changed

Lines changed: 699 additions & 20 deletions

File tree

handler/src/main/java/io/netty/handler/ssl/JdkSslClientContext.java

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.security.Provider;
2121
import javax.net.ssl.KeyManager;
2222
import javax.net.ssl.KeyManagerFactory;
23+
import javax.net.ssl.X509ExtendedTrustManager;
2324
import javax.net.ssl.SSLContext;
2425
import javax.net.ssl.SSLException;
2526
import javax.net.ssl.SSLSessionContext;
@@ -176,8 +177,8 @@ public JdkSslClientContext(
176177
long sessionCacheSize, long sessionTimeout) throws SSLException {
177178
super(newSSLContext(provider, toX509CertificatesInternal(trustCertCollectionFile),
178179
trustManagerFactory, null, null,
179-
null, null, sessionCacheSize, sessionTimeout, null, KeyStore.getDefaultType(), null), true,
180-
ciphers, cipherFilter, apn, ClientAuth.NONE, null, false);
180+
null, null, sessionCacheSize, sessionTimeout, null, KeyStore.getDefaultType(),
181+
null, false), true, ciphers, cipherFilter, apn, ClientAuth.NONE, null, false);
181182
}
182183

183184
/**
@@ -260,7 +261,7 @@ public JdkSslClientContext(File trustCertCollectionFile, TrustManagerFactory tru
260261
trustCertCollectionFile), trustManagerFactory,
261262
toX509CertificatesInternal(keyCertChainFile), toPrivateKeyInternal(keyFile, keyPassword),
262263
keyPassword, keyManagerFactory, sessionCacheSize, sessionTimeout,
263-
null, KeyStore.getDefaultType(), null), true,
264+
null, KeyStore.getDefaultType(), null, false), true,
264265
ciphers, cipherFilter, apn, ClientAuth.NONE, null, false);
265266
}
266267

@@ -270,11 +271,11 @@ public JdkSslClientContext(File trustCertCollectionFile, TrustManagerFactory tru
270271
KeyManagerFactory keyManagerFactory, Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
271272
ApplicationProtocolConfig apn, String[] protocols, long sessionCacheSize, long sessionTimeout,
272273
SecureRandom secureRandom, String keyStoreType, String endpointIdentificationAlgorithm,
273-
ResumptionController resumptionController)
274+
ResumptionController resumptionController, boolean sanPeerIdentityLookup)
274275
throws SSLException {
275276
super(newSSLContext(sslContextProvider, trustCertCollection, trustManagerFactory,
276277
keyCertChain, key, keyPassword, keyManagerFactory, sessionCacheSize,
277-
sessionTimeout, secureRandom, keyStoreType, resumptionController),
278+
sessionTimeout, secureRandom, keyStoreType, resumptionController, sanPeerIdentityLookup),
278279
true, ciphers, cipherFilter, toNegotiator(apn, false), ClientAuth.NONE, protocols, false,
279280
endpointIdentificationAlgorithm, resumptionController);
280281
}
@@ -285,7 +286,8 @@ private static SSLContext newSSLContext(Provider sslContextProvider,
285286
PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory,
286287
long sessionCacheSize, long sessionTimeout,
287288
SecureRandom secureRandom, String keyStore,
288-
ResumptionController resumptionController) throws SSLException {
289+
ResumptionController resumptionController,
290+
boolean sanPeerIdentityLookup) throws SSLException {
289291
try {
290292
if (trustCertCollection != null) {
291293
trustManagerFactory = buildTrustManagerFactory(trustCertCollection, trustManagerFactory, keyStore);
@@ -297,8 +299,8 @@ private static SSLContext newSSLContext(Provider sslContextProvider,
297299
SSLContext ctx = sslContextProvider == null ? SSLContext.getInstance(PROTOCOL)
298300
: SSLContext.getInstance(PROTOCOL, sslContextProvider);
299301
ctx.init(keyManagerFactory == null ? null : keyManagerFactory.getKeyManagers(),
300-
trustManagerFactory == null ? null :
301-
wrapIfNeeded(trustManagerFactory.getTrustManagers(), resumptionController),
302+
trustManagerFactory == null ? null : wrapIfNeeded(
303+
trustManagerFactory.getTrustManagers(), resumptionController, sanPeerIdentityLookup),
302304
secureRandom);
303305

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

320-
private static TrustManager[] wrapIfNeeded(TrustManager[] tms, ResumptionController resumptionController) {
321-
if (resumptionController != null) {
322-
for (int i = 0; i < tms.length; i++) {
322+
private static TrustManager[] wrapIfNeeded(TrustManager[] tms, ResumptionController resumptionController,
323+
boolean sanPeerIdentityLookup) {
324+
for (int i = 0; i < tms.length; i++) {
325+
tms[i] = SanPeerIdentityTrustManager.wrapIfNeeded(tms[i], sanPeerIdentityLookup);
326+
if (resumptionController != null) {
323327
tms[i] = resumptionController.wrapIfNeeded(tms[i]);
324328
}
325329
}

handler/src/main/java/io/netty/handler/ssl/OpenSslClientContext.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,8 @@ public OpenSslClientContext(File trustCertCollectionFile, TrustManagerFactory tr
196196
OpenSslKeyMaterialProvider.validateKeyMaterialSupported(keyCertChain, key, keyPassword);
197197
sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory,
198198
keyCertChain, key, keyPassword, keyManagerFactory, keyStore,
199-
sessionCacheSize, sessionTimeout, resumptionController);
199+
sessionCacheSize, sessionTimeout, endpointIdentificationAlgorithm,
200+
resumptionController, options);
200201
success = true;
201202
} finally {
202203
if (!success) {

handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslClientContext.java

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ public final class ReferenceCountedOpenSslClientContext extends ReferenceCounted
7373
try {
7474
sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory,
7575
keyCertChain, key, keyPassword, keyManagerFactory, keyStore,
76-
sessionCacheSize, sessionTimeout, resumptionController);
76+
sessionCacheSize, sessionTimeout, endpointIdentificationAlgorithm,
77+
resumptionController, options);
7778
success = true;
7879
} finally {
7980
if (!success) {
@@ -94,7 +95,9 @@ static OpenSslSessionContext newSessionContext(ReferenceCountedOpenSslContext th
9495
X509Certificate[] keyCertChain, PrivateKey key,
9596
String keyPassword, KeyManagerFactory keyManagerFactory,
9697
String keyStore, long sessionCacheSize, long sessionTimeout,
97-
ResumptionController resumptionController)
98+
String endpointIdentificationAlgorithm,
99+
ResumptionController resumptionController,
100+
Map.Entry<SslContextOption<?>, Object>... options)
98101
throws SSLException {
99102
if (key == null && keyCertChain != null || key != null && keyCertChain == null) {
100103
throw new IllegalArgumentException(
@@ -155,8 +158,11 @@ static OpenSslSessionContext newSessionContext(ReferenceCountedOpenSslContext th
155158
TrustManagerFactory.getDefaultAlgorithm());
156159
trustManagerFactory.init((KeyStore) null);
157160
}
158-
final X509TrustManager manager = chooseTrustManager(
159-
trustManagerFactory.getTrustManagers(), resumptionController);
161+
final boolean sanPeerIdentityLookup = isSanPeerIdentityLookupEnabled(
162+
endpointIdentificationAlgorithm, options);
163+
final X509TrustManager manager = wrapTrustManagerIfNeeded(
164+
chooseTrustManager(trustManagerFactory.getTrustManagers(), resumptionController),
165+
sanPeerIdentityLookup);
160166

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

213+
private static boolean isSanPeerIdentityLookupEnabled(String endpointIdentificationAlgorithm,
214+
Map.Entry<SslContextOption<?>, Object>[] options) {
215+
if (endpointIdentificationAlgorithm == null) {
216+
return false;
217+
}
218+
if (options == null) {
219+
return false;
220+
}
221+
for (Map.Entry<SslContextOption<?>, Object> option : options) {
222+
if (option == null) {
223+
continue;
224+
}
225+
if (SanPeerIdentityConfig.SAN_PEER_IDENTITY_LOOKUP.equals(option.getKey())) {
226+
return Boolean.TRUE.equals(option.getValue());
227+
}
228+
}
229+
return false;
230+
}
231+
232+
@SuppressJava6Requirement(reason = "Usage guarded by java version check")
233+
private static X509TrustManager wrapTrustManagerIfNeeded(X509TrustManager manager,
234+
boolean sanPeerIdentityLookup) {
235+
if (!sanPeerIdentityLookup) {
236+
return manager;
237+
}
238+
if (useExtendedTrustManager(manager)) {
239+
return new SanPeerIdentityTrustManager((X509ExtendedTrustManager) manager);
240+
}
241+
return manager;
242+
}
243+
207244
static final class OpenSslClientSessionContext extends OpenSslSessionContext {
208245
OpenSslClientSessionContext(ReferenceCountedOpenSslContext context, OpenSslKeyMaterialProvider provider) {
209246
super(context, provider, SSL.SSL_SESS_CACHE_CLIENT, new OpenSslClientSessionCache(context.engineMap));
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2026 The Netty Project
3+
*
4+
* The Netty Project licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
package io.netty.handler.ssl;
17+
18+
final class SanPeerIdentityConfig {
19+
static final SslContextOption<Boolean> SAN_PEER_IDENTITY_LOOKUP =
20+
new SslContextOption<Boolean>("SAN_PEER_IDENTITY_LOOKUP");
21+
22+
private SanPeerIdentityConfig() {
23+
}
24+
}
25+

0 commit comments

Comments
 (0)