• 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

89.55
/../xds/src/main/java/io/grpc/xds/internal/security/trust/XdsTrustManagerFactory.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.trust;
18

19
import static com.google.common.base.Preconditions.checkArgument;
20
import static com.google.common.base.Preconditions.checkNotNull;
21
import static com.google.common.base.Preconditions.checkState;
22

23
import com.google.common.annotations.VisibleForTesting;
24
import com.google.common.base.Strings;
25
import io.envoyproxy.envoy.config.core.v3.DataSource.SpecifierCase;
26
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
27
import io.netty.handler.ssl.util.SimpleTrustManagerFactory;
28
import java.io.File;
29
import java.io.IOException;
30
import java.io.InputStream;
31
import java.security.KeyStore;
32
import java.security.KeyStoreException;
33
import java.security.NoSuchAlgorithmException;
34
import java.security.cert.CertStoreException;
35
import java.security.cert.CertificateException;
36
import java.security.cert.X509Certificate;
37
import java.util.HashMap;
38
import java.util.List;
39
import java.util.Map;
40
import java.util.logging.Level;
41
import java.util.logging.Logger;
42
import javax.net.ssl.ManagerFactoryParameters;
43
import javax.net.ssl.TrustManager;
44
import javax.net.ssl.TrustManagerFactory;
45
import javax.net.ssl.X509ExtendedTrustManager;
46

47
/**
48
 * Factory class used to provide a {@link XdsX509TrustManager} for trust and SAN checks.
49
 */
