@@ -276,38 +276,23 @@ internal object CertificateVerifier {
276
276
//
277
277
// 2: Likely because of 1, Android requires all issued certificates to have some form of
278
278
// revocation included in their authority information. This doesn't work universally as
279
- // internal CAs managed by companies aren't required to follow this (and generally don't),
280
- // so verifying those certificates would fail .
279
+ // issuing certificates in use may omit authority access information (for example the
280
+ // Let's Encrypt R3 Intermediate Certificate) .
281
281
//
282
- // Revocation checking has two factors:
283
- //
284
- // 1. Is the root CA known (installed in system trust store)?
285
- // 2. Did the server staple an OSCP response for it's own leaf certificate?
286
- //
287
- // Thus the below revocation logic handles four cases:
288
- //
289
- // 1. Known root + OSCP stapled -> Full-chain revocation, no extra network use
290
- // 2. Known root + no OSCP stapled -> Full-chain revocation, with extra network use
291
- // 3. Unknown root + OSCP stapled -> End-entity-only revocation, no extra network use
292
- // 4. Unknown root + no OSCP stapled -> End-entity-only revocation, with extra network use
282
+ // Given these constraints, the best option is to only check revocation information
283
+ // at the end-entity depth. We will prefer OCSP (to use stapled information if possible).
284
+ // If there is no stapled OCSP response, Android may use the network to attempt to fetch
285
+ // one. If OCSP checking fails, it may fall back to fetching CRLs. We allow "soft"
286
+ // failures, for example transient network errors.
293
287
val parameters = PKIXBuilderParameters (keystore, null )
294
288
295
289
val validator = CertPathValidator .getInstance(" PKIX" )
296
290
val revocationChecker = validator.revocationChecker as PKIXRevocationChecker
297
291
298
- // `PKIXRevocationChecker` checks the entire chain by default.
299
- // We allow it to fail if there are network issues.
300
- // If the chain's root is known, use this default setting for full-chain
301
- // revocation (excludes root itself, see below).
302
- // Else, only check revocation status for the end-entity.
303
- revocationChecker.options = if (isKnownRoot(validChain.last())) {
304
- EnumSet .of(PKIXRevocationChecker .Option .SOFT_FAIL )
305
- } else {
306
- EnumSet .of(
307
- PKIXRevocationChecker .Option .SOFT_FAIL ,
308
- PKIXRevocationChecker .Option .ONLY_END_ENTITY
309
- )
310
- }
292
+ revocationChecker.options = EnumSet .of(
293
+ PKIXRevocationChecker .Option .SOFT_FAIL ,
294
+ PKIXRevocationChecker .Option .ONLY_END_ENTITY
295
+ )
311
296
312
297
// Use the OCSP data `rustls` provided, if present.
313
298
// Its expected that the server only sends revocation data for its own leaf certificate.
@@ -329,14 +314,8 @@ internal object CertificateVerifier {
329
314
// - https://developer.android.com/reference/java/security/cert/PKIXRevocationChecker
330
315
parameters.isRevocationEnabled = false
331
316
332
- // Validate the revocation status of all non-root certificates in the chain .
317
+ // Validate the revocation status of the end entity certificate .
333
318
try {
334
- // `checkServerTrusted` always returns a trusted full chain. However, root CAs
335
- // don't have revocation properties so attempting to validate them as such fails.
336
- // To avoid this, always remove the root CA from the chain before validating its
337
- // revocation status. This is identical to the `CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT`
338
- // flag in the Win32 API.
339
- validChain.removeLast()
340
319
validator.validate(certFactory.generateCertPath(validChain), parameters)
341
320
} catch (e: CertPathValidatorException ) {
342
321
return VerificationResult (StatusCode .Revoked , e.toString())
@@ -383,62 +362,4 @@ internal object CertificateVerifier {
383
362
384
363
return String (hexChars)
385
364
}
386
-
387
- // Check if CA root is known or not.
388
- // Known means installed in root CA store, either a preset public CA or a custom one installed by an enterprise.
389
- // Function is public for testing only.
390
- //
391
- // Ref: https://source.chromium.org/chromium/chromium/src/+/main:net/android/java/src/org/chromium/net/X509Util.java;l=351
392
- fun isKnownRoot (root : X509Certificate ): Boolean {
393
- // System keystore and cert directory must be non-null to perform checking
394
- systemKeystore?.let { loadedSystemKeystore ->
395
- systemCertificateDirectory?.let { loadedSystemCertificateDirectory ->
396
-
397
- // Check the in-memory cache first
398
- val key = Pair (root.subjectX500Principal, root.publicKey)
399
- if (systemTrustAnchorCache.contains(key)) {
400
- return true
401
- }
402
-
403
- // System trust anchors are stored under a hash of the principal.
404
- // In case of collisions, append number.
405
- val hash = hashPrincipal(root.subjectX500Principal)
406
- var i = 0
407
- while (true ) {
408
- val alias = " $hash .$i "
409
-
410
- if (! File (loadedSystemCertificateDirectory, alias).exists()) {
411
- break
412
- }
413
-
414
- val anchor = loadedSystemKeystore.getCertificate(" system:$alias " )
415
-
416
- // It's possible for `anchor` to be `null` if the user deleted a trust anchor.
417
- // Continue iterating as there may be further collisions after the deleted anchor.
418
- if (anchor == null ) {
419
- continue
420
- // This should never happen
421
- } else if (anchor !is X509Certificate ) {
422
- // SAFETY: This logs a unique identifier (hash value) only in cases where a file within the
423
- // system's root trust store is not a valid X509 certificate (extremely unlikely error).
424
- // The hash doesn't tell us any sensitive information about the invalid cert or reveal any of
425
- // its contents - it just lets us ID the bad file if a customer is having TLS failure issues.
426
- Log .e(TAG , " anchor is not a certificate, alias: $alias " )
427
- continue
428
- // If subject and public key match, it's a system root.
429
- } else {
430
- if ((root.subjectX500Principal == anchor.subjectX500Principal) && (root.publicKey == anchor.publicKey)) {
431
- systemTrustAnchorCache.add(key)
432
- return true
433
- }
434
- }
435
-
436
- i + = 1
437
- }
438
- }
439
- }
440
-
441
- // Not found in cache or store: non-public
442
- return false
443
- }
444
365
}
0 commit comments