如何生成并将自签名的密钥对导入到JSSE的系统默认密钥库,以便在本地主机上进行测试?
我正在开发两个Java应用,一个是服务器,一个是客户端。它们通过SSL通信,这要归功于JSSE。
最终,服务器将获得由受信任机构颁发的证书,如果我对JSSE的理解没错,那么客户端应该能够在不进行任何额外配置的情况下连接。
在开发阶段,为此我将两者都放在同一台计算机上运行,它们将通过 localhost 连接。我想在Java的默认密钥库和信任库中生成一个自签名证书(cacerts)。顺便说一句,我是在Docker上进行这项工作,所以不会把这些证书永久添加到实际系统中。
我正在使用以下命令来生成密钥对:
keytool -genkeypair -storepass changeit -alias test -keyalg RSA -validity 365 -dname "CN=, OU=, O=, L=, ST=, C="
这还不够。我的客户端仍然无法完成SSL握手。
在上述命令之后,我还尝试了以下命令:
keytool -exportcert -storepass changeit -alias test -file test.cer
keytool -importcert -storepass changeit -alias test -file test.cer
然而,这会导致一个错误:
keytool error: java.lang.Exception: Certificate reply and certificate in keystore are identical
这表明该密钥对已经存在于 cacerts 中,无需导出再导入密钥;然而仅仅生成密钥对并不能工作。
我到底哪里做错了?我的目标是在 localhost 上运行我的服务器和客户端,并通过SSL进行通信,同时仅使用默认的系统密钥库和信任库。
我知道可以创建本地服务器密钥库和本地客户端信任库文件,然后通过JVM参数如 -Djavax.net.ssl.keyStore 和 -Djavax.net.ssl.keyStorePassword 等来使用它们。但我不想采用这个方案,原因有两个。第一,它与完成后实际运行客户端时所使用的命令不一致。第二,它会让运行命令显得过于冗长。
解决方案
-genkeypair 已经在 keystore(密钥库) 中创建了带自签名证书的私钥条目,因此通常不需要再次将同一证书导入到同一个keystore/别名中,这可能会触发错误。
如果目标是让证书在TLS握手中被信任,应该将其导入到 truststore(信任库),而不是keystore(密钥库)。
keytool -exportcert \
-alias test \
-keystore keystore.jks \
-file cert.cer
keytool -importcert \
-alias test-server \
-file cert.cer \
-keystore truststore.jks
然后将应用程序配置为使用它:
-Djavax.net.ssl.trustStore=/path/to/truststore.jks
密钥库和信任库的作用不同:密钥库保存私钥,而信任库包含用于TLS验证的受信任证书。