概述
在这个教程中,我们将探讨在发起SSL请求时可能会遇到的一些常见问题。
1. 证书存储错误
当Java应用程序与远程方建立SSL连接时,它需要通过验证证书来确定服务器是否可信。如果根证书不在证书存储文件中,将会出现安全异常:
Untrusted: Exception in thread "main" 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_HOME/lib/security/cacerts
。
2. 自签名证书
在非生产环境中,通常会发现由不受信任的颁发机构(CA)签发的证书,这些被称为自签名证书。
我们可以在https://wrong.host.badssl.com/ 或 https://self-signed.badssl.com/ 等URL找到一些不被信任的证书示例。在任何浏览器中打开这两个URL都会导致安全异常。我们可以查看它们,了解证书之间的差异。
打开https://self-signed.badssl.com/ 后,我们会看到浏览器返回一个“证书颁发机构无效”的错误,因为证书是由浏览器未知的权威机构签发的:
另一方面,打开https://wrong.host.badssl.com/ 会导致“证书通用名称无效”的错误,这表明证书的颁发给的主机名与提供的主机名不同:
对于运行在JDK或JRE中的应用程序,这些安全异常将阻止我们连接到这些不受信任的方。
3. 管理证书存储以信任我们的证书
幸运的是,JDK和JRE提供了与证书存储交互以管理其内容的工具,即Keytool,它位于$JAVA_HOME/bin/keytool
。
重要提示:Keytool与之交互需要密码。默认密码是“changeit”。
3.1. 列出证书
要获取JVM证书存储中注册的所有证书列表,我们需要执行以下命令:
keytool -list -keystore $JAVA_HOME/lib/security/cacerts
这将返回所有条目列表,如下所示:
Your keystore contains 227 entries
Alias name: accvraiz1
Creation date: Apr 14, 2021
Entry type: trustedCertEntry
Owner: C=ES, O=ACCV, OU=PKIACCV, CN=ACCVRAIZ1
Issuer: C=ES, O=ACCV, OU=PKIACCV, CN=ACCVRAIZ1
....
3.2. 添加证书
为了手动将证书添加到列表中,以便我们在发起SSL请求时进行验证,我们需要执行以下命令:
keytool -import -trustcacerts -file [certificate-file] -alias [alias] -keystore $JAVA_HOME/lib/security/cacerts
例如:
keytool -import -alias ss-badssl.com -keystore $JAVA_HOME/lib/security/cacerts -file ss-badssl.pem
3.3. 定制证书存储路径
如果上述方法不起作用,可能是因为我们的Java应用程序正在使用不同的证书存储。为了确认这一点,我们可以在运行Java应用程序时指定使用的证书存储:
java -Djavax.net.ssl.trustStore=CustomTrustStorePath ...
这样,我们确保它使用了我们之前编辑过的证书存储。如果这没有帮助,我们还可以通过应用VM选项来调试SSL连接:
-Djavax.net.debug=all
4. 自动化脚本
最后,我们可以创建一个简单但实用的脚本来自动化整个过程:
#!/bin/sh
# cacerts.sh
/usr/bin/openssl s_client -showcerts -connect $1:443 </dev/null 2>/dev/null | /usr/bin/openssl x509 -outform PEM > /tmp/$1.pem
$JAVA_HOME/bin/keytool -import -trustcacerts -file /tmp/$1.pem -alias $1 -keystore $JAVA_HOME/lib/security/cacerts
rm /tmp/$1.pem
在脚本中,首先尝试与传递的第一个参数作为DNS的域名建立SSL连接并请求显示证书。然后,证书信息通过openssl
处理并保存为PEM文件。
最后,我们将使用这个PEM文件,通过指示keytool
将证书导入cacerts文件,并使用DNS作为别名。
例如,我们可以尝试添加https://self-signed.badssl.com 的证书:
cacerts.sh self-signed.badssl.com
运行后,我们可以检查cacerts文件现在包含该证书:
keytool -list -keystore $JAVA_HOME/lib/security/cacerts
最后,我们将在新证书中看到:
#5: ObjectId: 2.5.29.32 Criticality=false
CertificatePolicies [
[CertificatePolicyId: [2.5.29.32.0]
Alias name: self-signed.badssl.com
Creation date: Oct 22, 2021
Entry type: trustedCertEntry
Owner: CN=*.badssl.com, O=BadSSL, L=San Francisco, ST=California, C=US
Issuer: CN=*.badssl.com, O=BadSSL, L=San Francisco, ST=California, C=US
Serial number: c9c0f0107cc53eb0
Valid from: Mon Oct 11 22:03:54 CEST 2021 until: Wed Oct 11 22:03:54 CEST 2023
Certificate fingerprints:
....
5. 手动添加证书
如果我们出于任何原因不想使用openssl
,也可以使用浏览器提取证书并通过keytool
添加。在基于Chromium的浏览器中,打开网站如https://self-signed.badssl.com/,然后打开开发者工具(Windows和Linux上的F12)。点击“安全”标签,然后点击“查看证书”。证书信息将显示出来:
转到“详细信息”标签,点击“导出”按钮并保存。这就是我们的PEM文件:
最后,我们使用keytool
导入它:
$JAVA_HOME/bin/keytool -import -trustcacerts -file CERTIFICATEFILE -alias ALIAS -keystore $JAVA_HOME/lib/security/cacerts
6. 总结
在这篇文章中,我们学习了如何将自签名证书添加到我们的JDK/JRE证书存储中。现在,当Java应用程序连接到包含这些证书的站点时,它们可以信任服务器端。