一张证书引发的噱案

2022-03-13 16:41:52 +08:00
 felix021

- 引 -

我也没想到在神策数据这大半年能遇到好几次和证书相关的问题。

- 起 -

2021 年 9 月 3 号,一个新客户接入到我们的 SaaS 系统。在某个环节,我们会给客户发个 HTTPS 请求,没想到竟然遇到了个 SSLHandshakeException:

Caused by: javax.net.ssl.SSLHandshakeException: ... unable to find valid certification path to requested target

在服务器上用 curl 试一把,也报错:

$ curl -v https://some.domain/
CAfile: /etc/pki/tls/certs/ca-bundle.crt
...
curl: (60) Peer's Certificate issuer is not recognized.

但用浏览器打开这个 URL ,却是没问题的,这说明问题应该出在我们的服务器端。

- 析 -

我们知道,HTTPS 是靠证书保证通信安全的;但客户端如何保证服务端给的证书是可信的呢?

由于证书总是由某个证书颁发机构( Certificate issuer ,或 Certificate Authority ,简写成 CA )签发的,如果我们事先将一批可信的证书颁发机构存储在本地,就可以在发起请求的时候判断证书是否可信了。

有时情况会更复杂一些:某些机构不在我们的列表里,但他的证书是由我们信任的某个机构颁发的,我们也认为他是可信的,因此他颁发的证书也是可信的。

于是这就构成了一个信任链,链的末端是「根证书颁发机构」( Root CA ),这些机构通常是国际上公认可靠的大型机构,或者国家权威机关背书的机构。

理解了这点,就可以推测,应当是我们服务器上的机构列表没有及时更新;只要把该客户证书的颁发机构加入本地的列表就应该能解决该问题。

- 解 -

再细看上面 curl 命令的输出,有一行 CAfile: /etc/pki/tls/certs/ca-bundle.crt,这就是 curl 使用到的证书颁发机构列表。

www.baidu.com 为例,我们可以通过如下命令获取客户证书的信任链:

$ openssl s_client -showcerts -servername server -connect www.baidu.com:443 > cacert.pem

在得到的 cacert.pem 中,我们可以看到如下内容(略作简化):

Certificate chain
 0 s:/CN=baidu.com
   i:/CN=GlobalSign Organization Validation CA - SHA256 - G2

-----BEGIN CERTIFICATE-----
MIIKQDCCCSigAwIBAgIMEZhyT2Z0o9Yhv76iMA0GCSqGSIb3DQEBCwUAMGYxCzAJ
...(略)...
n3XcFtwQLBY9Iuqh8Mn7vtiv5k2azdGsYhZcFBCBAeUoRhDC
-----END CERTIFICATE-----

 1 s:/CN=GlobalSign Organization Validation CA - SHA256 - G2
   i:/OU=Root CA/CN=GlobalSign Root CA

-----BEGIN CERTIFICATE-----
MIIEaTCCA1GgAwIBAgILBAAAAAABRE7wQkcwDQYJKoZIhvcNAQELBQAwVzELMAkG
...(略)...
K1pp74P1S8SqtCr4fKGxhZSM9AyHDPSsQPhZSZg=
-----END CERTIFICATE-----

...(略)...

可以看到里面有两段用 --BEGIN CERTIFICATE----END CERTIFICATE-- 包起来的 base64 编码字符串,这就是被编码为 PEM 格式( Privacy Enhanced Mail )的证书了(有时也会用 .crt 作为扩展名)。

在 BEGIN 前面有一些摘要,可以帮助我们了解证书的内容,比如 s:/CN=baidu.com 表示这个证书的主体( s 即 subject )是 baidu.com ( CN 即 common name ),i:/CN=GlobalSign 表示它的颁发机构( i 即 issuer )是 GlobalSign 。

因此可以看到,这个 cacert.pem 实际上包含了两个证书,一个是百度使用的证书,另一个是颁发该证书的 GlobalSign 这个机构( CA )自己的证书。

通过 curl --cacert cacert.pem https://www.baidu.com 我们可以确认,这个信任链能用来验证 www.baidu.com 的证书(实际上我们只需要里面第二个证书,将第一个证书删除,不影响 curl 的执行)。

回到该客户的情况,我们用相同的方法取得客户证书颁发机构的证书,将它放到 /etc/pki/ca-trust/source/anchors/ 目录,执行 update-ca-trust 将其加入到证书列表中,就可以正常使用 curl 命令来请求了。

- 然 -

没有「但是」的文章不是好文章。

curl 正常了,但是我们的 Java 代码依然报错,这说明 java 和 curl 使用了不同的 CA 列表。

问题倒是好解决,简单搜索一下,就了解到 jre 的证书是存放在 $JAVA_HOME/jre/lib/security/cacerts 这个文件里,需要使用专门的 keytool 工具来更新它:

