记一次解决 golang x509 报错的经过

6,240 阅读4分钟
原文链接: icell.io

前段时间在上线一个 go 语言编写的服务的时候,发现所有的 https 请求全部挂了,报错是 x509 certificate signed by unknown authority, 因为线上环境和测试环境访问的域名不同,所以这个问题在测试环境并没有遇到,而且线上环境一开始是用 elixir 进行访问的,没有出过问题,所以只能先自己尝试解决,这篇文章就是记录一下解决问题的过程。

解决方法

首先呢,当然是先 Google 一下,发现绝大部分人的解决方法都是设置 TLSClientConfig 来跳过 https 证书验证。这显然是直接能够解决问题的,但是这实际上是避开了解决问题的方法,所以还是放弃了这种解决思路直接找出问题根源。

一开始这个服务是使用 elixir 编写的,同样的 https endpoint 没有什么问题,那是不是我 golang 的代码写得有问题呢?就从这一步开始着手,不过很可惜,用代码访问别的 https 的网站都是可以的,就公司线上环境的域名不行,所以就开始想着这问题是不是线上 tls 配置的不对导致的?打开 Chrome,输入域名访问,发现完全任何警告和报错,这说不通啊,如果服务器 tls 配置有问题那为什么别的语言请求就可以,为什么 Chrome 访问没有报错?

好在,还有一个利器可以使用,ssllabs 可以对证书进行分析,看看有没有什么问题,果断用了一把,果然发现有问题,分析过后的得分仅为 B

然后仔细看接下来的报告,里面有一项是直接显示有问题的:

报告里面说到,存在 Chain issues,其中有 COMODO RSA Domain Validation Secure Server CA 这个证书需要额外进行下载。这就很好理解了,肯定是服务器端配置有问题,所以解决这个问题就有两种方法:

  1. 服务器修复一下配置;
  2. 代码里面做一下兼容;

考虑到线上环境对于我来说是个黑盒,所以还是我来改一下,不劳烦更改服务器配置了。更改方法很简单:

  1. 把缺少的证书文件的 public key 存为文件 comodo.crt,然后把它添加到 Docker 容器中的 /usr/local/share/ca-certificates/ 路径下;
  2. 执行 update-ca-certificates 命令;
  3. 大功告成。

这个解决思路就相当于,你说你确实某个证书,那我就把这个证书给你加上,这就不存在问题了。但是知道了解决方法了,为什么会存在这样一个问题呢?Chain issues 到底是个什么问题?

问题根源

首先要了解一个证书链的概念,证书链其实构成了整个证书的信任基础,证书校验方需要对证书链进行正确的校验。我们可以使用 Chrome 看一下 github.com 的证书链:

上图中所展示出来的一个证书链的关系就是 github.com -> DigiCert SHA2 Extended Validation Server CA -> DigiCert High Assurance EV Root CA 构成,一整条链分为三种证书类型,分别是服务器实体证书、中间证书和根证书。根据证书链的校验过程如下:

  1. 获取证书链:客户端/浏览器连接一个 https 服务器,服务器会将完整的证书链返回给客户端/浏览器。
  2. 校验证书链关系:客户端/浏览器会确保除了跟证书之外的每个证书的签发者是它上一级证书的使用者,如果不一致则证书校验失败。
  3. 迭代签名校验:客户端/浏览器从服务器实体证书的上一级证书(假设是 B 证书)获取 public key 来校验服务器实体证书的签名,成功之后从 B 证书的上一级证书(假设是 C 证书)获取 public key 来校验 B 证书的签名,成功之后再继续,依次不断迭代,直到结果为根证书的签发人和使用人一致则校验成功。

而我们遇到的这个问题就是 https 网站部署证书的时候并没有正确地部署证书链,在第一步获取证书链的时候出现了问题。但是对于 Chrome 浏览器或者别的一些开发语言来说,如果服务器返回的证书链不完整,它们会单独对缺失的证书进行补充,但 golang 并没有执行这个操作,导致整个 https 协议握手失败,从而出现了这个问题。