我正在添加到我们的大型Java应用程序的模块必须与另一家公司的SSL安全网站进行交谈 . 问题是该站点使用自签名证书 . 我有一份证书副本,以验证我没有遇到中间人攻击,我需要将此证书合并到我们的代码中,以便与服务器的连接成功 .
这是基本代码:
void sendRequest(String dataPacket) {
String urlStr = "https://host.example.com/";
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setMethod("POST");
conn.setRequestProperty("Content-Length", data.length());
conn.setDoOutput(true);
OutputStreamWriter o = new OutputStreamWriter(conn.getOutputStream());
o.write(data);
o.flush();
}
在没有为自签名证书进行任何额外处理的情况下,这会在conn.getOutputStream()处死,但有以下异常:
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
....
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
理想情况下,我的代码需要教Java接受这个自签名证书,对于应用程序中的这一点,以及其他任何地方 .
我知道我可以将证书导入JRE的证书颁发机构商店,这将允许Java接受它 . 如果我能提供帮助,那不是我想采取的方法;对于他们可能不使用的一个模块,我们所有客户的机器上看起来非常具有侵入性;它会影响使用相同JRE的所有其他Java应用程序,我不喜欢它,即使访问此站点的任何其他Java应用程序的几率为零 . 这也不是一个简单的操作:在UNIX上,我必须获得以这种方式修改JRE的访问权限 .
我还看到我可以创建一个执行一些自定义检查的TrustManager实例 . 看起来我甚至可以创建一个TrustManager,在除了这个证书之外的所有实例中委托给真正的TrustManager . 但看起来TrustManager是全局安装的,我认为会影响我们应用程序的所有其他连接,这对我来说也不是很合适 .
设置Java应用程序以接受自签名证书的首选,标准或最佳方法是什么?我可以完成上面提到的所有目标,还是我必须妥协?是否有一个涉及文件和目录和配置设置的选项,以及几乎没有代码?
5 回答
我们复制JRE的信任库并将我们的自定义证书添加到该信任库,然后告诉应用程序将自定义信任库与系统属性一起使用 . 这样我们就可以单独保留默认的JRE信任库 .
缺点是,当您更新JRE时,您不会让其新的信任库自动与您的自定义信任库合并 .
您可以通过安装程序或启动例程来验证truststore / jdk并检查不匹配或自动更新信任库来处理此方案 . 我不知道如果在应用程序运行时更新信任库会发生什么 .
这个解决方案不是100%优雅或万无一失,但它简单,有效,并且不需要代码 .
自己创建
SSLSocket
工厂,并在连接前将其设置在HttpsURLConnection
上 .你需要创建一个
SSLSocketFactory
并保持它 . 这是如何初始化它的草图:如果您在创建密钥库时需要帮助,请发表评论 .
这是加载密钥库的示例:
要创建一个PEM格式的证书密钥存储,您可以使用
CertificateFactory
编写自己的代码,或只是keytool
导入从JDK(密钥工具不会为一个"key entry"工作,但只是罚款一"trusted entry") .我在线阅读了很多地方来解决这个问题 . 这是我为使其工作而编写的代码:
app.certificateString是包含证书的String,例如:
我已经测试过你可以在证书字符串中放置任何字符,如果它是自签名的,只要你保持上面的确切结构 . 我用笔记本电脑的终端命令行获取了证书字符串 .
如果创建
SSLSocketFactory
不是一个选项,只需将密钥导入JVM即可$openssl s_client -connect dev-server:443
,然后创建一个看起来像dev-server.pem的文件导入密钥:
#keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem
. 密码:改变重启JVM
资料来源:How to solve javax.net.ssl.SSLHandshakeException?
当使用commons-httpclient访问带有自签名证书的内部https服务器时,我不得不做这样的事情 . 是的,我们的解决方案是创建一个只传递所有内容的自定义TrustManager(记录调试消息) .
这归结为我们自己的SSLSocketFactory从我们的本地SSLContext创建SSL套接字,该套接字设置为只有我们的本地TrustManager与之相关联 . 您根本不需要靠近密钥库/证书库 .
所以这是我们的LocalSSLSocketFactory:
与实现SecureProtocolSocketFactory的其他方法一起 . LocalSSLTrustManager是前面提到的虚拟信任管理器实现 .