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

grpc / grpc-java / #19126

28 Mar 2024 04:54AM UTC coverage: 88.247% (-0.03%) from 88.274%
#19126

push

github

web-flow
Specify a locale for upper/lower case conversions (1.63.x backport)

None of these conversions should use the arbitrary system locale. Error
Prone will help prevent these getting introduced in the future.

Fixes #10372

31204 of 35360 relevant lines covered (88.25%)

0.88 hits per line

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

88.35
/../xds/src/main/java/io/grpc/xds/internal/security/trust/XdsX509TrustManager.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.checkNotNull;
20

21
import com.google.common.annotations.VisibleForTesting;
22
import com.google.common.base.Strings;
23
import com.google.re2j.Pattern;
24
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
25
import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher;
26
import io.envoyproxy.envoy.type.matcher.v3.StringMatcher;
27
import java.net.Socket;
28
import java.security.cert.CertificateException;
29
import java.security.cert.CertificateParsingException;
30
import java.security.cert.X509Certificate;
31
import java.util.Collection;
32
import java.util.List;
33
import java.util.Locale;
34
import javax.annotation.Nullable;
35
import javax.net.ssl.SSLEngine;
36
import javax.net.ssl.SSLParameters;
37
import javax.net.ssl.SSLSocket;
38
import javax.net.ssl.X509ExtendedTrustManager;
39
import javax.net.ssl.X509TrustManager;
40

41
/**
42
 * Extension of {@link X509ExtendedTrustManager} that implements verification of
43
 * SANs (subject-alternate-names) against the list in CertificateValidationContext.
44
 */
