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

grpc / grpc-java / #18903

17 Nov 2023 11:04PM UTC coverage: 88.211% (-0.03%) from 88.236%
#18903

push

github

web-flow
util: Remove shutdown subchannels from OD tracking (#10683)

An OutlierDetectionLoadBalancer child load balancer might decided to
shut down any subchannel it is tracking. We need to make sure that those
subchannels are removed from the outlier detection tracker map to avoid
a memory leak.

30342 of 34397 relevant lines covered (88.21%)

0.88 hits per line

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

81.9
/../util/src/main/java/io/grpc/util/AdvancedTlsX509TrustManager.java
1
/*
2
 * Copyright 2021 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.util;
18

19
import io.grpc.ExperimentalApi;
20
import java.io.File;
21
import java.io.FileInputStream;
22
import java.io.IOException;
23
import java.net.Socket;
24
import java.security.GeneralSecurityException;
25
import java.security.KeyStore;
26
import java.security.KeyStoreException;
27
import java.security.NoSuchAlgorithmException;
28
import java.security.cert.CertificateException;
29
import java.security.cert.X509Certificate;
30
import java.util.concurrent.ScheduledExecutorService;
31
import java.util.concurrent.ScheduledFuture;
32
import java.util.concurrent.TimeUnit;
33
import java.util.logging.Level;
34
import java.util.logging.Logger;
35
import javax.net.ssl.SSLEngine;
36
import javax.net.ssl.SSLParameters;
37
import javax.net.ssl.SSLSocket;
38
import javax.net.ssl.TrustManager;
39
import javax.net.ssl.TrustManagerFactory;
40
import javax.net.ssl.X509ExtendedTrustManager;
41
import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
42

43
/**
44
 * AdvancedTlsX509TrustManager is an {@code X509ExtendedTrustManager} that allows users to configure
45
 * advanced TLS features, such as root certificate reloading, peer cert custom verification, etc.
46
 * For Android users: this class is only supported in API level 24 and above.
47
 */
48
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/8024")
49
@IgnoreJRERequirement
50
public final class AdvancedTlsX509TrustManager extends X509ExtendedTrustManager {
51
  private static final Logger log = Logger.getLogger(AdvancedTlsX509TrustManager.class.getName());
1✔
52

53
  private final Verification verification;
54
  private final SslSocketAndEnginePeerVerifier socketAndEnginePeerVerifier;
55

56
  // The delegated trust manager used to perform traditional certificate verification.
57
  private volatile X509ExtendedTrustManager delegateManager = null;
1✔
58

59
  private AdvancedTlsX509TrustManager(Verification verification,
60
      SslSocketAndEnginePeerVerifier socketAndEnginePeerVerifier) throws CertificateException {
1✔
61
    this.verification = verification;
1✔
62
    this.socketAndEnginePeerVerifier = socketAndEnginePeerVerifier;
1✔
63
  }
1✔
64

65
  @Override
66
  public void checkClientTrusted(X509Certificate[] chain, String authType)
67
      throws CertificateException {
68
    throw new CertificateException(
1✔
69
        "Not enough information to validate peer. SSLEngine or Socket required.");
70
  }
71

72
  @Override
73
  public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket)
74
      throws CertificateException {
75
    checkTrusted(chain, authType, null, socket, false);
1✔
76
  }
1✔
77

78
  @Override
79
  public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
80
      throws CertificateException {
81
    checkTrusted(chain, authType, engine, null, false);
1✔
82
  }
1✔
83

84
  @Override
85
  public void checkServerTrusted(X509Certificate[] chain,  String authType, SSLEngine engine)
86
      throws CertificateException {
87
    checkTrusted(chain, authType, engine, null, true);
1✔
88
  }
1✔
89

90
  @Override
91
  public void checkServerTrusted(X509Certificate[] chain, String authType)
92
      throws CertificateException {
93
    throw new CertificateException(
1✔
94
        "Not enough information to validate peer. SSLEngine or Socket required.");
95
  }
96

97
  @Override
98
  public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket)
99
      throws CertificateException {
100
    checkTrusted(chain, authType, null, socket, true);
1✔
101
  }
1✔
102

103
  @Override
104
  public X509Certificate[] getAcceptedIssuers() {
105
    if (this.delegateManager == null) {
1✔
106
      return new X509Certificate[0];
×
107
    }
108
    return this.delegateManager.getAcceptedIssuers();
1✔
109
  }
