返回

Android OkHttp 连接 Let's Encrypt 证书问题及解决方法

Android

在 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 的根证书添加到应用的信任库中。