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

grpc / grpc-java / #20094

25 Nov 2025 06:17AM UTC coverage: 88.615% (+0.02%) from 88.594%
#20094

push

github

web-flow
xds: Avoid using empty SNI string that was never sent, for SAN validation (#12532)

35126 of 39639 relevant lines covered (88.61%)

0.89 hits per line

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

89.93
/../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()) {
1✔
223
          if (CertificateUtils.useChannelAuthorityIfNoSniApplicable) {
1✔
224
            sniToUse = grpcHandler.getAuthority();
×
225
          }
226
          autoSniSanValidationDoesNotApply = true;
1✔
227
        } else {
228
          autoSniSanValidationDoesNotApply = false;
1✔
229
        }
230
        sni = sniToUse;
1✔
231
      } else {
1✔
232
        sni = grpcHandler.getAuthority();
1✔
233
        autoSniSanValidationDoesNotApply = false;
1✔
234
      }
235
    }
1✔
236

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

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

247
      sslContextProviderSupplier.updateSslContext(
1✔
248
          new SslContextProvider.Callback(ctx.executor()) {
1✔
249

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

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

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

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

287
  private static final class ServerSecurityProtocolNegotiator implements ProtocolNegotiator {
288

289
    @Nullable private final ProtocolNegotiator fallbackProtocolNegotiator;
290

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

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

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

308
    @Override
309
    public void close() {}
×
310
  }
311

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

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

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

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

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

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

393
      sslContextProviderSupplier.updateSslContext(
1✔
394
          new SslContextProvider.Callback(ctx.executor()) {
1✔
395

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

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

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

© 2026 Coveralls, Inc