Let's Encrypt SSL证书失效问题

• 本文约 1605 字,阅读大致需要 4 分钟 | Security | #Let's Encrypt | #SSL | #BoringSSL | #Certificates | #PKI | #OpenSSL | #TLS | #证书

Let’s Encrypt 是我目前使用的证书发证机构。早上 yegle 提到了某个bug,于是研究了一下,这里记一笔,算是留个历史见证吧。

发生了什么

在PKI证书体系中,信任是通过逐级签名以及对这些签名的验证来实现的。对大部分客户端系统来说,这些系统中会存在一个或多个受信的根证书发证机构的证书,其公钥是通过这些系统本身的更新机制派发的。

根证书发证机构的证书通常是自签名的,更新相对来说比较麻烦(它通常依赖于操作系统的在线或离线更新机制),因此这些证书的私钥事关重大,因此人们通常不希望经常更新它们,因而证书发证机构在绝大多数时候并不会使用这些私钥来签署证书。取而代之的是,他们会创建一些称为中级发证机构的证书用来签署日常的证书,这样根证书的私钥可以采用更为严密的方法来保护,例如可以把它们从网络上彻底断开,而中级发证机构的证书则可以以较高的频率进行密钥轮换,借此来避免其私钥暴露导致的风险。

一个新的发证机构进入市场时,其自签名的根证书往往不会被已经在市场上的客户端系统认可,因此这样一来新的发证机构想要获得用户就必须想办法解决能被用户承认这个问题。一旦获得了一定的知名度并证明了自己的可靠性,这些发证机构便可以遵循一定的流程获得主流浏览器或操作系统的认可,并将自己的根证书也加入到它们的受信根证书列表中了。所以,在起步阶段,新的发证机构往往会要求一些已经存在的根证书去对其根证书进行交叉签署,在客户端验证证书有效性时,由于这些根证书在他们看来是一个经过了受信根证书签名的中级证书而不是一个普通的不受信自签名根证书,因此也就不会给出无法验证证书是否有效的提示,而是能够正确地对其有效性进行验证了。

Let’s Encrypt在起步阶段正是采用了这种做法。通俗地说,它的中级发证机构的证书被两家根证书机构同时签名,其一是它自己的ISRG Root,另一个是另一家根证书机构IdenTrust的DST Root X3。初期,由于DST Root X3已经被许多操作系统和浏览器认证过,因此为其普及起到了非常重要的作用。

UTC时间2021年9月29日19:21:40,DST Root X3的根证书过期了。这样一来,这一边的信任链便不再成立。

DST Root X3的过期时间是早已公布了的,过去几年,Let’s Encrypt也大力推广了自己的ISRG Root,因此对比较新的操作系统来说,DST Root X3的根证书过期这件事并不是什么太大的问题,毕竟另外一条信任链仍然可以用来验证证书的有效性。

然而我们都知道凡事都是有代价的。早先版本的OpenSSL中存在一个bug,简而言之,在验证证书时,它会捋出一条潜在的信任链,然后再逐个确认链条上的每一个环节的过期时间。

这样一来,如果本地的根证书存储中包含了已经过期的DST Root X3证书,OpenSSL就有可能找到这条信任链,并在稍后发现该根证书已经过期并不再信任之。这会导致证书无法验证,并导致对应的服务无法访问。

应对方案

首先,更新到最新版本的SSL库是可以完全避免这个问题的。OpenSSL在2016年的1.1.0版本中已经修正了这个问题。当然这个行业你懂的,不出点惊天的问题你永远不知道谁在游泳的时候光着腚,这不今天就退潮了吗?

OpenSSL官方给出了三种替代的应对方案

第一条是删掉已经过期的DST Root X3证书。这个方法比较简单,但部署成本却比较高,因为它需要修改根证书数据库,对于已经在役的系统特别是学名万物互联 (Internet of Things) 的万粪互联 (Internet of Shit) 来说,您这个要求就可能太高了。

第二条是让调用者启用 X509_V_FLAG_TRUSTED_FIRST。这需要修改应用程序;对于第三方应用程序作者来说,这个要求可能也有点高,不过OpenSSL官方已经准备出一个新版本的OpenSSL 1.0.2系列来允许发行商把该选项写死在库里。

第三条则是服务器端可以做的事情:不要提供采用DST Root X3签名的那个中级证书。这个对于自己控制服务器的同学来说更容易实现一些,只需要用 openssl x509 -in chain.pem -noout -text 查看一下证书链,并根据需要删除其中那个交叉签名的证书就可以了。

我猜最终整个行业将采取的措施应该是第一条或第二条,如果你是那个受害的运行服务器的倒霉蛋儿,现在采取第三条应对方案也许也不晚。