$ keytool -import -trustcacerts -file cacert.pem -alias 证书颁发机构的名称 -keystore $JAVA_HOME/jre/lib/security/cacerts

Enter keystore password:  changeit (这是 jre 自带的默认密码)

Certificate was added to keystore

再次验证,Java 代码就可以正常运行了。

注:如果想要单独验证某个证书,可以这样

$ keytool -genkeypair -alias boguscert -storepass storePassword -keypass secretPassword -keystore keystore -dname "CN=Developer"
$ keytool -delete -alias boguscert -storepass storePassword -keystore emptyStore.keystore
$ keytool -import -trustcacerts -file cacert.pem -alias 机构名称 -keystore keystore
$ java -Djavax.net.ssl.trustStore=keystore -Djavax.net.ssl.trustStorePassword=storePassword -cp $CLASS_PATH CLASS_NAME

- 劫 -

不巧的是,这周又遇到了一个证书信任的问题,这次是客户的环境向我们的服务器发起请求,报了相同的错误。

有了前车之鉴,上面这些命令执行起来可谓得心应手,但是这次却不灵了。

排查过程比较琐碎,也因为陷入思维定势而走了一些弯路,但其实原因很简单,这里就不卖关子了。

这家客户是一家泛金融类的企业,其生产环境的网络安全级别非常高,不仅有严格的外网访问限制,而且针对所有 https 请求都会默认劫持,用一个自签名证书返回错误信息。

经过与客户沟通,将神策数据的域名添加到白名单后,问题得以解决。

- 故事 -

讲完了事故,再讲讲故事。

非对称加密、证书、信任链这一系列发明,构成了现在 web 通信安全的基石,很难想象如果没有这些基础设施,现在互联网还能做些什么。

但是这里隐藏了一个大 bug:我们凭什么相信本地这些证书颁发机构是可信的?

至少有三种情况会打破这个假设:

可能你的电脑 /手机被病毒导入了 CA 证书;或者你自己可能就做过这个事情,比如公司网管要求添加公司的自签名证书,又或者你为了能使用 Charles 来抓 https 请求,导入了它自签名的 Root CA 证书。

我没有在公开渠道查到相关的事故(倒是有一个代理商把客户证书的私钥给泄漏了);如果某个机构的私钥泄漏,这家机构应该离倒闭也不远了。

各国政府控制的 CA 机构大概都干过些「不干净」的事情(至少有这种冲动),有一些被发现了,有一些还没有。出于本文的安全考虑,这里就不展开细节了。此外,「不被政府控制」的那些机构,就一定干净么?说到底,机构总是被所在国管辖的,当遇到政府行政命令的时候,不一定有反抗的能力。

综上,理论上并不存在 100% 可靠的通信安全方案。

如果你的应用对通信安全要求非常严格,连本地的 CA 列表都不相信,可以考虑加入更多的手段来提高通信的安全等级。

简单一点的场景(例如 app 不想被抓包破解协议),可以自己校验服务器的证书(证书指纹,或者自己指定证书颁发机构列表);要求更高的场景(例如需要访问内部控制系统),可以给客户端颁发证书,浏览器会在请求时提供证书用于校验,感兴趣的话可以参考 这个不太完善的项目

- 收 -

结尾照例做一个小结:

  1. HTTPS 是基于证书链来保证通信安全的;
  2. 信任的基石是本地的证书颁发机构( CA )列表;
  3. 可以通过向本地列表添加 CA 证书的方式来解决需要信任的证书;
  4. 本地的 CA 不一定都是可信的;
  5. 可以通过更严格的校验,或者客户端证书来加强通信的安全等级。

最后,神策在北京、上海、成都、武汉、深圳等多地均在招聘开发、产品、QA 等岗位,感兴趣的小伙伴欢迎私信勾搭;也可以点击我的 内推链接 查看 JD 并投递简历。

关注公众号,查看更多历史文章

   ▄▄▄▄▄▄▄   ▄      ▄▄▄▄ ▄▄▄▄▄▄▄  
   █ ▄▄▄ █ ▄▀ ▄ ▀██▄ ▀█▄ █ ▄▄▄ █  
   █ ███ █  █  █  █▀▀▀█▀ █ ███ █  
   █▄▄▄▄▄█ ▄ █▀█ █▀█ ▄▀█ █▄▄▄▄▄█  
   ▄▄▄ ▄▄▄▄█  ▀▄█▀▀▀█ ▄█▄▄   ▄    
   ▄█▄▄▄▄▄▀▄▀▄██   ▀ ▄  █▀▄▄▀▄▄█  
   █ █▀▄▀▄▄▀▀█▄▀█▄▀█████▀█▀▀█ █▄  
    ▀▀  █▄██▄█▀  █ ▀█▀ ▀█▀ ▄▀▀▄█  
   █▀ ▀ ▄▄▄▄▄▄▀▄██  █ ▄████▀▀ █▄  
   ▄▀▄▄▄ ▄ ▀▀▄████▀█▀  ▀ █▄▄▄▀▄█  
   ▄▀▀██▄▄  █▀▄▀█▀▀ █▀ ▄▄▄██▀ ▀   
   ▄▄▄▄▄▄▄ █ █▀ ▀▀   ▄██ ▄ █▄▀██  
   █ ▄▄▄ █ █▄ ▀▄▀ ▀██  █▄▄▄█▄  ▀  
   █ ███ █ ▄ ███▀▀▀█▄ █▀▄ ██▄ ▀█  
   █▄▄▄▄▄█ ██ ▄█▀█  █ ▀██▄▄▄  █▄  