110

111
  /**
112
   * Uses the default trust certificates stored on user's local system.
113
   * After this is used, functions that will provide new credential
114
   * data(e.g. updateTrustCredentials(), updateTrustCredentialsFromFile()) should not be called.
115
   */
116
  public void useSystemDefaultTrustCerts() throws CertificateException, KeyStoreException,
117
      NoSuchAlgorithmException {
118
    // Passing a null value of KeyStore would make {@code TrustManagerFactory} attempt to use
119
    // system-default trust CA certs.
120
    this.delegateManager = createDelegateTrustManager(null);
1✔
121
  }
1✔
122

123
  /**
124
   * Updates the current cached trust certificates as well as the key store.
125
   *
126
   * @param trustCerts the trust certificates that are going to be used
127
   */
128
  public void updateTrustCredentials(X509Certificate[] trustCerts) throws IOException,
129
      GeneralSecurityException {
130
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
1✔
131
    keyStore.load(null, null);
1✔
132
    int i = 1;
1✔
133
    for (X509Certificate cert: trustCerts) {
1✔
134
      String alias = Integer.toString(i);
1✔
135
      keyStore.setCertificateEntry(alias, cert);
1✔
136
      i++;
1✔
137
    }
138
    X509ExtendedTrustManager newDelegateManager = createDelegateTrustManager(keyStore);
1✔
139
    this.delegateManager = newDelegateManager;
1✔
140
  }
1✔
141

142
  private static X509ExtendedTrustManager createDelegateTrustManager(KeyStore keyStore)
143
      throws CertificateException, KeyStoreException, NoSuchAlgorithmException {
144
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(
1✔
145
        TrustManagerFactory.getDefaultAlgorithm());
1✔
146
    tmf.init(keyStore);
1✔
147
    X509ExtendedTrustManager delegateManager = null;
1✔
148
    TrustManager[] tms = tmf.getTrustManagers();
1✔
149
    // Iterate over the returned trust managers, looking for an instance of X509TrustManager.
150
    // If found, use that as the delegate trust manager.
151
    for (int j = 0; j < tms.length; j++) {
1✔
152
      if (tms[j] instanceof X509ExtendedTrustManager) {
1✔
153
        delegateManager = (X509ExtendedTrustManager) tms[j];
1✔
154
        break;
1✔
155
      }
156
    }
157
    if (delegateManager == null) {
1✔
158
      throw new CertificateException(
×
159
          "Failed to find X509ExtendedTrustManager with default TrustManager algorithm "
160
              + TrustManagerFactory.getDefaultAlgorithm());
×
161
    }
162
    return delegateManager;
1✔
163
  }
164

165
  private void checkTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine,
166
      Socket socket, boolean checkingServer) throws CertificateException {
167
    if (chain == null || chain.length == 0) {
1✔
168
      throw new IllegalArgumentException(
1✔
169
          "Want certificate verification but got null or empty certificates");
170
    }
171
    if (sslEngine == null && socket == null) {
1✔
172
      throw new CertificateException(
×
173
          "Not enough information to validate peer. SSLEngine or Socket required.");
174
    }
175
    if (this.verification != Verification.INSECURELY_SKIP_ALL_VERIFICATION) {
1✔
176
      X509ExtendedTrustManager currentDelegateManager = this.delegateManager;
1✔
177
      if (currentDelegateManager == null) {
1✔
178
        throw new CertificateException("No trust roots configured");
×
179
      }
180
      if (checkingServer) {
1✔
181
        String algorithm = this.verification == Verification.CERTIFICATE_AND_HOST_NAME_VERIFICATION
1✔
182
            ? "HTTPS" : "";
1✔
183
        if (sslEngine != null) {
1✔
184
          SSLParameters sslParams = sslEngine.getSSLParameters();
1✔
185
          sslParams.setEndpointIdentificationAlgorithm(algorithm);
1✔
186
          sslEngine.setSSLParameters(sslParams);
1✔
187
          currentDelegateManager.checkServerTrusted(chain, authType, sslEngine);
1✔
188
        } else {
1✔
189
          if (!(socket instanceof SSLSocket)) {
×
190
            throw new CertificateException("socket is not a type of SSLSocket");
×
191
          }
192
          SSLSocket sslSocket = (SSLSocket)socket;
×
193
          SSLParameters sslParams = sslSocket.getSSLParameters();
×
194
          sslParams.setEndpointIdentificationAlgorithm(algorithm);
×
195
          sslSocket.setSSLParameters(sslParams);
×
196
          currentDelegateManager.checkServerTrusted(chain, authType, sslSocket);
×
197
        }
198
      } else {
1✔
199
        currentDelegateManager.checkClientTrusted(chain, authType, sslEngine);
1✔
200
      }
201
    }
202
    // Perform the additional peer cert check.
203
    if (socketAndEnginePeerVerifier != null) {
1✔
204
      if (sslEngine != null) {
1✔
205
        socketAndEnginePeerVerifier.verifyPeerCertificate(chain, authType, sslEngine);
1✔
206
      } else {
207
        socketAndEnginePeerVerifier.verifyPeerCertificate(chain, authType, socket);
×
208
      }
209
    }
210
  }
