最近在配置 Android 客户端的 Https 时, 发现运维同事给我的公钥证书的有效期只有 3 个月, 如果直接使用这个证书对 Https 进行配置, 那么三个月以后, 线上的 App 就会因为证书过期, 造成所有 Https 请求失败, 进而无法从服务器获取数据, 这是完全不能接受的
可能这个问题不是很普遍, 其一, 运维同事提供给你的证书可能有效期很长; 其二, 如果给你的是中间证书或者根证书, 这些证书有效期本身就比较长, 但是毕竟我遇到了, 还是准备和大家分享一下, 帮助遇到此坑的同学成功爬坑
文章以问答的形式展开, 总共涉及三个问题因为这块其实涉及的知识面很广, 我还没有完全掌握, 有些地方也只是了解的程度, 所以有说的不对的地方, 还望指正但是按照文章进行配置, 成功解决上面的问题还是没问题的
一为什么 App 需要在本地对 Https 做单独的证书配置?
在 SSL/TLS 握手的过程中, 需要对服务器发送过来的公钥证书进行校验, 判断证书是否有效
按道理, 如果这个证书是一个被广泛认可的 CA 机构签发的证书, 因为该机构的根证书可能已经存储在 Android 系统中, 那么在证书验证的过程中, 通过证书链链接到对应的根证书, 就可以完成验证这种情况不需要自己在 app 本地配置证书
那在什么情况下需要在 App 本地配置证书呢?
目前我遇到的情况有两种:
服务器公钥证书使用的是自签名证书
服务器公钥证书使用的是根证书未被浏览器接受的 CA 机构签发的证书(比如 Let's encrypt)
为什么在这两种情况下需要在 App 本地配置证书呢?
简单来说, 这两种情况如果不在 App 本地配置证书, HTTPS 的 SSL/TLS 握手过程就不会成功, HTTPS 连接就不能够建立那么为什么握手不能成功呢? 因为签发这两种证书的 CA 机构, 其根证书并不在公认的可信任范围内, 所以其根证书并不会默认存储在 Android 系统中在 SSL/TLS 握手过程中, 当服务器将其公钥证书发送给 App 后, 这个公钥证书在证书链验证过程中, 由于不能链接到系统自带的根证书, 从而造成证书校验失败, 进而造成 SSL/TLS 握手失败
所以在上面两种情况下, 就需要在 App 本地配置证书, 协助完成握手过程中的证书校验
二如何配置以及为什么这么配置?
配置其实很简单, 网上有不少文章, 这里就不详细介绍了, 推荐张鸿洋的文章: Android Https 相关完全解析 当 OkHttp 遇到 Https
公钥证书我了解的有两种验证方式一种是证书链校验, 通过该公钥证书链接到系统保存的根证书, 说明证书有效; 另一种是对比 App 本地存储证书的编码与服务器发送证书的编码, 如果一致, 说明证书有效
关于第二种验证方式, 读过源码, 不过因为实在涉及的知识面太广了, 没能整的太明白, 有兴趣的同学可以看看 JDK 中 PKIXValidator 这个类, 它里面有个方法叫
engineValidate()
, 其中有一个
if (trustedCerts.contains(cert)...
语句
综上一和二所述, 自签名证书和小 CA 机构 (其实一点都不小, 有的很大) 签发的证书, 为了验证其有效性, 必须在 App 本地为校验过程提供所需的证书
三解决证书有效期短的问题
按照上面第二个问答的说法, 我们分两种情况进行分析假设 App 本地保存的证书为 A, 服务器在握手阶段发送给 App 的证书为 B, 证书 B 到其根证书链路上的证书为 C
第一种 通过证书编码对比进行证书校验
这种方式下, 如果证书 A 有效期较短, 就会遇到与我一样的问题那么很简单, 我们只要使用一个有效期长一点的证书替换即可
但是问题来了, 编码比对方式下, App 本地保存的证书 A 只能是与服务器在握手阶段发送给 App 的证书 B 相同的证书, 即 A 必须与 B 是同一个证书, 这就限制了可用证书的数量, 如果 B 有效期短, 我们就没有办法将 A 替换为一个有效期长的证书
第二种 通过证书链进行证书校验
这种方式下, 因为只要 App 本地保存的证书是证书 B 到其根证书链路上的任意一个证书, 在 SSL/TLS 握手阶段都能成功完成证书校验, 所以 App 本地保存的证书有很多个可选, 只要找到其中一个有效期够长的即可一般选择中间证书或根证书比较合理
综上所述, 其实只要按照网上配置 Https 证书的文章进行正常配置, 然后选择一个有效期长的证书放到 App 中即可解决问题
四结语
像我们公司使用 CA 机构的是 LetsEncrypt, 按道理只要将 LetsEncrypt 的根证书放到 App 本地即可, 但是实际操作过程中发现网络请求因为没找到根证书失败了, 真是邪门了仔细分析后发现, 因为 LetsEncrypt 的根证书在浏览器中是不被默认支持的, 所以我们的公钥证书并不是直接使用 LetsEncrypt 的根证书或中间证书 (使用 LetsEncrypt 的根证书进行签名版本) 进行签名的, 而是使用的 LetsEncrypt 颁发的由机构 IdenTrust 交叉签名的中间证书在我把这个中间证书放到 App 中后, 网络请求 ok, 有效期短的问题解决
LetsEncrypt 根证书和中间证书的下载地址: letsencrypt.org/certificate
希望通过这个文章, 能够给第一次做相关工作的朋友提供一些帮助
来源: https://juejin.im/post/5a6dc6125188253dc3323a25