我正在用Java编写一个应用程序,它通过HTTPS连接到两个Web服务器。一个通过默认的信任链获得证书,另一个使用自签名证书。当然,连接到第一台服务器是开箱即用的,而在我使用该服务器的证书创建一个trustStore之前,使用自签名证书连接到服务器不起作用。但是,与默认可信服务器的连接不再起作用,因为显然我创建自己的默认trustStore会被忽略。
我找到的一个解决方案是将证书从默认的trustStore添加到我自己的。但是,我不喜欢这个解决方案,因为它需要我继续管理那个trustStore。 (我不能认为这些证书在可预见的将来会保持不变,对吗?)
除此之外,我发现了两个有着类似问题的5岁线程:
Registering multiple keystores in JVM
How can I have multiple SSL certificates for a Java server
他们都深入到Java SSL基础架构。我希望到现在有一个更方便的解决方案,我可以在我的代码的安全审查中轻松解释。
答案 0 :(得分:59)
您可以使用与我在previous answer中提到的类似的模式(针对其他问题)。
基本上,获取默认信任管理器,创建第二个使用您自己的信任库的信任管理器。将它们都包装在一个自定义信任管理器实现中,该实现将代理调用两者(当一个失败时返回另一个)。
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);
您不必将该上下文设置为默认上下文。您如何使用它取决于您正在使用的客户端库(以及从哪里获取其套接字工厂)。
原则上说,无论如何,您始终必须根据需要更新信任库。 Java 7 JSSE参考指南有一个重要的注意事项"关于此,现在降级为"note" in version 8 of the same guide:
JDK附带了有限数量的受信任根证书 java-home / lib / security / cacerts文件。如keytool中所述 参考页面,您有责任维护(即添加 如果使用此文件,则删除此文件中包含的证书 将文件作为信任库。
取决于您的服务器的证书配置 联系,您可能需要添加其他根证书。获得 需要来自相应供应商的特定根证书。
答案 1 :(得分:5)
也许我回答这个问题还为时6年,但它可能对其他开发人员也有帮助。我还遇到了加载默认信任库和我自己的自定义信任库的相同挑战。在对多个项目使用相同的自定义解决方案之后,我认为创建一个图书馆并使其公开提供以回馈社区非常方便。请在这里看看:Github - SSLContext-Kickstart
用法:
import nl.altindag.sslcontext.SSLFactory;
import javax.net.ssl.SSLContext;
import java.security.cert.X509Certificate;
import java.util.List;
public class App {
public static void main(String[] args) {
String trustStorePath = ...;
char[] password = "password".toCharArray();
SSLFactory sslFactory = SSLFactory.builder()
.withDefaultTrustMaterial()
.withTrustMaterial(trustStorePath, password)
.build();
SSLContext sslContext = sslFactory.getSslContext();
List<X509Certificate> trustedCertificates = sslFactory.getTrustedCertificates();
}
}
我不确定是否应该在此处发布此内容,因为它也可以被视为推广“我的库”的一种方法,但我认为这对面临相同挑战的开发人员可能会有所帮助。
答案 2 :(得分:3)
这是Bruno's answer的较干净版本
public void configureTrustStore() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException,
CertificateException, IOException {
X509TrustManager jreTrustManager = getJreTrustManager();
X509TrustManager myTrustManager = getMyTrustManager();
X509TrustManager mergedTrustManager = createMergedTrustManager(jreTrustManager, myTrustManager);
setSystemTrustManager(mergedTrustManager);
}
private X509TrustManager getJreTrustManager() throws NoSuchAlgorithmException, KeyStoreException {
return findDefaultTrustManager(null);
}
private X509TrustManager getMyTrustManager() throws FileNotFoundException, KeyStoreException, IOException,
NoSuchAlgorithmException, CertificateException {
// Adapt to load your keystore
try (FileInputStream myKeys = new FileInputStream("truststore.jks")) {
KeyStore myTrustStore = KeyStore.getInstance("jks");
myTrustStore.load(myKeys, "password".toCharArray());
return findDefaultTrustManager(myTrustStore);
}
}
private X509TrustManager findDefaultTrustManager(KeyStore keyStore)
throws NoSuchAlgorithmException, KeyStoreException {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore); // If keyStore is null, tmf will be initialized with the default trust store
for (TrustManager tm : tmf.getTrustManagers()) {
if (tm instanceof X509TrustManager) {
return (X509TrustManager) tm;
}
}
return null;
}
private X509TrustManager createMergedTrustManager(X509TrustManager jreTrustManager,
X509TrustManager customTrustManager) {
return new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
// If you're planning to use client-cert auth,
// merge results from "defaultTm" and "myTm".
return jreTrustManager.getAcceptedIssuers();
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
customTrustManager.checkServerTrusted(chain, authType);
} catch (CertificateException e) {
// This will throw another CertificateException if this fails too.
jreTrustManager.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.
jreTrustManager.checkClientTrusted(chain, authType);
}
};
}
private void setSystemTrustManager(X509TrustManager mergedTrustManager)
throws NoSuchAlgorithmException, KeyManagementException {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { mergedTrustManager }, null);
// You don't have to set this as the default context,
// it depends on the library you're using.
SSLContext.setDefault(sslContext);
}
答案 3 :(得分:1)
您可以通过调用TrustManagerFactory.init((KeyStore)null)
并获取其X509Certificate
来检索默认信任存储。将此与您自己的证书结合在一起。您可以使用.jks
从.p12
或KeyStore.load
文件中加载自签名证书,也可以通过{加载.crt
(或.cer
)文件。 {1}}。
CertificateFactory
答案 4 :(得分:-1)
我发现,您还可以使用Apache HttpComponents库中的SSLContextBuilder
类将自定义密钥存储添加到SSLContext
:
SSLContextBuilder builder = new SSLContextBuilder();
try {
keyStore.load(null, null);
builder.loadTrustMaterial(keyStore, null);
builder.loadKeyMaterial(keyStore, null);
} catch (NoSuchAlgorithmException | KeyStoreException | CertificateException | IOException
| UnrecoverableKeyException e) {
log.error("Can not load keys from keystore '{}'", keyStore.getProvider(), e);
}
return builder.build();