问题描述
我正在用 Java 编写一个应用程序,它通过 HTTPS 连接到两个 Web 服务器.一个通过默认信任链获得信任的证书,另一个使用自签名证书.当然,连接到第一台服务器是开箱即用的,而在我使用来自该服务器的证书创建 trustStore 之前,使用自签名证书连接到服务器是无效的.但是,与默认信任服务器的连接不再起作用,因为显然一旦我创建了自己的信任库,默认信任库就会被忽略.
I'm writing an application in Java which connects to two web servers via HTTPS. One got a certificate trusted via the default chain of trust, the other uses a self signed certificate. Of course, connecting to the first server worked out of the box, whereas connecting to the server with the self signed certificate did not work until I created a trustStore with the certificate from that server. However, the connection to the by default trusted server does not work any more, because apparently the default trustStore gets to be ignored once I created my own.
我找到的一个解决方案是将默认信任库中的证书添加到我自己的.但是,我不喜欢这个解决方案,因为它需要我继续管理那个 trustStore.(我不能假设这些证书在可预见的未来保持不变,对吗?)
One solution I found was to add the certificates from the default trustStore to my own. However, I don't like this solution, because it requires me to keep managing that trustStore. (I cannot assume these certificates remain static in the foreseeable future, right?)
除此之外,我发现了两个有类似问题的 5 岁线程:
Apart from that I found two 5 year old threads with a similar problem:
他们都深入研究了 Java SSL 基础设施.我希望现在有一个更方便的解决方案,我可以在对我的代码进行安全审查时轻松解释.
They both go deep into the Java SSL infrastructure. I was hoping that by now there is a more convenient solution which I can explain easily in a security review of my code.
推荐答案
您可以使用与我在 以前的答案(针对不同的问题).
You could use a similar pattern to what I've mentioned in a previous answer (for a different problem).
本质上,获取默认信任管理器,创建使用您自己的信任存储的第二个信任管理器.将它们都包装在一个自定义的信任管理器实现中,该实现将调用委托给两者(当一个失败时回退到另一个).
Essentially, get hold of the default trust manager, create a second trust manager that uses your own trust store. Wrap them both in a custom trust manager implementation that delegates call to both (falling back on the other when one fails).
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
// Using null here initialises the TMF with the default trust store.
tmf.init((KeyStore) null);
// Get hold of the default trust manager
X509TrustManager defaultTm = null;
for (TrustManager tm : tmf.getTrustManagers()) {
if (tm instanceof X509TrustManager) {
defaultTm = (X509TrustManager) tm;
break;
}
}
FileInputStream myKeys = new FileInputStream("truststore.jks");
// Do the same with your trust store this time
// Adapt how you load the keystore to your needs
KeyStore myTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
myTrustStore.load(myKeys, "password".toCharArray());
myKeys.close();
tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(myTrustStore);
// Get hold of the default trust manager
X509TrustManager myTm = null;
for (TrustManager tm : tmf.getTrustManagers()) {
if (tm instanceof X509TrustManager) {
myTm = (X509TrustManager) tm;
break;
}
}
// Wrap it in your own class.
final X509TrustManager finalDefaultTm = defaultTm;
final X509TrustManager finalMyTm = myTm;
X509TrustManager customTm = new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
// If you're planning to use client-cert auth,
// merge results from "defaultTm" and "myTm".
return finalDefaultTm.getAcceptedIssuers();
}
@Override
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
try {
finalMyTm.checkServerTrusted(chain, authType);
} catch (CertificateException e) {
// This will throw another CertificateException if this fails too.
finalDefaultTm.checkServerTrusted(chain, authType);
}
}
@Override
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
// If you're planning to use client-cert auth,
// do the same as checking the server.
finalDefaultTm.checkClientTrusted(chain, authType);
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { customTm }, null);
// You don't have to set this as the default context,
// it depends on the library you're using.
SSLContext.setDefault(sslContext);
您不必将该上下文设置为默认上下文.您如何使用它取决于您使用的客户端库(以及它从何处获取套接字工厂).
You don't have to set that context as the default context. How you use it depends on the client library you're using (and where it gets its socket factories from).
话虽如此,原则上,无论如何您都必须根据需要更新信任库.Java 7 JSSE 参考指南对此有一个重要说明",现在降级为 "note" 在同一指南的第 8 版中:
This being said, in principle, you'd always have to update the truststore as required anyway. The Java 7 JSSE Reference Guide had an "important note" about this, now downgraded to just a "note" in version 8 of the same guide:
JDK 附带有限数量的可信根证书java-home/lib/security/cacerts 文件.如 keytool 中所述参考页,您有责任维护(即添加并删除)此文件中包含的证书(如果您使用它)文件作为信任库.
根据您选择的服务器的证书配置联系,您可能需要添加额外的根证书.获得需要来自相应供应商的特定根证书.
Depending on the certificate configuration of the servers that you contact, you may need to add additional root certificates. Obtain the needed specific root certificates from the appropriate vendor.
这篇关于在 java 中使用自定义信任库以及默认信任库的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!