You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android使用LetsEncrypt证书做SSL Pinning报CertPathValidatorException如何解决

SSL Pinning 触发CertPathValidatorException: Trust anchor for certification path not found. 异常解决方案

问题根因

该报错本质是系统证书校验逻辑无法匹配你导入的信任锚,常见触发原因如下:

  • Let's Encrypt 默认返回的证书链为叶子证书 -> R3中间证书 -> ISRG Root X1 -> 过期DST Root CA X3(兼容旧Android设备),你仅导入ISRG Root X1的情况下,系统默认TrustManager会校验到链末端的过期根证书,触发校验失败
  • 本地raw目录存放的ISRG Root X1证书文件损坏、格式错误,或和服务端返回的证书链不匹配
  • FuelManager初始化时机错误,重复初始化导致配置被覆盖

修复步骤

1. 校验本地证书有效性

取出res/raw/isrgrootx1文件,执行以下命令验证证书内容正确:
openssl x509 -in isrgrootx1.pem -text -noout
确认输出的Subject为CN=ISRG Root X1, O=Internet Security Research Group, C=US,有效期到2035年6月4日。

2. 替换为自定义校验逻辑(推荐)

系统默认TrustManager会执行完整证书链校验,会被Let's Encrypt的兼容交叉签名链干扰,直接自定义TrustManager仅校验钉住的证书公钥即可,代码如下:

import java.security.SecureRandom
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.X509TrustManager

fun getPinnedSSLSocketFactory(ctx: Context): SSLSocketFactory {
    val certificateFactory = CertificateFactory.getInstance("X.509")
    // 加载本地钉住的ISRG Root X1证书
    val pinnedCa = certificateFactory.generateCertificate(
        ctx.resources.openRawResource(R.raw.isrgrootx1)
    ) as X509Certificate
    val pinnedPublicKey = pinnedCa.publicKey.encoded

    val customTrustManager = object : X509TrustManager {
        override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) = Unit

        override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
            if (chain.isNullOrEmpty()) throw CertificateException("证书链为空")
            // 遍历服务端返回的证书链,匹配钉住的公钥即可信任
            val matchPinned = chain.any { cert ->
                cert.publicKey.encoded.contentEquals(pinnedPublicKey)
            }
            if (!matchPinned) throw CertificateException("证书未通过Pinning校验")
        }

        override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray()
    }

    return SSLContext.getInstance("TLS").apply {
        init(null, arrayOf(customTrustManager), SecureRandom())
    }.socketFactory
}

3. 修正Fuel初始化逻辑

将Fuel的配置逻辑从Activity的onStart方法迁移到自定义Application类的onCreate方法中,全局仅初始化一次:

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        FuelManager.instance.apply {
            socketFactory = getPinnedSSLSocketFactory(applicationContext)
            // 如有需要可自行配置hostnameVerifier,建议保留默认校验逻辑
        }
    }
}

可选方案

如果你不想修改TrustManager逻辑,也可以将R3中间证书也导入到你自定义的KeyStore中,完整的证书链即可通过系统默认TrustManager的校验。

内容的提问来源于stack exchange,提问作者Alphexo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 06:06:06