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

grpc / grpc-java / #19310

25 Jun 2024 06:39PM CUT coverage: 88.417% (-0.04%) from 88.456%
#19310

push

github

web-flow
Start 1.64.2 development cycle (#11319)

32046 of 36244 relevant lines covered (88.42%)

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

© 2025 Coveralls, Inc