8263 次点击
所在节点    推广
56 条回复
felix021
2022-03-13 23:07:32 +08:00
@hxndg 是的,确实写得有点碎,前面说了,主要是记录自己的一些操作备查,加上一些觉得可能有用的基础知识串起来。

「不适合发长文、基础知识」是 25 楼的观点,我怀疑 30 楼这位同学可能是在说反话~
shanliang
2022-03-13 23:58:39 +08:00
这什么客户用沃通的证书,不是来搞笑的吗
felix021
2022-03-14 00:42:25 +08:00
@shanliang 客户用的不是 wosign ,我在原文没有提到;只是 23 楼提了 wosign 的一个 bad case 。
ryd994
2022-03-14 02:28:04 +08:00
@felix021 #27 这依然不可以接受。除非你们有专业人士能复制审核要加入的中级证书。中级证书不需要加到 CA 列表,只有 CA 才能加。楼上已经说过了,是对方服务器没有正确配置 chaining ,正常情况下是服务器会把除了 CA 的两个证书都发过来。
你的服务器的安全运作全都可能依赖 ca-bundle (虽然 Linux 通常还有 gpg 签名,但问题是你不知道还有什么别的程序也用到了)

这不是一个运维问题,这是一个安全问题,影响的不仅仅是这个客户,还有你平台的安全和其他客户的安全。。

解决办法我也说过了,给客户请求单独指定 ca 文件,不可以动操作系统的 CA 。
ryd994
2022-03-14 02:42:33 +08:00
浏览器获取中级证书不需要额外花费时间,是服务器在 TLS 握手时,应该发来证书链上除了 CA 的所有证书。CA 不需要因为 CA 发了也没用。
@whitehack #31 这些也是商业公司。可以选择不和任何人做生意也不给任何人提供服务。但是,这不能成为楼主说的签发假证书的理由。

中国有 wosign 啊,那事后来也就被 ban 了一年。wosign 的资质还是在的。但是这事无论你有没有 CA 你都一样完蛋了。
除了 CA 还要有证书透明度服务。这也是西方的哦。
也就是说你唯一选择就是自建全套服务。原理上来说并不难。实际操作上嘛,非常时间非常手段,审计跳过就好了。反正都是要改配置(就算不改 CA 也要禁用透明度要求)。放飞自我以后自签也不是什么问题嘛。你看 12306 还不是用自签用了很久。
kingfalse
2022-03-14 08:12:51 +08:00
直接忽略证书校验就可以,不必要水出这么大一片水文,真的
kingfalse
2022-03-14 08:20:29 +08:00
@felix021 分享了个广告?
Livid
2022-03-14 10:06:01 +08:00
@maleclub 谢谢。这个主题已经被移动到 /go/promotions
ihciah
2022-03-14 10:15:21 +08:00
楼主不是字节的吗?
felix021
2022-03-14 10:35:44 +08:00
@ihciah 换了大半年了
shenqi
2022-03-14 10:37:49 +08:00
将一句话的事情写成一篇文章,专家啊。
felix021
2022-03-14 10:38:08 +08:00
@kingfalse 我这写了半天还被喷技术差,您这句话要是让楼上几位看见了,估计更惨
kevinlexming
2022-03-14 11:27:08 +08:00
一石三鸟
libotony
2022-03-14 12:08:38 +08:00
这个问题好像是某个 CA 的根证书过期了,浏览器或者操作系统在更新中把新的 CA 证书给加进去了并且做了重定向,但是 curl 或者某些编程语言所依赖的证书库并没有更新,直接信任 CA 是一个非常粗暴的解决方案。正确的方式是找到根本原因,本例中应该交叉对比 openssl 获取的证书链和浏览器的证书链是不是一致,不然别人去请求你的客户的 AP I 还会出错。如果猜的没错的话,证书更新一下就好了。
xloger
2022-03-14 13:17:58 +08:00
楼主我倒是关注挺久了,大部分帖子内容都看过且比较有意思。而且招人部分每次都会被说......
这之间的界限不好区分,但对我来说能接受。
Immortal
2022-03-14 15:20:54 +08:00
看到 id 才发现是 rss 订阅里的老哥 之前写的文章挺有趣

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://tanronggui.xyz/t/840034

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX