• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #20002

29 Sep 2025 04:21PM UTC coverage: 88.592% (+0.02%) from 88.575%
#20002

push

github

web-flow
xds: xDS based SNI setting and SAN validation (#12378)

When using xDS credentials make SNI for the Tls handshake to be
configured via xDS, rather than use the channel authority as the SNI,
and make SAN validation to be able to use the SNI sent when so
instructed via xDS.

Implements A101.

34877 of 39368 relevant lines covered (88.59%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.0
/../xds/src/main/java/io/grpc/xds/internal/security/SecurityProtocolNegotiators.java
1
/*
2
 * Copyright 2019 The gRPC Authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://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,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package io.grpc.xds.internal.security;
18

19
import static com.google.common.base.Preconditions.checkNotNull;
20

21
import com.google.common.annotations.VisibleForTesting;
22
import com.google.common.base.Strings;
23
import io.grpc.Attributes;
24
import io.grpc.Grpc;
25
import io.grpc.internal.GrpcUtil;
26
import io.grpc.internal.ObjectPool;
27
import io.grpc.netty.GrpcHttp2ConnectionHandler;
28
import io.grpc.netty.InternalProtocolNegotiationEvent;
29
import io.grpc.netty.InternalProtocolNegotiator;
30
import io.grpc.netty.InternalProtocolNegotiator.ProtocolNegotiator;
31
import io.grpc.netty.InternalProtocolNegotiators;
32
import io.grpc.netty.ProtocolNegotiationEvent;
33
import io.grpc.xds.EnvoyServerProtoData;
34
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
35
import io.grpc.xds.internal.XdsInternalAttributes;
36
import io.grpc.xds.internal.security.trust.CertificateUtils;
37
import io.netty.channel.ChannelHandler;
38
import io.netty.channel.ChannelHandlerAdapter;
39
import io.netty.channel.ChannelHandlerContext;
40
import io.netty.channel.ChannelInboundHandlerAdapter;
41
import io.netty.handler.ssl.SslContext;
42
import io.netty.util.AsciiString;
43
import java.security.cert.CertStoreException;
44
import java.util.AbstractMap;
45
import java.util.ArrayList;
46
import java.util.List;
47
import java.util.concurrent.Executor;
48
import java.util.logging.Level;
49
import java.util.logging.Logger;
50
import javax.annotation.Nullable;
51
import javax.net.ssl.X509TrustManager;
52

53
/**
54
 * Provides client and server side gRPC {@link ProtocolNegotiator}s to provide the SSL
55
 * context.
56
 */
57
@VisibleForTesting
58
public final class SecurityProtocolNegotiators {
59

60
  // Prevent instantiation.
61
  private SecurityProtocolNegotiators() {
62
  }
63

64
  private static final Logger logger
1✔
65
      = Logger.getLogger(SecurityProtocolNegotiators.class.getName());
1✔
66

67
  private static final AsciiString SCHEME = AsciiString.of("http");
1✔
68

69
  public static final Attributes.Key<SslContextProviderSupplier>
70
      ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER =
1✔
71
      Attributes.Key.create("io.grpc.xds.internal.security.server.sslContextProviderSupplier");
1✔
72

73
  /** Attribute key for SslContextProviderSupplier (used from client) for a subchannel. */
74
  @Grpc.TransportAttr
75
  public static final Attributes.Key<SslContextProviderSupplier>
76
      ATTR_SSL_CONTEXT_PROVIDER_SUPPLIER =
1✔
77
      Attributes.Key.create("io.grpc.xds.internal.security.SslContextProviderSupplier");
1✔
78

79
  /**
80
   * Returns a {@link InternalProtocolNegotiator.ClientFactory}.
81
   *
82
   * @param fallbackNegotiator protocol negotiator to use as fallback.
83
   */
84
  public static InternalProtocolNegotiator.ClientFactory clientProtocolNegotiatorFactory(
85
      @Nullable InternalProtocolNegotiator.ClientFactory fallbackNegotiator) {
86
    return new ClientFactory(fallbackNegotiator);
1✔
87
  }
88

89
  public static InternalProtocolNegotiator.ServerFactory serverProtocolNegotiatorFactory(
90
      @Nullable InternalProtocolNegotiator.ServerFactory fallbackNegotiator) {
91
    return new ServerFactory(fallbackNegotiator);
1✔
92
  }
93

94
  private static final class ServerFactory implements InternalProtocolNegotiator.ServerFactory {
95

96
    private final InternalProtocolNegotiator.ServerFactory fallbackProtocolNegotiator;
97

98
    private ServerFactory(InternalProtocolNegotiator.ServerFactory fallbackNegotiator) {
1✔
99
      this.fallbackProtocolNegotiator = fallbackNegotiator;
1✔
100
    }
1✔
101

102
    @Override
103
    public ProtocolNegotiator newNegotiator(ObjectPool<? extends Executor> offloadExecutorPool) {
104
      return new ServerSecurityProtocolNegotiator(
1✔
105
          fallbackProtocolNegotiator.newNegotiator(offloadExecutorPool));
1✔
106
    }
107
  }
108

109
  private static final class ClientFactory implements InternalProtocolNegotiator.ClientFactory {
110

111
    private final InternalProtocolNegotiator.ClientFactory fallbackProtocolNegotiator;
112

113
    private ClientFactory(InternalProtocolNegotiator.ClientFactory fallbackNegotiator) {
1✔
114
      this.fallbackProtocolNegotiator = fallbackNegotiator;
1✔
115
    }
1✔
116

117
    @Override
118
    public ProtocolNegotiator newNegotiator() {
119
      return new ClientSecurityProtocolNegotiator(fallbackProtocolNegotiator.newNegotiator());
1✔
120
    }
121

122
    @Override
123
    public int getDefaultPort() {
124
      return GrpcUtil.DEFAULT_PORT_SSL;
1✔
125
    }
126
  }
127

128
  @VisibleForTesting
129
  static final class ClientSecurityProtocolNegotiator implements ProtocolNegotiator {
130

131
    @Nullable private final ProtocolNegotiator fallbackProtocolNegotiator;
132

133
    ClientSecurityProtocolNegotiator(@Nullable ProtocolNegotiator fallbackProtocolNegotiator) {
1✔
134
      this.fallbackProtocolNegotiator = fallbackProtocolNegotiator;
1✔
135
    }
1✔
136

137
    @Override
138
    public AsciiString scheme() {
139
      return SCHEME;
1✔
140
    }
141

142
    @Override
143
    public ChannelHandler newHandler(GrpcHttp2ConnectionHandler grpcHandler) {
144
      // check if SslContextProviderSupplier was passed via attributes
145
      SslContextProviderSupplier localSslContextProviderSupplier =
1✔
146
          grpcHandler.getEagAttributes().get(ATTR_SSL_CONTEXT_PROVIDER_SUPPLIER);
1✔
147
      if (localSslContextProviderSupplier == null) {
1✔
148
        checkNotNull(
1✔
149
            fallbackProtocolNegotiator, "No TLS config and no fallbackProtocolNegotiator!");
150
        return fallbackProtocolNegotiator.newHandler(grpcHandler);
1✔
151
      }
152
      return new ClientSecurityHandler(grpcHandler, localSslContextProviderSupplier,
1✔
153
          grpcHandler.getEagAttributes().get(XdsInternalAttributes.ATTR_ADDRESS_NAME));
1✔
154
    }
155

156
    @Override
157
    public void close() {}
1✔
158
  }
159

160
  private static class BufferReadsHandler extends ChannelInboundHandlerAdapter {
1✔
161
    private final List<Object> reads = new ArrayList<>();
1✔
162
    private boolean readComplete;
163

164
    @Override
165
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
166
      reads.add(msg);
1✔
167
    }
1✔
168

169
    @Override
170
    public void channelReadComplete(ChannelHandlerContext ctx) {
171
      readComplete = true;
1✔
172
    }
1✔
173

174
    @Override
175
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
176
      for (Object msg : reads) {
1✔
177
        super.channelRead(ctx, msg);
1✔
178
      }
1✔
179
      if (readComplete) {
1✔
180
        super.channelReadComplete(ctx);
1✔
181
      }
182
    }
1✔
183

184
    @Override
185
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
186
      logger.log(Level.SEVERE, "exceptionCaught", cause);
×
187
      ctx.fireExceptionCaught(cause);
×
188
    }
×
189
  }
