一尘不染

Java如何在特定连接上使用不同的证书?

java

我要添加到大型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应用程序以接受自签名证书的首选,标准或最佳方法是什么?我是否可以实现上述所有目标,还是必须妥协?是否有涉及文件和目录以及配置设置的选项,以及几乎没有代码的选项?


阅读 516

收藏
2020-02-26

共1个答案

一尘不染

SSLSocket自己创建一个工厂,并HttpsURLConnection在连接之前将其设置在工厂上。

...
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslFactory);
conn.setMethod("POST");
...

你将要创建一个SSLSocketFactory并保留它。这是如何初始化它的草图:

/* Load the keyStore that includes self-signed cert as a "trusted" entry. */
KeyStore keyStore = ... 
TrustManagerFactory tmf = 
  TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tmf.getTrustManagers(), null);
sslFactory = ctx.getSocketFactory();

如果你在创建密钥库方面需要帮助,请发表评论。

这是加载密钥库的示例:

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(trustStore, trustStorePassword);
trustStore.close();

要使用PEM格式的证书创建密钥库,你可以使用编写自己的代码CertificateFactory,或者仅从keytoolJDK 导入它(密钥工具不适用于“密钥项”,但对于“受信任的项”来说就很好)。

keytool -import -file selfsigned.pem -alias server -keystore server.jks
2020-02-26