目录

JAVA 导入信任证书 (Keytool 的使用)

1. 问题背景

使用 ssl 连接时,遇到不信任的证书,应用程序一般都会拒绝连接。

浏览网站时,我们可以通过在浏览器的设置中导入证书,把证书加入到信任列表中。

而在 JAVA 直接进行 SSL 连接应用时,默认没有一个界面来导入证书。JAVA 进行不信任的 ssl 连接时,会报如下异常:

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 的运行环境中导入信任证书。

2. 诊断方式

参照链接: https://confluence.atlassian.com/kb/unable-to-connect-to-ssl-services-due-to-pkix-path-building-failed-779355358.html

以上链接中提供了一种方式,用于诊断你的 Java 环境中是否包含了相应的信任证书。此方式可诊断 HTTPS, IMAPS, LDAPS 等。

1) 下载 SSLPoke.class(原始地址) SSLPoke.zip(本地) 。如下载 SSLPoke.zip ,需要解压得到 SSLPoke.class 文件,后续使用是需要用到 SSLPoke.class 。

2) 运行如下命令,诊断连接是否可信。

$JAVA_HOME/bin/java SSLPoke jira.example.com 443

其中 java 是你要使用的 java 环境,后面是你要诊断的 url 和 port 。

$JAVA_HOME/bin/java SSLPoke jira.example.com 443
Successfully connected
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:229)
	at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1351)
	at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:156)
	at sun.security.ssl.Handshaker.processLoop(Handshaker.java:925)
	at sun.security.ssl.Handshaker.process_record(Handshaker.java:860)
	at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1043)
	at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1343)
	at sun.security.ssl.SSLSocketImpl.writeRecord(SSLSocketImpl.java:728)
	at sun.security.ssl.AppOutputStream.write(AppOutputStream.java:123)
	at sun.security.ssl.AppOutputStream.write(AppOutputStream.java:138)
	at SSLPoke.main(SSLPoke.java:31)
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:145)
	at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:131)
	at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
	at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:382)
	... 15 more

补充: 此方式还能诊断 LDAPS 等 SSL 连接, 如 LDAPS 常用 636 端口:

$JAVA_HOME/bin/java SSLPoke ldap.example.com 636
Successfully connected

3. 解决方式

Java 使用了一种叫 keystore 的文件来存储证书 (默认是位于 $JAVA_HOME/lib/security/cacerts) 。 该文件使用 keytool 工具去管理 (该工具默认位于 $JAVA_HOME/bin/keytool)。

keytool 工具的使用不在这里展开,网上有比较详细的说明。这里主要列举几个会用到的命令。

命令1,列出 keystore 中的证书。

keytool -list

默认情况下,它会在你的 $HOME 目录下产生一个空的 .keystore 文件。如要指定 Java 正在用的 keystore 文件,使用以下参数

keytool -list -keystore $JAVA_HOME/lib/security/cacerts

注意一下, keystore 文件都受 密码 保护。生成新的 keystore 文件时,会要求你输入一个新密码;而当访问一个已有的 keystore 文件时,会要求你验证密码。

$JAVA_HOME/lib/security/cacerts 的默认密码为 “changeit” !!!

$JAVA_HOME/lib/security/cacerts 的默认密码为 “changeit” !!!

$JAVA_HOME/lib/security/cacerts 的默认密码为 “changeit” !!!

重要的事情说三遍!!!

命令2,导入证书。

keytool -import -alias <证书别名> -keystore $JAVA_HOME/jre/lib/security/cacerts -file your.crt

导入时会需要验证密码,默认密码见上面。

4.附加内容:如何获取别人的证书

参考链接: https://confluence.atlassian.com/kb/connecting-to-ssl-services-802171215.html

以 google.com 为例。

openssl s_client -connect google.com:443 < /dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > public.crt
openssl s_client -connect google.com:443 < NUL | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > public.crt