很多时候,公司内部会使用私有证书。我们把私有根证书下载到本地并信任,就可以访问公司内的 HTTPS 域名了。

那具体一点说,我们访问 HTTPS 域名的时候,网站会把域名的证书推送给我们(一般来说是浏览器,或者是 curl 命令等),这个证书里面包含了 CN 以及公钥等信息。同时还包含证书链 – 中间证书直到根证书。链上的证书的信息是不完整的,比如说,根证书里面可能有 CN 信息,但没有公钥数据。

那么问题来了,我怎么知道这个证书需要使用哪个根证书去验证呢?通过对比 CN 吗?还是别的什么字段?

如果你知道这个答案,可以不用看下去了。如果不知道,也可以直接翻到最后一节看。

不好!出问题了!

突然接到隔壁组同事的电话,说他们访问我这边的一个应用,app.corp.com,报了证书过期的错误。

我第一反应是,这个域名大概在半年前证书过期了,导致了一次事故。难不成是又过期了?

马上打开电脑,打开网站,正常访问,查看证书信息,也没看到什么异常。(注:其实应该是有异常的,可能是 Edge 浏览器忽略了这个异常?后面换浏览器打不开网站 。)

但同事给的报错截图上面写的清清楚楚,“证书过期”。

然后登陆到一台服务器上面,curl -v https://app.corp.com,发现果然有问题。上面显示 issuer’s certificate 过期。

我们的域名证书是由根证书直接签发的,难道说根证书过期了?

一直以来的理解都不对

一方面庆幸,还好不是我的锅了,另外一方面又想,如果根证书过期,事情就搞大了吧?

因为一直以来我对根证书的理解是这样的:

开天辟地之时,公司先创建一个私钥和公钥,然后拿私钥公钥做出来一个根证书 CA。这个 CA 呢,一方面用来签发其他子域名的证书,另外一方面,需要分发到每一台服务器以及员工的办公电脑上面。

按我这个理解的话,如果根证书过期了。就需要拿私钥公钥再重新做一个根证书,然后给每个子域名再签发证书,同时还要把新的根证书分发到每一台服务器以及员工办公电脑上。

这个工作量有些大。

并非如此

我们联系了 SRE 的同事。他们说是需要下载一个新的根证书,并更新到出问题的服务器,也就是调用我们服务的那个同事的服务器上,就可以了。(并不需要什么再签发子域名的证书。)

测下来也的确如此。

那么问题就来了,app.corp.com 传过来的证书,是怎么对应到新下载的根证书的呢?

Golang 代码答疑

在网上搜索了一下,并没有找到答案(可能是我的关键字给的不对)。不过一切都在代码里,去翻翻看代码吧。

Golang 的 tls 库里面有一个代码示例 https://pkg.go.dev/crypto/tls#example-Dial。从这里 debug 进去,仔细查看,终于明白了这其中机制。

简单来说,golang 会遍历所有的根证书,看看用根证书能不能解码 app.corp.com 的加密信息。如果可以,那就是我们要找的根证书。然后再看这个根证书是不是过期啊,等等。

再稍微多说一些细节呢。

  1. golang 会首先会根证书做一个排序,优先验证更可能的根证书,比如说 CN 一样的。
  2. golang 不会验证所有的根证书,这里面有一个硬编码,最多验证100个。
  3. golang 验证到合法根证书之后,不会停下来,会尝试拿所有的证书链信息做后续的使用(这应该是一个 golang 可以改进的点)。黑盒测试的结果,curl 会停下来。也就是说,如果我们电脑上没有合法的根证书,客户端在根证书验证这里耗时会增加不少。