1✔
211

212
  /**
213
   * Schedules a {@code ScheduledExecutorService} to read trust certificates from a local file path
214
   * periodically, and update the cached trust certs if there is an update.
215
   *
216
   * @param trustCertFile  the file on disk holding the trust certificates
217
   * @param period the period between successive read-and-update executions
218
   * @param unit the time unit of the initialDelay and period parameters
219
   * @param executor the execute service we use to read and update the credentials
220
   * @return an object that caller should close when the file refreshes are not needed
221
   */
222
  public Closeable updateTrustCredentialsFromFile(File trustCertFile, long period, TimeUnit unit,
223
      ScheduledExecutorService executor) throws IOException, GeneralSecurityException {
224
    long updatedTime = readAndUpdate(trustCertFile, 0);
1✔
225
    if (updatedTime == 0) {
1✔
226
      throw new GeneralSecurityException(
×
227
          "Files were unmodified before their initial update. Probably a bug.");
228
    }
229
    final ScheduledFuture<?> future =
1✔
230
        executor.scheduleWithFixedDelay(
1✔
231
            new LoadFilePathExecution(trustCertFile), period, period, unit);
232
    return new Closeable() {
1✔
233
      @Override public void close() {
234
        future.cancel(false);
1✔
235
      }
1✔
236
    };
237
  }
238

239
  private class LoadFilePathExecution implements Runnable {
240
    File file;
241
    long currentTime;
242

243
    public LoadFilePathExecution(File file) {
1✔
244
      this.file = file;
1✔
245
      this.currentTime = 0;
1✔
246
    }
1✔
247

248
    @Override
249
    public void run() {
250
      try {
251
        this.currentTime = readAndUpdate(this.file, this.currentTime);
×
252
      } catch (IOException | GeneralSecurityException e) {
×
253
        log.log(Level.SEVERE, "Failed refreshing trust CAs from file. Using previous CAs", e);
×
254
      }
×
255
    }
×
256
  }
257

258
  /**
259
   * Updates the trust certificates from a local file path.
260
   *
261
   * @param trustCertFile  the file on disk holding the trust certificates
262
   */
263
  public void updateTrustCredentialsFromFile(File trustCertFile) throws IOException,
264
      GeneralSecurityException {
265
    long updatedTime = readAndUpdate(trustCertFile, 0);
1✔
266
    if (updatedTime == 0) {
1✔
267
      throw new GeneralSecurityException(
×
268
          "Files were unmodified before their initial update. Probably a bug.");
269
    }
270
  }
1✔
271

272
  /**
273
   * Reads the trust certificates specified in the path location, and update the key store if the
274
   * modified time has changed since last read.
275
   *
276
   * @param trustCertFile  the file on disk holding the trust certificates
277
   * @param oldTime the time when the trust file is modified during last execution
278
   * @return oldTime if failed or the modified time is not changed, otherwise the new modified time
279
   */
280
  private long readAndUpdate(File trustCertFile, long oldTime)
281
      throws IOException, GeneralSecurityException {
282
    long newTime = trustCertFile.lastModified();
1✔
283
    if (newTime == oldTime) {
1✔
284
      return oldTime;
×
285
    }
286
    FileInputStream inputStream = new FileInputStream(trustCertFile);
1✔
287
    try {
288
      X509Certificate[] certificates = CertificateUtils.getX509Certificates(inputStream);
1✔
289
      updateTrustCredentials(certificates);
1✔
290
      return newTime;
1✔
291
    } finally {
292
      inputStream.close();
1✔
293
    }
294
  }
