16
16
17
17
package io .grpc .util ;
18
18
19
- import io .grpc .ExperimentalApi ;
19
+ import static com .google .common .base .Preconditions .checkNotNull ;
20
+
20
21
import java .io .File ;
21
22
import java .io .FileInputStream ;
22
23
import java .io .IOException ;
42
43
43
44
/**
44
45
* 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.
46
+ * advanced TLS features, such as root certificate reloading and peer cert custom verification.
47
+ * The basic instantiation pattern is
48
+ * <code>new Builder().build().useSystemDefaultTrustCerts();</code>
49
+ *
50
+ * <p>For Android users: this class is only supported in API level 24 and above.
47
51
*/
48
- @ ExperimentalApi ("https://github.com/grpc/grpc-java/issues/8024" )
49
52
@ IgnoreJRERequirement
50
53
public final class AdvancedTlsX509TrustManager extends X509ExtendedTrustManager {
51
54
private static final Logger log = Logger .getLogger (AdvancedTlsX509TrustManager .class .getName ());
52
55
56
+ // Minimum allowed period for refreshing files with credential information.
57
+ private static final int MINIMUM_REFRESH_PERIOD_IN_MINUTES = 1 ;
58
+ private static final String NOT_ENOUGH_INFO_MESSAGE =
59
+ "Not enough information to validate peer. SSLEngine or Socket required." ;
53
60
private final Verification verification ;
54
61
private final SslSocketAndEnginePeerVerifier socketAndEnginePeerVerifier ;
55
62
56
63
// The delegated trust manager used to perform traditional certificate verification.
57
64
private volatile X509ExtendedTrustManager delegateManager = null ;
58
65
59
66
private AdvancedTlsX509TrustManager (Verification verification ,
60
- SslSocketAndEnginePeerVerifier socketAndEnginePeerVerifier ) throws CertificateException {
67
+ SslSocketAndEnginePeerVerifier socketAndEnginePeerVerifier ) {
61
68
this .verification = verification ;
62
69
this .socketAndEnginePeerVerifier = socketAndEnginePeerVerifier ;
63
70
}
64
71
65
72
@ Override
66
73
public void checkClientTrusted (X509Certificate [] chain , String authType )
67
74
throws CertificateException {
68
- throw new CertificateException (
69
- "Not enough information to validate peer. SSLEngine or Socket required." );
75
+ throw new CertificateException (NOT_ENOUGH_INFO_MESSAGE );
70
76
}
71
77
72
78
@ Override
@@ -90,8 +96,7 @@ public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEng
90
96
@ Override
91
97
public void checkServerTrusted (X509Certificate [] chain , String authType )
92
98
throws CertificateException {
93
- throw new CertificateException (
94
- "Not enough information to validate peer. SSLEngine or Socket required." );
99
+ throw new CertificateException (NOT_ENOUGH_INFO_MESSAGE );
95
100
}
96
101
97
102
@ Override
@@ -127,6 +132,7 @@ public void useSystemDefaultTrustCerts() throws CertificateException, KeyStoreEx
127
132
*/
128
133
public void updateTrustCredentials (X509Certificate [] trustCerts ) throws IOException ,
129
134
GeneralSecurityException {
135
+ checkNotNull (trustCerts , "trustCerts" );
130
136
KeyStore keyStore = KeyStore .getInstance (KeyStore .getDefaultType ());
131
137
keyStore .load (null , null );
132
138
int i = 1 ;
@@ -135,8 +141,7 @@ public void updateTrustCredentials(X509Certificate[] trustCerts) throws IOExcept
135
141
keyStore .setCertificateEntry (alias , cert );
136
142
i ++;
137
143
}
138
- X509ExtendedTrustManager newDelegateManager = createDelegateTrustManager (keyStore );
139
- this .delegateManager = newDelegateManager ;
144
+ this .delegateManager = createDelegateTrustManager (keyStore );
140
145
}
141
146
142
147
private static X509ExtendedTrustManager createDelegateTrustManager (KeyStore keyStore )
@@ -148,9 +153,9 @@ private static X509ExtendedTrustManager createDelegateTrustManager(KeyStore keyS
148
153
TrustManager [] tms = tmf .getTrustManagers ();
149
154
// Iterate over the returned trust managers, looking for an instance of X509TrustManager.
150
155
// If found, use that as the delegate trust manager.
151
- for (int j = 0 ; j < tms . length ; j ++ ) {
152
- if (tms [ j ] instanceof X509ExtendedTrustManager ) {
153
- delegateManager = (X509ExtendedTrustManager ) tms [ j ] ;
156
+ for (TrustManager tm : tms ) {
157
+ if (tm instanceof X509ExtendedTrustManager ) {
158
+ delegateManager = (X509ExtendedTrustManager ) tm ;
154
159
break ;
155
160
}
156
161
}
@@ -169,8 +174,7 @@ private void checkTrusted(X509Certificate[] chain, String authType, SSLEngine ss
169
174
"Want certificate verification but got null or empty certificates" );
170
175
}
171
176
if (sslEngine == null && socket == null ) {
172
- throw new CertificateException (
173
- "Not enough information to validate peer. SSLEngine or Socket required." );
177
+ throw new CertificateException (NOT_ENOUGH_INFO_MESSAGE );
174
178
}
175
179
if (this .verification != Verification .INSECURELY_SKIP_ALL_VERIFICATION ) {
176
180
X509ExtendedTrustManager currentDelegateManager = this .delegateManager ;
@@ -211,12 +215,18 @@ private void checkTrusted(X509Certificate[] chain, String authType, SSLEngine ss
211
215
212
216
/**
213
217
* 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.
218
+ * periodically, and updates the cached trust certs if there is an update. You must close the
219
+ * returned Closeable before calling this method again or other update methods
220
+ * ({@link AdvancedTlsX509TrustManager#useSystemDefaultTrustCerts()},
221
+ * {@link AdvancedTlsX509TrustManager#updateTrustCredentials(X509Certificate[])},
222
+ * {@link AdvancedTlsX509TrustManager#updateTrustCredentialsFromFile(File)}).
223
+ * Before scheduling the task, the method synchronously reads and updates trust certificates once.
224
+ * If the provided period is less than 1 minute, it is automatically adjusted to 1 minute.
215
225
*
216
226
* @param trustCertFile the file on disk holding the trust certificates
217
227
* @param period the period between successive read-and-update executions
218
228
* @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
229
+ * @param executor the executor service we use to read and update the credentials
220
230
* @return an object that caller should close when the file refreshes are not needed
221
231
*/
222
232
public Closeable updateTrustCredentialsFromFile (File trustCertFile , long period , TimeUnit unit ,
@@ -226,14 +236,17 @@ public Closeable updateTrustCredentialsFromFile(File trustCertFile, long period,
226
236
throw new GeneralSecurityException (
227
237
"Files were unmodified before their initial update. Probably a bug." );
228
238
}
239
+ if (checkNotNull (unit , "unit" ).toMinutes (period ) < MINIMUM_REFRESH_PERIOD_IN_MINUTES ) {
240
+ log .log (Level .FINE ,
241
+ "Provided refresh period of {0} {1} is too small. Default value of {2} minute(s) "
242
+ + "will be used." , new Object [] {period , unit .name (), MINIMUM_REFRESH_PERIOD_IN_MINUTES });
243
+ period = MINIMUM_REFRESH_PERIOD_IN_MINUTES ;
244
+ unit = TimeUnit .MINUTES ;
245
+ }
229
246
final ScheduledFuture <?> future =
230
- executor .scheduleWithFixedDelay (
247
+ checkNotNull ( executor , "executor" ) .scheduleWithFixedDelay (
231
248
new LoadFilePathExecution (trustCertFile ), period , period , unit );
232
- return new Closeable () {
233
- @ Override public void close () {
234
- future .cancel (false );
235
- }
236
- };
249
+ return () -> future .cancel (false );
237
250
}
238
251
239
252
/**
@@ -264,13 +277,14 @@ public void run() {
264
277
try {
265
278
this .currentTime = readAndUpdate (this .file , this .currentTime );
266
279
} catch (IOException | GeneralSecurityException e ) {
267
- log .log (Level .SEVERE , "Failed refreshing trust CAs from file. Using previous CAs" , e );
280
+ log .log (Level .SEVERE , String .format ("Failed refreshing trust CAs from file. Using "
281
+ + "previous CAs (file lastModified = %s)" , file .lastModified ()), e );
268
282
}
269
283
}
270
284
}
271
285
272
286
/**
273
- * Reads the trust certificates specified in the path location, and update the key store if the
287
+ * Reads the trust certificates specified in the path location, and updates the key store if the
274
288
* modified time has changed since last read.
275
289
*
276
290
* @param trustCertFile the file on disk holding the trust certificates
@@ -279,7 +293,7 @@ public void run() {
279
293
*/
280
294
private long readAndUpdate (File trustCertFile , long oldTime )
281
295
throws IOException , GeneralSecurityException {
282
- long newTime = trustCertFile .lastModified ();
296
+ long newTime = checkNotNull ( trustCertFile , "trustCertFile" ) .lastModified ();
283
297
if (newTime == oldTime ) {
284
298
return oldTime ;
285
299
}
@@ -303,27 +317,32 @@ public static Builder newBuilder() {
303
317
return new Builder ();
304
318
}
305
319
306
- // The verification mode when authenticating the peer certificate.
320
+ /**
321
+ * The verification mode when authenticating the peer certificate.
322
+ */
307
323
public enum Verification {
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.
324
+ /**
325
+ * This is the DEFAULT and RECOMMENDED mode for most applications.
326
+ * Setting this on the client side performs both certificate and hostname verification, while
327
+ * setting it on the server side only performs certificate verification.
328
+ */
311
329
CERTIFICATE_AND_HOST_NAME_VERIFICATION ,
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.
330
+ /**
331
+ * DANGEROUS: Use trusted credentials to verify the certificate, but clients will not verify the
332
+ * certificate is for the expected host. This setting is only appropriate when accompanied by
333
+ * proper additional peer identity checks set through SslSocketAndEnginePeerVerifier. Failing to
334
+ * do so will leave your applications vulnerable to MITM attacks.
335
+ * This setting has the same behavior on server-side as CERTIFICATE_AND_HOST_NAME_VERIFICATION.
336
+ */
321
337
CERTIFICATE_ONLY_VERIFICATION ,
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
338
+ /**
339
+ * DANGEROUS: This SHOULD be used by advanced user intended to implement the entire verification
340
+ * logic themselves {@link SslSocketAndEnginePeerVerifier}) themselves. This includes: <br>
341
+ * 1. Proper verification of the peer certificate chain <br>
342
+ * 2. Proper checks of the identity of the peer certificate <br>
343
+ * Failing to do so will leave your application without any TLS-related protection. Keep in mind
344
+ * that any loaded trust certificates will be ignored when using this mode.
345
+ */
327
346
INSECURELY_SKIP_ALL_VERIFICATION ,
328
347
}
329
348
@@ -356,18 +375,41 @@ void verifyPeerCertificate(X509Certificate[] peerCertChain, String authType, SSL
356
375
throws CertificateException ;
357
376
}
358
377
378
+ /**
379
+ * Builds a new {@link AdvancedTlsX509TrustManager}. By default, no trust certificates are loaded
380
+ * after the build. To load them, use one of the following methods: {@link
381
+ * AdvancedTlsX509TrustManager#updateTrustCredentials(X509Certificate[])}, {@link
382
+ * AdvancedTlsX509TrustManager#updateTrustCredentialsFromFile(File, long, TimeUnit,
383
+ * ScheduledExecutorService)}, {@link AdvancedTlsX509TrustManager#updateTrustCredentialsFromFile
384
+ * (File, long, TimeUnit, ScheduledExecutorService)}.
385
+ */
359
386
public static final class Builder {
360
387
361
388
private Verification verification = Verification .CERTIFICATE_AND_HOST_NAME_VERIFICATION ;
362
389
private SslSocketAndEnginePeerVerifier socketAndEnginePeerVerifier ;
363
390
364
391
private Builder () {}
365
392
393
+ /**
394
+ * Sets {@link Verification}, mode when authenticating the peer certificate. By default, {@link
395
+ * Verification#CERTIFICATE_AND_HOST_NAME_VERIFICATION} value is used.
396
+ *
397
+ * @param verification Verification mode used for the current AdvancedTlsX509TrustManager
398
+ * @return Builder with set verification
399
+ */
366
400
public Builder setVerification (Verification verification ) {
367
401
this .verification = verification ;
368
402
return this ;
369
403
}
370
404
405
+ /**
406
+ * Sets {@link SslSocketAndEnginePeerVerifier}, which methods will be called in addition to
407
+ * verifying certificates.
408
+ *
409
+ * @param verifier SslSocketAndEnginePeerVerifier used for the current
410
+ * AdvancedTlsX509TrustManager
411
+ * @return Builder with set verifier
412
+ */
371
413
public Builder setSslSocketAndEnginePeerVerifier (SslSocketAndEnginePeerVerifier verifier ) {
372
414
this .socketAndEnginePeerVerifier = verifier ;
373
415
return this ;
0 commit comments