一张证书引发的噱案
source link: https://www.v2ex.com/t/840034
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
我也没想到在神策数据这大半年能遇到好几次和证书相关的问题。
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 代码就可以正常运行了。
注:如果想要单独验证某个证书,可以这样
- (1) 先创建一个空的 keyStore (密码为 storePassword ):
$ keytool -genkeypair -alias boguscert -storepass storePassword -keypass secretPassword -keystore keystore -dname "CN=Developer"
$ keytool -delete -alias boguscert -storepass storePassword -keystore emptyStore.keystore
- (2) 添加证书到该 keyStore:
$ keytool -import -trustcacerts -file cacert.pem -alias 机构名称 -keystore keystore
- (3) 指定 keyStore 启动 java 程序:
$ java -Djavax.net.ssl.trustStore=keystore -Djavax.net.ssl.trustStorePassword=storePassword -cp $CLASS_PATH CLASS_NAME
不巧的是,这周又遇到了一个证书信任的问题,这次是客户的环境向我们的服务器发起请求,报了相同的错误。
有了前车之鉴,上面这些命令执行起来可谓得心应手,但是这次却不灵了。
排查过程比较琐碎,也因为陷入思维定势而走了一些弯路,但其实原因很简单,这里就不卖关子了。
这家客户是一家泛金融类的企业,其生产环境的网络安全级别非常高,不仅有严格的外网访问限制,而且针对所有 https 请求都会默认劫持,用一个自签名证书返回错误信息。
经过与客户沟通,将神策数据的域名添加到白名单后,问题得以解决。
讲完了事故,再讲讲故事。
非对称加密、证书、信任链这一系列发明,构成了现在 web 通信安全的基石,很难想象如果没有这些基础设施,现在互联网还能做些什么。
但是这里隐藏了一个大 bug:我们凭什么相信本地这些证书颁发机构是可信的?
至少有三种情况会打破这个假设:
- 本地 CA 列表被污染
可能你的电脑 /手机被病毒导入了 CA 证书;或者你自己可能就做过这个事情,比如公司网管要求添加公司的自签名证书,又或者你为了能使用 Charles 来抓 https 请求,导入了它自签名的 Root CA 证书。
- 机构的私钥泄漏
我没有在公开渠道查到相关的事故(倒是有一个代理商把客户证书的私钥给泄漏了);如果某个机构的私钥泄漏,这家机构应该离倒闭也不远了。
- 看起来正经的机构也可能不正经
各国政府控制的 CA 机构大概都干过些「不干净」的事情(至少有这种冲动),有一些被发现了,有一些还没有。出于本文的安全考虑,这里就不展开细节了。此外,「不被政府控制」的那些机构,就一定干净么?说到底,机构总是被所在国管辖的,当遇到政府行政命令的时候,不一定有反抗的能力。
综上,理论上并不存在 100% 可靠的通信安全方案。
如果你的应用对通信安全要求非常严格,连本地的 CA 列表都不相信,可以考虑加入更多的手段来提高通信的安全等级。
简单一点的场景(例如 app 不想被抓包破解协议),可以自己校验服务器的证书(证书指纹,或者自己指定证书颁发机构列表);要求更高的场景(例如需要访问内部控制系统),可以给客户端颁发证书,浏览器会在请求时提供证书用于校验,感兴趣的话可以参考 这个不太完善的项目。
结尾照例做一个小结:
- HTTPS 是基于证书链来保证通信安全的;
- 信任的基石是本地的证书颁发机构( CA )列表;
- 可以通过向本地列表添加 CA 证书的方式来解决需要信任的证书;
- 本地的 CA 不一定都是可信的;
- 可以通过更严格的校验,或者客户端证书来加强通信的安全等级。
最后,神策在北京、上海、成都、武汉、深圳等多地均在招聘开发、产品、QA 等岗位,感兴趣的小伙伴欢迎私信勾搭;也可以点击我的 内推链接 查看 JD 并投递简历。
关注公众号,查看更多历史文章
▄▄▄▄▄▄▄ ▄ ▄▄▄▄ ▄▄▄▄▄▄▄
█ ▄▄▄ █ ▄▀ ▄ ▀██▄ ▀█▄ █ ▄▄▄ █
█ ███ █ █ █ █▀▀▀█▀ █ ███ █
█▄▄▄▄▄█ ▄ █▀█ █▀█ ▄▀█ █▄▄▄▄▄█
▄▄▄ ▄▄▄▄█ ▀▄█▀▀▀█ ▄█▄▄ ▄
▄█▄▄▄▄▄▀▄▀▄██ ▀ ▄ █▀▄▄▀▄▄█
█ █▀▄▀▄▄▀▀█▄▀█▄▀█████▀█▀▀█ █▄
▀▀ █▄██▄█▀ █ ▀█▀ ▀█▀ ▄▀▀▄█
█▀ ▀ ▄▄▄▄▄▄▀▄██ █ ▄████▀▀ █▄
▄▀▄▄▄ ▄ ▀▀▄████▀█▀ ▀ █▄▄▄▀▄█
▄▀▀██▄▄ █▀▄▀█▀▀ █▀ ▄▄▄██▀ ▀
▄▄▄▄▄▄▄ █ █▀ ▀▀ ▄██ ▄ █▄▀██
█ ▄▄▄ █ █▄ ▀▄▀ ▀██ █▄▄▄█▄ ▀
█ ███ █ ▄ ███▀▀▀█▄ █▀▄ ██▄ ▀█
█▄▄▄▄▄█ ██ ▄█▀█ █ ▀██▄▄▄ █▄
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK