From f62030ac5b6b4ef9a3247a4d168f91e8a1ebd467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 18 Jun 2026 14:05:54 +0200 Subject: [PATCH] Modify rustls' `CertificateVerifier`, removing the cert revocation logic: This operation is not done by Android by default and doesn't properly support CRLs (Certificate Revocation Lists) which can be a problem with CAs like Let's Encrypt. --- .../platformverifier/CertificateVerifier.kt | 96 ++----------------- 1 file changed, 7 insertions(+), 89 deletions(-) diff --git a/libraries/rustls-tls/src/main/kotlin/org/rustls/platformverifier/CertificateVerifier.kt b/libraries/rustls-tls/src/main/kotlin/org/rustls/platformverifier/CertificateVerifier.kt index 38abed0d92..b70413ffe5 100644 --- a/libraries/rustls-tls/src/main/kotlin/org/rustls/platformverifier/CertificateVerifier.kt +++ b/libraries/rustls-tls/src/main/kotlin/org/rustls/platformverifier/CertificateVerifier.kt @@ -14,7 +14,6 @@ package org.rustls.platformverifier import android.annotation.SuppressLint import android.content.Context import android.net.http.X509TrustManagerExtensions -import android.os.Build import android.util.Log import java.io.ByteArrayInputStream import java.io.File @@ -22,18 +21,13 @@ import java.security.KeyStore import java.security.KeyStoreException import java.security.MessageDigest import java.security.PublicKey -import java.security.cert.CertPathValidator -import java.security.cert.CertPathValidatorException import java.security.cert.CertificateException import java.security.cert.CertificateExpiredException import java.security.cert.CertificateFactory import java.security.cert.CertificateNotYetValidException import java.security.cert.CertificateParsingException -import java.security.cert.PKIXBuilderParameters -import java.security.cert.PKIXRevocationChecker import java.security.cert.X509Certificate import java.util.Date -import java.util.EnumSet import javax.net.ssl.TrustManagerFactory import javax.net.ssl.X509TrustManager import javax.security.auth.x500.X500Principal @@ -298,90 +292,14 @@ internal object CertificateVerifier { return VerificationResult(StatusCode.Ok) } - // Try to check the revocation status of the cert, if it is supported. + // ================================================================================================= + // IMPORTANT! DIFF WITH THE ORIGINAL SOURCES HERE: // - // This is supported at >= API 24, but we're supporting 22 (Android 5) for the best - // compatibility. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - // Note: - // - // 1. Android does not provide any way only to attempt to validate revocation from cached - // data like the other platforms do. This means it will always use the network for - // certificates which had no stapled response. - // - // 2: Likely because of 1, Android requires all issued certificates to have some form of - // revocation included in their authority information. This doesn't work universally as - // issuing certificates in use may omit authority access information (for example the - // Let's Encrypt R3 Intermediate Certificate). - // - // Given these constraints, the best option is to only check revocation information - // at the end-entity depth. We will prefer OCSP (to use stapled information if possible). - // If there is no stapled OCSP response, Android may use the network to attempt to fetch - // one. If OCSP checking fails, it may fall back to fetching CRLs. We allow "soft" - // failures, for example transient network errors. - // - // In the case of a non-public root, such as an internal CA or self-signed certificate, - // we opt to skip revocation checks entirely. The only exception is if the server - // provided stapled OCSP data, which is an explicit signal and won't introduce non-ideal - // platform behavior when attempting validation. - // - // This is because these are cases where a user or administrator has explicitly opted to - // trust a certificate they (at least believe) have control over. These certificates rarely - // contain revocation information as well, so these cases don't lose much. - // See https://github.com/rustls/rustls-platform-verifier/issues/69 as well. - if (ocspResponse == null && !isKnownRoot(validChain.last())) { - // Chain validation must have succeeded by this point. - return VerificationResult(StatusCode.Ok) - } - - val parameters = PKIXBuilderParameters(keystore, null) - - val validator = CertPathValidator.getInstance("PKIX") - val revocationChecker = validator.revocationChecker as PKIXRevocationChecker - - revocationChecker.options = EnumSet.of( - PKIXRevocationChecker.Option.SOFT_FAIL, - PKIXRevocationChecker.Option.ONLY_END_ENTITY - ) - - // Use the OCSP data `rustls` provided, if present. - // Its expected that the server only sends revocation data for its own leaf certificate. - // - // If this field is set, then Android will use it and skip any networking to - // attempt a fetch for that certificate. Otherwise, it will attempt to fetch it from the network. - // Ref: https://cs.android.com/android/platform/superproject/+/master:libcore/ojluni/src/main/java/sun/security/provider/certpath/RevocationChecker.java;l=694 - ocspResponse?.let { providedResponse -> - revocationChecker.ocspResponses = mapOf(endEntity to providedResponse) - } - - // Use the custom revocation definition. - // "Note that when a `PKIXRevocationChecker` is added to `PKIXParameters`, it clones the `PKIXRevocationChecker`; - // thus any subsequent modifications to the `PKIXRevocationChecker` have no effect." - // - https://developer.android.com/reference/java/security/cert/PKIXRevocationChecker - parameters.certPathCheckers = listOf(revocationChecker) - // "When supplying a revocation checker in this manner, it will be used to check revocation - // irrespective of the setting of the `RevocationEnabled` flag." - // - https://developer.android.com/reference/java/security/cert/PKIXRevocationChecker - parameters.isRevocationEnabled = false - - // Validate the revocation status of the end entity certificate. - try { - validator.validate(certFactory.generateCertPath(validChain), parameters) - } catch (e: CertPathValidatorException) { - // LetsEncrypt no longer include OCSP information (as OCSP is being deprecated) which Android is not - // happy with since it *only* tries OCSP by default. We aren't 100% decided on how to fix this yet for real - // (see https://github.com/rustls/rustls-platform-verifier/pull/179) so for now we implement an out for - // tests to allow regular maintenance to proceed. - if (BuildConfig.TEST && e.reason == CertPathValidatorException.BasicReason.UNSPECIFIED) { - return VerificationResult(StatusCode.Ok) - } - - return VerificationResult(StatusCode.Revoked, e.toString()) - } - } else { - // This is allowed to be skipped since revocation checking is best-effort. - Log.w(TAG, "did not attempt to validate OCSP due to Android version") - } + // We removed the cert revocation checks because Android won't do them by default + // and supporting them would be quite difficult given CRLs won't work out of the box. + // + // See https://github.com/rustls/rustls-platform-verifier/issues/221 for more info. + // ================================================================================================= return VerificationResult(StatusCode.Ok) }