45
final class XdsX509TrustManager extends X509ExtendedTrustManager implements X509TrustManager {
46

47
  // ref: io.grpc.okhttp.internal.OkHostnameVerifier and
48
  // sun.security.x509.GeneralNameInterface
49
  private static final int ALT_DNS_NAME = 2;
50
  private static final int ALT_URI_NAME = 6;
51
  private static final int ALT_IPA_NAME = 7;
52

53
  private final X509ExtendedTrustManager delegate;
54
  private final CertificateValidationContext certContext;
55

56
  XdsX509TrustManager(@Nullable CertificateValidationContext certContext,
57
                      X509ExtendedTrustManager delegate) {
1✔
58
    checkNotNull(delegate, "delegate");
1✔
59
    this.certContext = certContext;
1✔
60
    this.delegate = delegate;
1✔
61
  }
1✔
62

63
  private static boolean verifyDnsNameInPattern(
64
      String altNameFromCert, StringMatcher sanToVerifyMatcher) {
65
    if (Strings.isNullOrEmpty(altNameFromCert)) {
1✔
66
      return false;
×
67
    }
68
    switch (sanToVerifyMatcher.getMatchPatternCase()) {
1✔
69
      case EXACT:
70
        return verifyDnsNameExact(
1✔
71
            altNameFromCert, sanToVerifyMatcher.getExact(), sanToVerifyMatcher.getIgnoreCase());
1✔
72
      case PREFIX:
73
        return verifyDnsNamePrefix(
1✔
74
            altNameFromCert, sanToVerifyMatcher.getPrefix(), sanToVerifyMatcher.getIgnoreCase());
1✔
75
      case SUFFIX:
76
        return verifyDnsNameSuffix(
1✔
77
            altNameFromCert, sanToVerifyMatcher.getSuffix(), sanToVerifyMatcher.getIgnoreCase());
1✔
78
      case CONTAINS:
79
        return verifyDnsNameContains(
1✔
80
            altNameFromCert, sanToVerifyMatcher.getContains(), sanToVerifyMatcher.getIgnoreCase());
1✔
81
      case SAFE_REGEX:
82
        return verifyDnsNameSafeRegex(altNameFromCert, sanToVerifyMatcher.getSafeRegex());
1✔
83
      default:
84
        throw new IllegalArgumentException(
×
85
            "Unknown match-pattern-case " + sanToVerifyMatcher.getMatchPatternCase());
×
86
    }
87
  }
88

89
  private static boolean verifyDnsNameSafeRegex(
90
          String altNameFromCert, RegexMatcher sanToVerifySafeRegex) {
91
    Pattern safeRegExMatch = Pattern.compile(sanToVerifySafeRegex.getRegex());
1✔
92
    return safeRegExMatch.matches(altNameFromCert);
1✔
93
  }
94

95
  private static boolean verifyDnsNamePrefix(
96
      String altNameFromCert, String sanToVerifyPrefix, boolean ignoreCase) {
97
    if (Strings.isNullOrEmpty(sanToVerifyPrefix)) {
1✔
98
      return false;
×
99
    }
100
    return ignoreCase
1✔
101
        ? altNameFromCert.toLowerCase(Locale.ROOT).startsWith(
1✔
102
            sanToVerifyPrefix.toLowerCase(Locale.ROOT))
1✔
103
        : altNameFromCert.startsWith(sanToVerifyPrefix);
1✔
104
  }
105

106
  private static boolean verifyDnsNameSuffix(
107
          String altNameFromCert, String sanToVerifySuffix, boolean ignoreCase) {
108
    if (Strings.isNullOrEmpty(sanToVerifySuffix)) {
1✔
109
      return false;
×
110
    }
111
    return ignoreCase
1✔
112
            ? altNameFromCert.toLowerCase(Locale.ROOT).endsWith(
1✔
113
                sanToVerifySuffix.toLowerCase(Locale.ROOT))
1✔
114
            : altNameFromCert.endsWith(sanToVerifySuffix);
1✔
115
  }
116

117
  private static boolean verifyDnsNameContains(
118
          String altNameFromCert, String sanToVerifySubstring, boolean ignoreCase) {
119
    if (Strings.isNullOrEmpty(sanToVerifySubstring)) {
1✔
120
      return false;
×
121
    }
122
    return ignoreCase
1✔
123
            ? altNameFromCert.toLowerCase(Locale.ROOT).contains(
1✔
124
                sanToVerifySubstring.toLowerCase(Locale.ROOT))
1✔
125
            : altNameFromCert.contains(sanToVerifySubstring);
1✔
126
  }
127

128
  private static boolean verifyDnsNameExact(
129
      String altNameFromCert, String sanToVerifyExact, boolean ignoreCase) {
130
    if (Strings.isNullOrEmpty(sanToVerifyExact)) {
1✔
131
      return false;
×
132
    }
133
    return ignoreCase
1✔
134
        ? sanToVerifyExact.equalsIgnoreCase(altNameFromCert)
1✔
135
        : sanToVerifyExact.equals(altNameFromCert);
1✔
136
  }
137

138
  private static boolean verifyDnsNameInSanList(
139
      String altNameFromCert, List<StringMatcher> verifySanList) {
140
    for (StringMatcher verifySan : verifySanList) {
1✔
141
      if (verifyDnsNameInPattern(altNameFromCert, verifySan)) {
1✔
142
        return true;
1✔
143
      }
144
    }
1✔
145
    return false;
1✔
146
  }
147

148
  private static boolean verifyOneSanInList(List<?> entry, List<StringMatcher> verifySanList)
149
      throws CertificateParsingException {
150
    // from OkHostnameVerifier.getSubjectAltNames
151
    if (entry == null || entry.size() < 2) {
1✔
152
      throw new CertificateParsingException("Invalid SAN entry");
×
153
    }
154
    Integer altNameType = (Integer) entry.get(0);
1✔
155
    if (altNameType == null) {
1✔
156
      throw new CertificateParsingException("Invalid SAN entry: null altNameType");
×
157
    }
158
    switch (altNameType) {
1✔
159
      case ALT_DNS_NAME:
160
      case ALT_URI_NAME:
161
      case ALT_IPA_NAME:
162
        return verifyDnsNameInSanList((String) entry.get(1), verifySanList);
1✔
163
      default:
164
        return false;
1✔
165
    }
166
  }
167

168
  // logic from Envoy::Extensions::TransportSockets::Tls::ContextImpl::verifySubjectAltName
169
  private static void verifySubjectAltNameInLeaf(
170
      X509Certificate cert, List<StringMatcher> verifyList) throws CertificateException {
171
    Collection<List<?>> names = cert.getSubjectAlternativeNames();
1✔
172
    if (names == null || names.isEmpty()) {
1✔
173
      throw new CertificateException("Peer certificate SAN check failed");
1✔
174
    }
175
    for (List<?> name : names) {
1✔
176
      if (verifyOneSanInList(name, verifyList)) {
1✔
177
        return;
1✔
178
      }
179
    }
1✔
180
    // at this point there's no match
181
    throw new CertificateException("Peer certificate SAN check failed");
1✔
182
  }
183

184
  /**
185
   * Verifies SANs in the peer cert chain against verify_subject_alt_name in the certContext.
186
   * This is called from various check*Trusted methods.
187
   */
188
  @VisibleForTesting
189
  void verifySubjectAltNameInChain(X509Certificate[] peerCertChain) throws CertificateException {
190
    if (certContext == null) {
1✔
191
      return;
1✔
192
    }
193
    List<StringMatcher> verifyList = certContext.getMatchSubjectAltNamesList();
1✔
194
    if (verifyList.isEmpty()) {
1✔
195
      return;
1✔
196
    }
197
    if (peerCertChain == null || peerCertChain.length < 1) {
1✔
198
      throw new CertificateException("Peer certificate(s) missing");
1✔
199
    }
200
    // verify SANs only in the top cert (leaf cert)
201
    verifySubjectAltNameInLeaf(peerCertChain[0], verifyList);
1✔
202
  }
1✔
203

204
  @Override
205
  public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket)
