@@ -276,38 +276,23 @@ internal object CertificateVerifier {
276276 //
277277 // 2: Likely because of 1, Android requires all issued certificates to have some form of
278278 // 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) .
281281 //
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.
293287 val parameters = PKIXBuilderParameters (keystore, null )
294288
295289 val validator = CertPathValidator .getInstance(" PKIX" )
296290 val revocationChecker = validator.revocationChecker as PKIXRevocationChecker
297291
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+ )
311296
312297 // Use the OCSP data `rustls` provided, if present.
313298 // Its expected that the server only sends revocation data for its own leaf certificate.
@@ -329,14 +314,8 @@ internal object CertificateVerifier {
329314 // - https://developer.android.com/reference/java/security/cert/PKIXRevocationChecker
330315 parameters.isRevocationEnabled = false
331316
332- // Validate the revocation status of all non-root certificates in the chain .
317+ // Validate the revocation status of the end entity certificate .
333318 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()
340319 validator.validate(certFactory.generateCertPath(validChain), parameters)
341320 } catch (e: CertPathValidatorException ) {
342321 return VerificationResult (StatusCode .Revoked , e.toString())
@@ -383,62 +362,4 @@ internal object CertificateVerifier {
383362
384363 return String (hexChars)
385364 }
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- }
444365}
0 commit comments