295

296
  // Mainly used to avoid throwing IO Exceptions in java.io.Closeable.
297
  public interface Closeable extends java.io.Closeable {
298
    @Override
299
    void close();
300
  }
301

302
  public static Builder newBuilder() {
303
    return new Builder();
1✔
304
  }
305

306
  // The verification mode when authenticating the peer certificate.
307
  public enum Verification {
1✔
308
    // This is the DEFAULT and RECOMMENDED mode for most applications.
309
    // Setting this on the client side will do the certificate and hostname verification, while
310
    // setting this on the server side will only do the certificate verification.
311
    CERTIFICATE_AND_HOST_NAME_VERIFICATION,
1✔
312
    // This SHOULD be chosen only when you know what the implication this will bring, and have a
313
    // basic understanding about TLS.
314
    // It SHOULD be accompanied with proper additional peer identity checks set through
315
    // {@code PeerVerifier}(nit: why this @code not working?). Failing to do so will leave
316
    // applications to MITM attack.
317
    // Also note that this will only take effect if the underlying SDK implementation invokes
318
    // checkClientTrusted/checkServerTrusted with the {@code SSLEngine} parameter while doing
319
    // verification.
320
    // Setting this on either side will only do the certificate verification.
321
    CERTIFICATE_ONLY_VERIFICATION,
1✔
322
    // Setting is very DANGEROUS. Please try to avoid this in a real production environment, unless
323
    // you are a super advanced user intended to re-implement the whole verification logic on your
324
    // own. A secure verification might include:
325
    // 1. proper verification on the peer certificate chain
326
    // 2. proper checks on the identity of the peer certificate
327
    INSECURELY_SKIP_ALL_VERIFICATION,
1✔
328
  }
329

330
  // Additional custom peer verification check.
331
  // It will be used when checkClientTrusted/checkServerTrusted is called with the {@code Socket} or
332
  // the {@code SSLEngine} parameter.
333
  public interface SslSocketAndEnginePeerVerifier {
334
    /**
335
     * Verifies the peer certificate chain. For more information, please refer to
336
     * {@code X509ExtendedTrustManager}.
337
     *
338
     * @param  peerCertChain  the certificate chain sent from the peer
339
     * @param  authType the key exchange algorithm used, e.g. "RSA", "DHE_DSS", etc
340
     * @param  socket the socket used for this connection. This parameter can be null, which
341
     *         indicates that implementations need not check the ssl parameters
342
     */
343
    void verifyPeerCertificate(X509Certificate[] peerCertChain,  String authType, Socket socket)
344
        throws CertificateException;
345

346
    /**
347
     * Verifies the peer certificate chain. For more information, please refer to
348
     * {@code X509ExtendedTrustManager}.
349
     *
350
     * @param  peerCertChain  the certificate chain sent from the peer
351
     * @param  authType the key exchange algorithm used, e.g. "RSA", "DHE_DSS", etc
352
     * @param  engine the engine used for this connection. This parameter can be null, which
353
     *         indicates that implementations need not check the ssl parameters
354
     */
355
    void verifyPeerCertificate(X509Certificate[] peerCertChain, String authType, SSLEngine engine)
356
        throws CertificateException;
357
  }
358

359
  public static final class Builder {
360

361
    private Verification verification = Verification.CERTIFICATE_AND_HOST_NAME_VERIFICATION;
1✔
362
    private SslSocketAndEnginePeerVerifier socketAndEnginePeerVerifier;
363

364
    private Builder() {}
1✔
365

366
    public Builder setVerification(Verification verification) {
367
      this.verification = verification;
1✔
368
      return this;
1✔
369
    }
370

371
    public Builder setSslSocketAndEnginePeerVerifier(SslSocketAndEnginePeerVerifier verifier) {
372
      this.socketAndEnginePeerVerifier = verifier;
1✔
373
      return this;
1✔
374
    }
375

376
    public AdvancedTlsX509TrustManager build() throws CertificateException {
377
      return new AdvancedTlsX509TrustManager(this.verification, this.socketAndEnginePeerVerifier);
1✔
378
    }
379
  }
380
}
381

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