206
      throws CertificateException {
207
    delegate.checkClientTrusted(chain, authType, socket);
×
208
    verifySubjectAltNameInChain(chain);
×
209
  }
×
210

211
  @Override
212
  public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine)
213
      throws CertificateException {
214
    delegate.checkClientTrusted(chain, authType, sslEngine);
1✔
215
    verifySubjectAltNameInChain(chain);
1✔
216
  }
1✔
217

218
  @Override
219
  public void checkClientTrusted(X509Certificate[] chain, String authType)
220
      throws CertificateException {
221
    delegate.checkClientTrusted(chain, authType);
1✔
222
    verifySubjectAltNameInChain(chain);
1✔
223
  }
1✔
224

225
  @Override
226
  public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket)
227
      throws CertificateException {
228
    if (socket instanceof SSLSocket) {
1✔
229
      SSLSocket sslSocket = (SSLSocket) socket;
1✔
230
      SSLParameters sslParams = sslSocket.getSSLParameters();
1✔
231
      if (sslParams != null) {
1✔
232
        sslParams.setEndpointIdentificationAlgorithm(null);
1✔
233
        sslSocket.setSSLParameters(sslParams);
1✔
234
      }
235
    }
236
    delegate.checkServerTrusted(chain, authType, socket);
1✔
237
    verifySubjectAltNameInChain(chain);
1✔
238
  }
1✔
239

240
  @Override
241
  public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine)
242
      throws CertificateException {
243
    SSLParameters sslParams = sslEngine.getSSLParameters();
1✔
244
    if (sslParams != null) {
1✔
245
      sslParams.setEndpointIdentificationAlgorithm(null);
1✔
246
      sslEngine.setSSLParameters(sslParams);
1✔
247
    }
248
    delegate.checkServerTrusted(chain, authType, sslEngine);
1✔
249
    verifySubjectAltNameInChain(chain);
1✔
250
  }
1✔
251

252
  @Override
253
  public void checkServerTrusted(X509Certificate[] chain, String authType)
254
      throws CertificateException {
255
    delegate.checkServerTrusted(chain, authType);
1✔
256
    verifySubjectAltNameInChain(chain);
1✔
257
  }
1✔
258

259
  @Override
260
  public X509Certificate[] getAcceptedIssuers() {
261
    return delegate.getAcceptedIssuers();
1✔
262
  }
263
}
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