190

191
  @VisibleForTesting
192
  static final class ClientSecurityHandler
193
      extends InternalProtocolNegotiators.ProtocolNegotiationHandler {
194
    private final GrpcHttp2ConnectionHandler grpcHandler;
195
    private final SslContextProviderSupplier sslContextProviderSupplier;
196
    private final String sni;
197

198
    ClientSecurityHandler(
199
        GrpcHttp2ConnectionHandler grpcHandler,
200
        SslContextProviderSupplier sslContextProviderSupplier,
201
        String endpointHostname) {
202
      super(
1✔
203
          // superclass (InternalProtocolNegotiators.ProtocolNegotiationHandler) expects 'next'
204
          // handler but we don't have a next handler _yet_. So we "disable" superclass's behavior
205
          // here and then manually add 'next' when we call fireProtocolNegotiationEvent()
206
          new ChannelHandlerAdapter() {
1✔
207
            @Override
208
            public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
209
              ctx.pipeline().remove(this);
1✔
210
            }
1✔
211
          }, grpcHandler.getNegotiationLogger());
1✔
212
      checkNotNull(grpcHandler, "grpcHandler");
1✔
213
      this.grpcHandler = grpcHandler;
1✔
214
      this.sslContextProviderSupplier = sslContextProviderSupplier;
1✔
215
      EnvoyServerProtoData.BaseTlsContext tlsContext = sslContextProviderSupplier.getTlsContext();
1✔
216
      UpstreamTlsContext upstreamTlsContext = ((UpstreamTlsContext) tlsContext);
1✔
217
      if (CertificateUtils.isXdsSniEnabled) {
1✔
218
        sni = upstreamTlsContext.getAutoHostSni() && !Strings.isNullOrEmpty(endpointHostname)
1✔
219
            ? endpointHostname : upstreamTlsContext.getSni();
1✔
220
      } else {
221
        sni = grpcHandler.getAuthority();
1✔
222
      }
223
    }
1✔
224

225
    @VisibleForTesting
226
    String getSni() {
227
      return sni;
1✔
228
    }
229

230
    @Override
231
    protected void handlerAdded0(final ChannelHandlerContext ctx) {
232
      final BufferReadsHandler bufferReads = new BufferReadsHandler();
1✔
233
      ctx.pipeline().addBefore(ctx.name(), null, bufferReads);
1✔
234

235
      sslContextProviderSupplier.updateSslContext(
1✔
236
          new SslContextProvider.Callback(ctx.executor()) {
1✔
237

238
            @Override
239
            public void updateSslContextAndExtendedX509TrustManager(
240
                AbstractMap.SimpleImmutableEntry<SslContext, X509TrustManager> sslContextAndTm) {
241
              if (ctx.isRemoved()) {
1✔
242
                return;
1✔
243
              }
244
              logger.log(
1✔
245
                  Level.FINEST,
246
                  "ClientSecurityHandler.updateSslContext authority={0}, ctx.name={1}",
247
                  new Object[]{grpcHandler.getAuthority(), ctx.name()});
1✔
248
              ChannelHandler handler =
1✔
249
                  InternalProtocolNegotiators.tls(
1✔
250
                      sslContextAndTm.getKey(), sni, sslContextAndTm.getValue())
1✔
251
                      .newHandler(grpcHandler);
1✔
252

253
              // Delegate rest of handshake to TLS handler
254
              ctx.pipeline().addAfter(ctx.name(), null, handler);
1✔
255
              fireProtocolNegotiationEvent(ctx);
1✔
256
              ctx.pipeline().remove(bufferReads);
1✔
257
            }
1✔
258

259
            @Override
260
            public void onException(Throwable throwable) {
261
              ctx.fireExceptionCaught(throwable);
×
262
            }
×
263
          }
264
      );
265
    }
1✔
266

267
    @Override
268
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
269
        throws Exception {
270
      logger.log(Level.SEVERE, "exceptionCaught", cause);
×
271
      ctx.fireExceptionCaught(cause);
×
272
    }
×
273
  }
274

275
  private static final class ServerSecurityProtocolNegotiator implements ProtocolNegotiator {
276

277
    @Nullable private final ProtocolNegotiator fallbackProtocolNegotiator;
278

279
    /** Constructor. */
280
    @VisibleForTesting
281
    public ServerSecurityProtocolNegotiator(
282
        @Nullable ProtocolNegotiator fallbackProtocolNegotiator) {
1✔
283
      this.fallbackProtocolNegotiator = fallbackProtocolNegotiator;
1✔
284
    }
1✔
285

286
    @Override
287
    public AsciiString scheme() {
288
      return SCHEME;
×
289
    }
290

291
    @Override
292
    public ChannelHandler newHandler(GrpcHttp2ConnectionHandler grpcHandler) {
293
      return new HandlerPickerHandler(grpcHandler, fallbackProtocolNegotiator);
1✔
294
    }
295

296
    @Override
297
    public void close() {}
×
298
  }
299

300
  @VisibleForTesting
301
  static final class HandlerPickerHandler
