Android OkHttp 连接 Let's Encrypt 证书问题及解决方法
2024-10-14 07:24:33
在 Android M(API 级别 23)以及更早的版本中,如果你的应用使用 OkHttp 去连接那些由 Let's Encrypt 颁发证书的网站,你可能会碰到一个叫做 CertPathValidatorException
的异常。有意思的是,这个问题在 Android N(API 级别 24)及之后的版本上就消失了。
这背后的原因是什么呢?我们先来了解一下 Let's Encrypt。它是一个免费、自动化而且开放的证书颁发机构(CA),简单来说,它给网站发放 HTTPS 证书,让网站可以使用安全的 HTTPS 协议。Android 系统本身内置了一套它信任的 CA 证书列表,用来验证网站证书的真伪。但是,在 Android M 以及更早的版本里,系统默认是不信任 Let's Encrypt 的根证书 ISRG Root X1 的。所以,当你的应用想要连接一个使用 Let's Encrypt 证书的网站时,系统就会抛出一个 CertPathValidatorException
的异常,告诉你找不到信任的证书。
之前,由于 Let's Encrypt 的一个交叉签名证书过期,这个问题变得更加严重。Let's Encrypt 已经采取了措施,延长了这个证书的有效期,保证了兼容性。但是,根据 Let's Encrypt 的官方公告,这个交叉签名证书最终还是会在未来的某个时间点过期。
那么,在 Android M 以及更早的版本上,我们该如何解决这个问题呢?
一个办法是手动把 ISRG Root X1 证书添加到你的应用的信任库里。这样做的话,你需要把证书文件打包到你的应用中,然后在代码里加载它。下面是一个简单的例子:
// 加载 ISRG Root X1 证书
InputStream is = context.getAssets().open("isrgrootx1.cer");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate ca = cf.generateCertificate(is);
// 创建信任管理器
KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
// 创建 SSL 上下文
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
// 创建 OkHttpClient 并设置 SSL 上下文
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager)tmf.getTrustManagers()[0])
.build();
// ... 使用 OkHttpClient 发起请求
虽然这种方法可以解决问题,但是它也有一些不足之处。首先,你需要手动管理证书,如果证书更新了,你就需要重新打包你的应用。其次,它可能会让你的应用体积变大。
另一种方法是使用网络安全配置(Network Security Configuration)。这种方法让你可以在一个 XML 文件里定义应用的网络安全策略,包括自定义信任的证书。下面是一个例子:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="@raw/isrgrootx1" />
</trust-anchors>
</base-config>
</network-security-config>
在这个例子中,我们定义了一个叫做 network_security_config.xml
的文件,然后把 ISRG Root X1 证书作为信任的证书添加进去。证书文件 isrgrootx1.cer
应该放在应用的 res/raw
目录下。
想要使用网络安全配置,你需要在应用的 AndroidManifest.xml
文件里添加以下配置:
<application
...
android:networkSecurityConfig="@xml/network_security_config">
...
</application>
相比手动管理证书,这种方法更方便一些,因为它不需要你修改代码,只需要更新 XML 文件就可以了。
需要注意的是,网络安全配置只在 Android N 及之后的版本上生效。在 Android M 以及更早的版本上,你仍然需要手动管理证书。
总而言之,在 Android M 及更早的版本上,如果你的应用使用 OkHttp 连接 Let's Encrypt 颁发证书的网站,你可能会遇到 CertPathValidatorException
异常。你可以通过手动管理证书或者使用网络安全配置来解决这个问题。选择哪种方法取决于你的应用需求以及你的个人喜好。
希望这篇文章能够帮助你理解和解决这个问题。
常见问题及其解答
1. CertPathValidatorException
异常到底是什么?
CertPathValidatorException
异常是 Android 系统在验证网站证书时抛出的一个异常,表示证书路径验证失败。这通常是因为系统无法找到信任的证书锚点,或者证书链中存在问题。
2. 除了 Let's Encrypt,还有其他 CA 机构的证书可能会导致这个问题吗?
是的,如果 Android 系统不信任某个 CA 机构的根证书,那么连接使用该 CA 机构颁发的证书的网站时,也可能会出现 CertPathValidatorException
异常。
3. 手动管理证书和使用网络安全配置,哪种方法更好?
这两种方法各有优缺点。手动管理证书更加灵活,可以针对特定情况进行配置,但需要开发者手动维护证书。网络安全配置更加方便,不需要修改代码,但只在 Android N 及之后的版本上生效。开发者可以根据自己的需求和偏好选择合适的方法。
4. 如果我的应用需要支持 Android M 及更早的版本,我应该怎么做?
如果你的应用需要支持 Android M 及更早的版本,你必须手动管理证书,并将 ISRG Root X1 证书添加到应用的信任库中。
5. 未来 Let's Encrypt 的交叉签名证书过期后,会发生什么?
当 Let's Encrypt 的交叉签名证书过期后,Android M 及更早的版本将无法验证 Let's Encrypt 颁发的证书,除非开发者手动将 Let's Encrypt 的根证书添加到应用的信任库中。