欢迎访问PlayScala社区,转载请注明沐风(joymufeng)
在我们使用Java调用远程接口或是抓取数据时经常会发生以下错误:
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:387)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)
at sun.security.validator.Validator.validate(Validator.java:260)
at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:281)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:136)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1496)
... 27 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141)
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:382)
... 33 more
这个错误表明Java虚拟机在请求远程HTTPS服务器时无法验证证书的有效性,由于担心HTTPS中间人劫持,所以抛出错误警告调用者。可是奇怪的是这个HTTPS链接在浏览器中可以被正确访问,这是为什么呢?别急,听我慢慢道来。
什么是HTTPS/SSL证书?
HTTPS/SSL证书是由权威CA(Certificate Authority)机构颁发,主要用于服务器(应用)数据传输链路加密和身份认证,以及绑定网站域名。主要有EV SSL证书(Extended Validation SSL),OV SSL证书(Organization Validation SSL)和DV SSL证书(Domain Validation SSL)等。不同的证书类型在浏览器标识上会享受不同的待遇,例如EV、OV证书在浏览器地址栏上会显示企业名称,当然也意味着你在向CA机构申请证书时要付更多的钱,走更多的审核流程。通常DV证书就足够用了,审核流程简单,并且很便宜。
我们上面说到HTTPS/SSL证书是由CA机构颁发的,为了方便运营,CA机构下面还有很多的代理商,这些代理商就是所谓的中间证书颁发机构(Intermediate Certificate Authority),一些大的代理商下面还会继续设置代理商。通常我们的证书都是在代理商或者叫中间证书颁发机构那里申请的。
如何验证证书的有效性?
HTTPS/SSL证书是一种非对称加密技术,HTTPS/SSL证书中包含了所有者的一些基本信息和对外公开的公钥,也就是说HTTPS/SSL证书其实就是所有者的一张名片,但是谁能证明这张名片上的信息是否属实呢? 就像是你拿着一个破手镯,到马路上然后逢人就说"我是马云私生子,看,这是他留给我的手镯,上面还有他的签名。",显然不会有人相信你。但是如果马云站出来,一把抢过手镯仔细端详一番后,抱着你痛哭流涕,那么这个手镯确实可以证明你是马云私生子。HTTPS/SSL证书的作用就像这个手镯,需要经过权威机构认证,通过了认证才会得到大家的认可。所以你的证书需要经过你的代理商认证,代理商的证书需要经过父级代理商认证,... ,这样层层认证一直到顶层的CA机构。所以:
HTTPS/SSL证书其实是一个证书链,这条链上的所有证书均合法才能表明证书本身的合法性。
Java的HTTPS/SSL证书错误分析
搞清楚证书链概念后我们再回头看一下本文开头的错误,其实原因很简单。浏览器成功地完成了整条证书链的校验,所以认为证书是合法的;而在Java中未能完成整条证书链的校验,例如无法验证某个中间证书颁发机构的合法性,所以导致最终认证失败。例如域名chatbot.cn的证书链如下:
中间证书COMODO RSA Domain Validation Secure Server CA的指纹为:
33 9c dd 57 cf d5 b1 41 16 9b 61 5f f3 14 28 78 2d 1d a6 39
CA机构COMODO SECURE的指纹为:
af e5 d2 44 a8 d1 19 42 30 ff 47 9f e2 f8 97 bb cd 7a 8c b4
下面我们看下Windows证书管理器中收录的CA证书和中间证书,单击开始-运行,输入certmgr.msc回车,单击菜单栏【操作】-【查找证书】, 输入COMODO,分别搜索“中间证书颁发机构”和“受信任的根证书颁发机构”,结果如下:
该证书的指纹为:
33 9c dd 57 cf d5 b1 41 16 9b 61 5f f3 14 28 78 2d 1d a6 39
该证书指纹为:
af e5 d2 44 a8 d1 19 42 30 ff 47 9f e2 f8 97 bb cd 7a 8c b4
由于在Windows上整条证书链的认证是完整的,所以在浏览器中可以成功验证证书的有效性。
我们再来看一下Java这边,Java拥有自己的keystore(通常位于$JAVA_HOME/lib/security/cacerts)用于存储CA证书和中间证书,双击$JAVA_HOME/bin/javacpl.exe打开Java控制台,单击【安全】- 【管理证书】,选择【系统】选项卡,然后单击证书类型下拉框查看相关证书。其实还有一种更简单的办法查看Java的信任证书列表,执行如下命令:
keytool -keystore "$JAVA_HOME\jre\lib\security\cacerts" -storepass changeit -list
该命令输出结果如下:
C:\Users\Lenovo>keytool -keystore "D:\Software\jdk1.8.0_111\jre\lib\security\cacerts" -storepass changeit -list
密钥库类型: JKS
密钥库提供方: SUN
您的密钥库包含 104 个条目
digicertassuredidrootca, 2008-4-16, trustedCertEntry,
证书指纹 (SHA1): 05:63:B8:63:0D:62:D7:5A:BB:C8:AB:1E:4B:DF:B5:A8:99:B2:4D:43
comodorsaca, 2015-5-12, trustedCertEntry,
证书指纹 (SHA1): AF:E5:D2:44:A8:D1:19:42:30:FF:47:9F:E2:F8:97:BB:CD:7A:8C:B4
thawtepremiumserverca, 2015-5-21, trustedCertEntry,
...
...
在上面的输出中,我们只能找到CA机构COMODO SECURE的指纹(上面的倒数第4行),而找不到中间证书COMODO RSA Domain Validation Secure Server CA的指纹,所以在Java中无法验证整条证书链的有效性,所以导致Java程序在通过HTTPS协议访问chatbot.cn域名时发生证书错误。通常的解决办法是在Http Client端设置忽略证书错误,或是将缺少的中间证书导入Java keystore,详情请Google之。