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

grpc / grpc-java / #20023

21 Oct 2025 04:08PM UTC coverage: 88.596% (+0.03%) from 88.571%
#20023

push

github

web-flow
xds: Introduce flag for fallback to use the xds channel authority if no SNI is determined to be used. (#12422)

This is to allow the previous behavior if needed, and when the xds
channel authority is used as the SNI, it won't be used for the SAN
validation.

34951 of 39450 relevant lines covered (88.6%)

0.89 hits per line

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

89.19
/../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
    private final boolean autoSniSanValidationDoesNotApply;
198

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

235
    @VisibleForTesting
236
    String getSni() {
237
      return sni;
1✔
238
    }
239

240
    @Override
241
    protected void handlerAdded0(final ChannelHandlerContext ctx) {
242
      final BufferReadsHandler bufferReads = new BufferReadsHandler();
1✔
243
      ctx.pipeline().addBefore(ctx.name(), null, bufferReads);
1✔
244

245
      sslContextProviderSupplier.updateSslContext(
1✔
246
          new SslContextProvider.Callback(ctx.executor()) {
1✔
247

248
            @Override
249
            public void updateSslContextAndExtendedX509TrustManager(
250
                AbstractMap.SimpleImmutableEntry<SslContext, X509TrustManager> sslContextAndTm) {
251
              if (ctx.isRemoved()) {
1✔
252
                return;
1✔
253
              }
254
              logger.log(
1✔
255
                  Level.FINEST,
256
                  "ClientSecurityHandler.updateSslContext authority={0}, ctx.name={1}",
257
                  new Object[]{grpcHandler.getAuthority(), ctx.name()});
1✔
258
              ChannelHandler handler =
1✔
259
                  InternalProtocolNegotiators.tls(
1✔
260
                      sslContextAndTm.getKey(), sni, sslContextAndTm.getValue())
1✔
261
                      .newHandler(grpcHandler);
1✔
262

263
              // Delegate rest of handshake to TLS handler
264
              ctx.pipeline().addAfter(ctx.name(), null, handler);
1✔
265
              fireProtocolNegotiationEvent(ctx);
1✔
266
              ctx.pipeline().remove(bufferReads);
1✔
267
            }
1✔
268

269
            @Override
270
            public void onException(Throwable throwable) {
271
              ctx.fireExceptionCaught(throwable);
×
272
            }
×
273
          },
274
          autoSniSanValidationDoesNotApply);
275
    }
1✔
276

277
    @Override
278
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
279
        throws Exception {
280
      logger.log(Level.SEVERE, "exceptionCaught", cause);
×
281
      ctx.fireExceptionCaught(cause);
×
282
    }
×
283
  }
284

285
  private static final class ServerSecurityProtocolNegotiator implements ProtocolNegotiator {
286

287
    @Nullable private final ProtocolNegotiator fallbackProtocolNegotiator;
288

289
    /** Constructor. */
290
    @VisibleForTesting
291
    public ServerSecurityProtocolNegotiator(
292
        @Nullable ProtocolNegotiator fallbackProtocolNegotiator) {
1✔
293
      this.fallbackProtocolNegotiator = fallbackProtocolNegotiator;
1✔
294
    }
1✔
295

296
    @Override
297
    public AsciiString scheme() {
298
      return SCHEME;
×
299
    }
300

301
    @Override
302
    public ChannelHandler newHandler(GrpcHttp2ConnectionHandler grpcHandler) {
303
      return new HandlerPickerHandler(grpcHandler, fallbackProtocolNegotiator);
1✔
304
    }
305

306
    @Override
307
    public void close() {}
×
308
  }
309

310
  @VisibleForTesting
311
  static final class HandlerPickerHandler
312
      extends ChannelInboundHandlerAdapter {
313
    private final GrpcHttp2ConnectionHandler grpcHandler;
314
    @Nullable private final ProtocolNegotiator fallbackProtocolNegotiator;
315

316
    HandlerPickerHandler(
317
        GrpcHttp2ConnectionHandler grpcHandler,
318
        @Nullable ProtocolNegotiator fallbackProtocolNegotiator) {
1✔
319
      this.grpcHandler = checkNotNull(grpcHandler, "grpcHandler");
1✔
320
      this.fallbackProtocolNegotiator = fallbackProtocolNegotiator;
1✔
321
    }
1✔
322

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

362
  @VisibleForTesting
363
  static final class ServerSecurityHandler
364
          extends InternalProtocolNegotiators.ProtocolNegotiationHandler {
365
    private final GrpcHttp2ConnectionHandler grpcHandler;
366
    private final SslContextProviderSupplier sslContextProviderSupplier;
367

368
    ServerSecurityHandler(
369
            GrpcHttp2ConnectionHandler grpcHandler,
370
            SslContextProviderSupplier sslContextProviderSupplier) {
371
      super(
1✔
372
          // superclass (InternalProtocolNegotiators.ProtocolNegotiationHandler) expects 'next'
373
          // handler but we don't have a next handler _yet_. So we "disable" superclass's behavior
374
          // here and then manually add 'next' when we call fireProtocolNegotiationEvent()
375
          new ChannelHandlerAdapter() {
1✔
376
            @Override
377
            public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
378
              ctx.pipeline().remove(this);
1✔
379
            }
1✔
380
          }, grpcHandler.getNegotiationLogger());
1✔
381
      checkNotNull(grpcHandler, "grpcHandler");
1✔
382
      this.grpcHandler = grpcHandler;
1✔
383
      this.sslContextProviderSupplier = sslContextProviderSupplier;
1✔
384
    }
1✔
385

386
    @Override
387
    protected void handlerAdded0(final ChannelHandlerContext ctx) {
388
      final BufferReadsHandler bufferReads = new BufferReadsHandler();
1✔
389
      ctx.pipeline().addBefore(ctx.name(), null, bufferReads);
1✔
390

391
      sslContextProviderSupplier.updateSslContext(
1✔
392
          new SslContextProvider.Callback(ctx.executor()) {
1✔
393

394
            @Override
395
            public void updateSslContextAndExtendedX509TrustManager(
396
                AbstractMap.SimpleImmutableEntry<SslContext, X509TrustManager> sslContextAndTm) {
397
              ChannelHandler handler = InternalProtocolNegotiators.serverTls(
1✔
398
                  sslContextAndTm.getKey()).newHandler(grpcHandler);
1✔
399

400
              // Delegate rest of handshake to TLS handler
401
              if (!ctx.isRemoved()) {
1✔
402
                ctx.pipeline().addAfter(ctx.name(), null, handler);
1✔
403
                fireProtocolNegotiationEvent(ctx);
1✔
404
                ctx.pipeline().remove(bufferReads);
1✔
405
              }
406
            }
1✔
407

408
            @Override
409
            public void onException(Throwable throwable) {
410
              ctx.fireExceptionCaught(throwable);
×
411
            }
×
412
          },
413
          false);
414
    }
1✔
415
  }
416
}
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