为什么HTTPS中证书链不完整,有的客户端访问正常,有的会报错?

背景

某客户接入高防节点,他们通过java客户端发POST请求,传参到源站正常,经过防御节点失败。用浏览器经过节点访问网站,也是正常的。


排查过程

  • curl -k 请求源站,正常;请求节点,正常 (-k 不验证证书)

  • curl 请求源站,正常;请求节点,报如下错误

curl: (60) Peer's Certificate issuer is not recognized.
More details here: http://curl.haxx.se/docs/sslcerts.html

curl performs SSL certificate verification by default, using a "bundle"
 of Certificate Authority (CA) public keys (CA certs). If the default
 bundle file isn't adequate, you can specify an alternate file
 using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
 the bundle, the certificate verification probably failed due to a
 problem with the certificate (it might be expired, or the name might
 not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
 the -k (or --insecure) option.
  • 该报错说明,curl使用本地根证书集合文件/etc/ssl/certs/ca-certificates.crt无法验证节点的证书

  • 检查节点证书,发现客户上传的证书没有包含中间证书(CA),也就是证书链不完整

  • https://certificatechain.io补全中间证书(CA),重新上传

  • 重新发起请求验证,解决


小记

  • 那么问题来了,为什么证书链不完整,浏览器和苹果手机访问正常,安卓手机、java客户端却异常?

要解答这个问题,我们需要先了解客户端是如何获得中间证书的,一般有两种方式:

1、客户端自动下载

  证书包含颁发者机构访问信息:Authority Info Access (如上图),里面就会有颁发者CA证书的下载地址。通过这个URL,就可以获得这个证书的颁发者证书,即中间证书。当证书不包含中间证书的时候,客户端就可以通过这个URL去获得中间证书,从而建立可信链接。
  Windows、IOS和macOS都支持这种证书获取方式,但安卓不支持。java客户端(通常使用HttpURLConnection或者HttpClient)和curl命令一样,调用本地可信的证书列表无法验证,也不支持这种方式,进而无法验证。
  这种方式有个弊端,假如客户端无法访问证书信息里的URL(假如被墙了),那就无法获得中间证书,进而无法建立可信链接。

2、服务器推送
  这种方式就是将中间证书,预先部署在服务器上(把中间证书追加到证书后面,注意不要有空格),服务器在发送证书的同时,将中间证书一起发给客户端。这就确保各类浏览器都能获得完整的证书链,完成验证。

  • 补全CA即可(可以多个CA),根证书就不必要了,加上了反而有性能损失

  • curl 默认使用的密码学库是 NSS,初始化 certpath 目录是/etc/pki/nssdb,而校验证书使用的根证书库可以是 CAfile,也可以是 CApath。


工具网站

  • https://github.com/spatie/ssl-certificate-chain-resolver github项目地址

  • https://certificatechain.io 用于补全中间证书

  • https://www.ssllabs.com 用于检测网站的HTTPS配置

发表新评论