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

如何用KeyChain的X509Certificate替代KeyStore配置OkHttpClient?

用KeyChain证书替换KeyStore配置OkHttpClient的方案

核心逻辑

  • 把KeyChain拿到的证书转成可信证书集合,代替原来从assets加载到KeyStore的证书
  • 自定义X509TrustManager时,得把证书验证逻辑写对,不能只做空实现
  • 给OkHttpClient配置SSL上下文时,要把自定义TrustManager和SocketFactory绑定好

具体代码实现

1. 从KeyChain取证书链

先确保你已经通过KeyChain.choosePrivateKeyAlias拿到了证书的别名,然后直接取证书链:

// 假设已经拿到证书别名certAlias
val certChain = KeyChain.getCertificateChain(context, certAlias)
// 过滤出X509证书(你已经能正确获取,直接用就行)
val trustedCertificates = certChain.filterIsInstance<X509Certificate>()

2. 写靠谱的自定义TrustManager

之前自定义无效大概率是没正确实现验证逻辑,这里给你一个能正常工作的版本:

class KeyChainTrustManager(private val trustedCerts: List<X509Certificate>) : X509TrustManager {
    override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {
        // 如果不需要客户端认证,直接抛异常就行,或者根据你自己的需求改
        throw IllegalArgumentException("Client auth not supported")
    }

    override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
        chain ?: throw IllegalArgumentException("Server cert chain can't be null")
        
        // 先检查服务器证书是否在我们的可信列表里
        val serverCert = chain[0]
        val isTrusted = trustedCerts.any { trustedCert ->
            trustedCert.publicKey == serverCert.publicKey &&
            trustedCert.subjectDN.name == serverCert.subjectDN.name
        }
        
        if (!isTrusted) {
            throw CertificateException("Server cert not trusted")
        }

        // 可选:验证证书链的完整性(如果你的证书有中间证书的话)
        val trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
        trustFactory.init(KeyStore.getInstance(KeyStore.getDefaultType()))
        val defaultTrustManager = trustFactory.trustManagers.first() as X509TrustManager
        try {
            defaultTrustManager.checkServerTrusted(chain, authType)
        } catch (e: CertificateException) {
            throw CertificateException("Server cert chain validation failed", e)
        }
    }

    override fun getAcceptedIssuers(): Array<X509Certificate> {
        return trustedCerts.toTypedArray()
    }
}

3. 配置OkHttpClient

把上面的TrustManager关联到SSL上下文,再给OkHttpClient用上:

fun buildOkHttpClient(context: Context, certAlias: String): OkHttpClient {
    val certChain = KeyChain.getCertificateChain(context, certAlias)
    val trustedCerts = certChain.filterIsInstance<X509Certificate>()
    
    val trustManager = KeyChainTrustManager(trustedCerts)
    
    // 初始化SSL上下文
    val sslContext = SSLContext.getInstance("TLS")
    sslContext.init(null, arrayOf(trustManager), SecureRandom())
    
    return OkHttpClient.Builder()
        .sslSocketFactory(sslContext.socketFactory, trustManager)
        // 如果证书的CN/SAN和请求域名不匹配,临时加这行(生产环境别随便用)
        // .hostnameVerifier { _, _ -> true }
        .build()
}

为啥之前自定义TrustManager没用?你可以排查这几点

  • 验证逻辑写得不对:只空实现了方法,没实际检查服务器证书是否在可信列表里
  • SSLContext没绑定对:初始化SSLContext时没传入自定义的TrustManager,等于白写
  • 主机名验证失败:证书的域名字段和你请求的域名不匹配,哪怕证书可信也会报错
  • 证书链不全:KeyChain.getCertificateChain返回的证书链缺了中间或根证书,导致验证失败

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 04:18:23