50
public final class XdsTrustManagerFactory extends SimpleTrustManagerFactory {
51

52
  private static final Logger logger = Logger.getLogger(XdsTrustManagerFactory.class.getName());
1✔
53
  private XdsX509TrustManager xdsX509TrustManager;
54

55
  /** Constructor constructs from a {@link CertificateValidationContext}. */
56
  public XdsTrustManagerFactory(CertificateValidationContext certificateValidationContext)
57
      throws CertificateException, IOException, CertStoreException {
58
    this(
1✔
59
        getTrustedCaFromCertContext(certificateValidationContext),
1✔
60
        certificateValidationContext,
61
        false,
62
        false);
63
  }
1✔
64

65
  public XdsTrustManagerFactory(
66
          X509Certificate[] certs, CertificateValidationContext staticCertificateValidationContext,
67
          boolean autoSniSanValidation) throws CertStoreException {
68
    this(certs, staticCertificateValidationContext, true, autoSniSanValidation);
1✔
69
  }
1✔
70

71
  public XdsTrustManagerFactory(Map<String, List<X509Certificate>> spiffeTrustMap,
72
      CertificateValidationContext staticCertificateValidationContext, boolean autoSniSanValidation)
73
      throws CertStoreException {
74
    this(spiffeTrustMap, staticCertificateValidationContext, true, autoSniSanValidation);
1✔
75
  }
1✔
76

77
  private XdsTrustManagerFactory(
78
      X509Certificate[] certs,
79
      CertificateValidationContext certificateValidationContext,
80
      boolean validationContextIsStatic,
81
      boolean autoSniSanValidation)
82
      throws CertStoreException {
1✔
83
    if (validationContextIsStatic) {
1✔
84
      checkArgument(
1✔
85
          certificateValidationContext == null || !certificateValidationContext.hasTrustedCa()
1✔
86
          || certificateValidationContext.hasSystemRootCerts(),
1✔
87
          "only static certificateValidationContext expected");
88
    }
89
    xdsX509TrustManager = createX509TrustManager(
1✔
90
        certs, certificateValidationContext, autoSniSanValidation);
91
  }
1✔
92

93
  private XdsTrustManagerFactory(
94
      Map<String, List<X509Certificate>> spiffeTrustMap,
95
      CertificateValidationContext certificateValidationContext,
96
      boolean validationContextIsStatic,
97
      boolean autoSniSanValidation)
98
      throws CertStoreException {
1✔
99
    if (validationContextIsStatic) {
1✔
100
      checkArgument(
1✔
101
          certificateValidationContext == null || !certificateValidationContext.hasTrustedCa(),
1✔
102
          "only static certificateValidationContext expected");
103
      xdsX509TrustManager = createX509TrustManager(
1✔
104
          spiffeTrustMap, certificateValidationContext, autoSniSanValidation);
105
    }
106
  }
1✔
107

108
  private static X509Certificate[] getTrustedCaFromCertContext(
109
      CertificateValidationContext certificateValidationContext)
110
      throws CertificateException, IOException {
111
    final SpecifierCase specifierCase =
1✔
112
        certificateValidationContext.getTrustedCa().getSpecifierCase();
1✔
113
    if (specifierCase == SpecifierCase.FILENAME) {
1✔
114
      String certsFile = certificateValidationContext.getTrustedCa().getFilename();
1✔
115
      checkState(
1✔
116
          !Strings.isNullOrEmpty(certsFile),
1✔
117
          "trustedCa.file-name in certificateValidationContext cannot be empty");
118
      return CertificateUtils.toX509Certificates(new File(certsFile));
1✔
119
    } else if (specifierCase == SpecifierCase.INLINE_BYTES) {
1✔
120
      try (InputStream is =
1✔
121
          certificateValidationContext.getTrustedCa().getInlineBytes().newInput()) {
1✔
122
        return CertificateUtils.toX509Certificates(is);
1✔
123
      }
124
    } else {
125
      throw new IllegalArgumentException("Not supported: " + specifierCase);
×
126
    }
127
  }
128

129
  @VisibleForTesting
130
  static XdsX509TrustManager createX509TrustManager(
131
      X509Certificate[] certs, CertificateValidationContext certContext,
132
      boolean autoSniSanValidation)
133
      throws CertStoreException {
134
    return new XdsX509TrustManager(certContext, createTrustManager(certs), autoSniSanValidation);
1✔
135
  }
136

137
  @VisibleForTesting
138
  static XdsX509TrustManager createX509TrustManager(
139
      Map<String, List<X509Certificate>> spiffeTrustMapFile,
140
      CertificateValidationContext certContext, boolean autoSniSanValidation)
141
      throws CertStoreException {
142
    checkNotNull(spiffeTrustMapFile, "spiffeTrustMapFile");
1✔
143
    Map<String, X509ExtendedTrustManager> delegates = new HashMap<>();
1✔
144
    for (Map.Entry<String, List<X509Certificate>> entry:spiffeTrustMapFile.entrySet()) {
1✔
145
      delegates.put(entry.getKey(), createTrustManager(
1✔
146
          entry.getValue().toArray(new X509Certificate[0])));
1✔
147
    }
1✔
148
    return new XdsX509TrustManager(certContext, delegates, autoSniSanValidation);
1✔
149
  }
150

151
  private static X509ExtendedTrustManager createTrustManager(X509Certificate[] certs)
152
      throws CertStoreException {
153
    TrustManagerFactory tmf = null;
1✔
154
    try {
155
      tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
1✔
156
      KeyStore ks = KeyStore.getInstance("PKCS12");
1✔
157
      // perform a load to initialize KeyStore
158
      ks.load(/* stream= */ null, /* password= */ null);
1✔
159
      int i = 1;
1✔
160
      for (X509Certificate cert : certs) {
1✔
161
        // note: alias lookup uses toLowerCase(Locale.ENGLISH)
162
        // so our alias needs to be all lower-case and unique
163
        ks.setCertificateEntry("alias" + i, cert);
1✔
164
        i++;
1✔
165
      }
166
      tmf.init(ks);
1✔
167
    } catch (NoSuchAlgorithmException | KeyStoreException | IOException | CertificateException e) {
×
168
      logger.log(Level.SEVERE, "createX509TrustManager", e);
×
169
      throw new CertStoreException(e);
×
170
    }
1✔
171
    TrustManager[] tms = tmf.getTrustManagers();
1✔
172
    X509ExtendedTrustManager myDelegate = null;
1✔
173
    if (tms != null) {
1✔
174
      for (TrustManager tm : tms) {
1✔
175
        if (tm instanceof X509ExtendedTrustManager) {
1✔
176
          myDelegate = (X509ExtendedTrustManager) tm;
1✔
177
          break;
1✔
178
        }
179
      }
180
    }
181
    if (myDelegate == null) {
1✔
182
      throw new CertStoreException("Native X509 TrustManager not found.");
×
183
    }
184
    return myDelegate;
1✔
185
  }
186

187
  @Override
188
  protected void engineInit(KeyStore keyStore) throws Exception {
189
    throw new UnsupportedOperationException();
×
190
  }
191

192
  @Override
193
  protected void engineInit(ManagerFactoryParameters managerFactoryParameters) throws Exception {
194
    throw new UnsupportedOperationException();
×
195
  }
196

197
  @Override
198
  protected TrustManager[] engineGetTrustManagers() {
199
    return new TrustManager[] {xdsX509TrustManager};
1✔
200
  }
201
}
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