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

标签 java ssl keystore truststore jsse

我要添加到大型 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,它在除这个证书之外的所有实例中都委托(delegate)给真正的 TrustManager。但看起来 TrustManager 是全局安装的,我想这会影响我们应用程序的所有其他连接,我也觉得这不太对。

设置 Java 应用程序以接受自签名证书的首选、标准或最佳方法是什么?我能否实现上述所有目标,还是必须妥协?是否有涉及文件和目录以及配置设置以及几乎没有代码的选项?

最佳答案

自己创建一个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 keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(trustStore, trustStorePassword);
trustStore.close();

要使用 PEM 格式证书创建 keystore ,您可以使用 CertificateFactory 编写自己的代码,或者使用 JDK 中的 keytool 导入它(keytool 不会对“ key 输入”起作用,但对“受信任的输入”来说就没问题了)。

keytool -import -file selfsigned.pem -alias server -keystore server.jks

关于java - 如何在特定连接上使用不同的证书?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12392075/

相关文章:

java - public<T> void run (T object ) { } 是什么意思?

java - 在 Maven mojo 中使用 RetentionPolicy.SOURCE 处理注释

java - 响应返回给客户端后清理

android - 如何创建具有无限有效性的 Android keystore RSA key ?

android - KeyStore Provider 中不需要加密的 key 是什么?

java - 从进程列表中隐藏 keystore 密码

java - 如何在android中从facebook获取用户性别和生日

apache - 无法在 Postman 中使用自签名证书

java - 如何在 Java 1.6 中使用 ECC 私钥?

Spring Security 问题 - 在 subjectDN 中找不到匹配模式