302
      extends ChannelInboundHandlerAdapter {
303
    private final GrpcHttp2ConnectionHandler grpcHandler;
304
    @Nullable private final ProtocolNegotiator fallbackProtocolNegotiator;
305

306
    HandlerPickerHandler(
307
        GrpcHttp2ConnectionHandler grpcHandler,
308
        @Nullable ProtocolNegotiator fallbackProtocolNegotiator) {
1✔
309
      this.grpcHandler = checkNotNull(grpcHandler, "grpcHandler");
1✔
310
      this.fallbackProtocolNegotiator = fallbackProtocolNegotiator;
1✔
311
    }
1✔
312

313
    @Override
314
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
315
      if (evt instanceof ProtocolNegotiationEvent) {
1✔
316
        ProtocolNegotiationEvent pne = (ProtocolNegotiationEvent)evt;
1✔
317
        SslContextProviderSupplier sslContextProviderSupplier = InternalProtocolNegotiationEvent
1✔
318
                .getAttributes(pne).get(ATTR_SERVER_SSL_CONTEXT_PROVIDER_SUPPLIER);
1✔
319
        if (sslContextProviderSupplier == null) {
1✔
320
          logger.log(Level.FINE, "No sslContextProviderSupplier found in filterChainMatch "
1✔
321
              + "for connection from {0} to {1}",
322
              new Object[]{ctx.channel().remoteAddress(), ctx.channel().localAddress()});
1✔
323
          if (fallbackProtocolNegotiator == null) {
1✔
324
            ctx.fireExceptionCaught(new CertStoreException("No certificate source found!"));
1✔
325
            return;
1✔
326
          }
327
          logger.log(Level.FINE, "Using fallback credentials for connection from {0} to {1}",
1✔
328
              new Object[]{ctx.channel().remoteAddress(), ctx.channel().localAddress()});
1✔
329
          ctx.pipeline()
1✔
330
              .replace(
1✔
331
                  this,
332
                  null,
333
                  fallbackProtocolNegotiator.newHandler(grpcHandler));
1✔
334
          ctx.fireUserEventTriggered(pne);
1✔
335
          return;
1✔
336
        } else {
337
          ctx.pipeline()
1✔
338
              .replace(
1✔
339
                  this,
340
                  null,
341
                  new ServerSecurityHandler(
342
                      grpcHandler, sslContextProviderSupplier));
343
          ctx.fireUserEventTriggered(pne);
1✔
344
          return;
1✔
345
        }
346
      } else {
347
        super.userEventTriggered(ctx, evt);
×
348
      }
349
    }
×
350
  }
351

352
  @VisibleForTesting
353
  static final class ServerSecurityHandler
354
          extends InternalProtocolNegotiators.ProtocolNegotiationHandler {
355
    private final GrpcHttp2ConnectionHandler grpcHandler;
356
    private final SslContextProviderSupplier sslContextProviderSupplier;
357

358
    ServerSecurityHandler(
359
            GrpcHttp2ConnectionHandler grpcHandler,
360
            SslContextProviderSupplier sslContextProviderSupplier) {
361
      super(
1✔
362
          // superclass (InternalProtocolNegotiators.ProtocolNegotiationHandler) expects 'next'
363
          // handler but we don't have a next handler _yet_. So we "disable" superclass's behavior
364
          // here and then manually add 'next' when we call fireProtocolNegotiationEvent()
365
          new ChannelHandlerAdapter() {
1✔
366
            @Override
367
            public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
368
              ctx.pipeline().remove(this);
1✔
369
            }
1✔
370
          }, grpcHandler.getNegotiationLogger());
1✔
371
      checkNotNull(grpcHandler, "grpcHandler");
1✔
372
      this.grpcHandler = grpcHandler;
1✔
373
      this.sslContextProviderSupplier = sslContextProviderSupplier;
1✔
374
    }
1✔
375

376
    @Override
377
    protected void handlerAdded0(final ChannelHandlerContext ctx) {
378
      final BufferReadsHandler bufferReads = new BufferReadsHandler();
1✔
379
      ctx.pipeline().addBefore(ctx.name(), null, bufferReads);
1✔
380

381
      sslContextProviderSupplier.updateSslContext(
1✔
382
          new SslContextProvider.Callback(ctx.executor()) {
1✔
383

384
            @Override
385
            public void updateSslContextAndExtendedX509TrustManager(
386
                AbstractMap.SimpleImmutableEntry<SslContext, X509TrustManager> sslContextAndTm) {
387
              ChannelHandler handler = InternalProtocolNegotiators.serverTls(
1✔
388
                  sslContextAndTm.getKey()).newHandler(grpcHandler);
1✔
389

390
              // Delegate rest of handshake to TLS handler
391
              if (!ctx.isRemoved()) {
1✔
392
                ctx.pipeline().addAfter(ctx.name(), null, handler);
1✔
393
                fireProtocolNegotiationEvent(ctx);
1✔
394
                ctx.pipeline().remove(bufferReads);
1✔
395
              }
396
            }
1✔
397

398
            @Override
399
            public void onException(Throwable throwable) {
400
              ctx.fireExceptionCaught(throwable);
×
401
            }
×
402
          }
403
      );
404
    }
1✔
405
  }
406
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc