一、问题背景
环境中一个java微服务功能异常,看日志报错:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
二、问题解析
本质上,是java在访问https资源时的证书信任问题。如何解决这个问题呢?
为何有这个问题?
解决这个问题前,要了解https通信过程:
1) 客户端在使用HTTPS方式与Web服务器通信时有以下几个步骤,如图所示:
(1)客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。
(2)Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。
(3)客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
(4)客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
(5)Web服务器利用自己的私钥解密出会话密钥。
(6)Web服务器利用会话密钥加密与客户端之间的通信。
2)java程序的证书信任规则
如上文所述,客户端会从服务端拿到证书信息。调用端(客户端)会有一个证书信任列表,拿到证书信息后,会判断该证书是否可信任。
如果是用浏览器访问https资源,发现证书不可信任,一般会弹框告诉用户,对方的证书不可信任,是否继续之类。
Java虚拟机并不直接使用操作系统的keyring,而是有自己的security manager。与操作系统类似,jdk的security manager默认有一堆的根证书信任。
如果你的https站点证书是花钱申请的,被这些根证书所信任,那使用java来访问此https站点会非常方便。但是,如果你的证书是自签的或者不是权威CA签发的,那么用java访问https资源,发现证书不可信任,则会报文章开头说到的错误。
Windows的证书管理
CentOS7.x的证书管理
JRE的证书管理
浏览器的证书管理(Google)
三、解决问题的方法
1)将证书导入到jdk的信任证书中
2)在客户端(调用端)添加逻辑,忽略证书信任问题
从运维角度,此处我们选择将站点的证书导入到jdk的信任证书中
我们以站点 简书 www.jianshu.com 为例
这里证书链上有三条,我们分别导出Base64编码文本
分别导出命名如下
将三个文件上传到jre所在的服务器
# export LANG="en_US.UTF-8"
# keytool -import -file 111.cer -alias 111 -keystore $JAVA_HOME/jre/lib/security/cacerts --storepass "changeit" --noprompt -trustcacerts
# keytool -import -file 222.cer -alias 222 -keystore $JAVA_HOME/jre/lib/security/cacerts --storepass "changeit" --noprompt -trustcacerts
# keytool -import -file 333.cer -alias 333 -keystore $JAVA_HOME/jre/lib/security/cacerts --storepass "changeit" --noprompt -trustcacerts
导入的证书条目,用alias别名唯一区分,注意定义一个有意义的别名
四、JRE 证书库的查看和删除
查看
# keytool -list -rfc -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass "changeit"
# keytool -list -rfc -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass "changeit" | grep -i "alias"
删除
# keytool -delete -alias 111 -keystore $JAVA_HOME/jre/lib/security/cacerts --storepass "changeit"
# keytool -delete -alias 222 -keystore $JAVA_HOME/jre/lib/security/cacerts --storepass "changeit"
# keytool -delete -alias 333 -keystore $JAVA_HOME/jre/lib/security/cacerts --storepass "changeit"
五、参考
How are SSL certificate server names resolved/Can I add alternative names using keytool?
证书服务器的名称是怎样解析的?
https://stackoverflow.com/questions/8443081/how-are-ssl-certificate-server-names-resolved-can-i-add-alternative-names-using/8444863#8444863
javax.net.ssl.SSLHandshakeException: No appropriate protocol
https://stackoverflow.com/questions/41101789/javax-net-ssl-sslhandshakeexception-no-appropriate-protocol
How to solve javax.net.ssl.SSLHandshakeException Error?
https://stackoverflow.com/questions/6659360/how-to-solve-javax-net-ssl-sslhandshakeexception-error
How to bypass javax.net.ssl.SSLHandshakeException
https://ananich.pro/2020/06/how-to-bypass-javax-net-ssl-sslhandshakeexception
java在访问https资源时,忽略证书信任问题
https://blog.csdn.net/lizeyang/article/details/18983843
Android or java https ssl exception
https://www.trinea.cn/android/android-java-https-ssl-exception-2
无私钥情况下转换pem格式证书为jks格式
https://blog.csdn.net/carcoon/article/details/106133668
OpenSSL Certificate Authority
https://jamielinux.com/docs/openssl-certificate-authority/index.html
如何将PEM证书转换成JKS证书
https://biteeniu.github.io/ssl/convert_pem_to_jks
keytool - Key and Certificate Management Tool
https://docs.oracle.com/javase/7/docs/technotes/tools/windows/keytool.html