在 Java 中使用自定义信任库以及默认信任库

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24555890/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 12:58:17  来源:igfitidea点击:

Using a custom truststore in java as well as the default one

javasslkeystoretruststore

提问by user1785730

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.

我正在用 Java 编写一个应用程序,它通过 HTTPS 连接到两个 Web 服务器。一个通过默认信任链获得信任的证书,另一个使用自签名证书。当然,连接到第一台服务器是开箱即用的,而在我使用该服务器的证书创建了 trustStore 之前,使用自签名证书连接到服务器是无效的。但是,与默认受信任服务器的连接不再起作用,因为显然一旦我创建了自己的信任库,默认的 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?)

我找到的一种解决方案是将默认信任库中的证书添加到我自己的。但是,我不喜欢这个解决方案,因为它需要我继续管理那个 trustStore。(我不能假设这些证书在可预见的未来保持不变,对吗?)

Apart from that I found two 5 year old threads with a similar problem:

除此之外,我发现了两个有类似问题的 5 岁线程:

Registering multiple keystores in JVM

在 JVM 中注册多个密钥库

How can I have multiple SSL certificates for a Java server

如何为 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.

他们都深入研究了 Java SSL 基础设施。我希望现在有一个更方便的解决方案,我可以在对我的代码的安全中轻松解释。

采纳答案by Bruno

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).

您不必将该上下文设置为默认上下文。您如何使用它取决于您使用的客户端库(以及它从哪里获取套接字工厂)。



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:

话虽如此,原则上,您始终必须根据需要更新信任库。Java 7 JSSE 参考指南对此有一个“重要说明”,现在在同一指南的第 8 版中降级为仅“说明”

The JDK ships with a limited number of trusted root certificates in the java-home/lib/security/cacerts file. As documented in keytool reference pages, it is your responsibility to maintain (that is, add and remove) the certificates contained in this file if you use this file as a truststore.

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.

JDK 在 java-home/lib/security/cacerts 文件中附带了数量有限的可信根证书。如 keytool 参考页面中所述,如果您将此文件用作信任库,则您有责任维护(即添加和删除)此文件中包含的证书。

根据您联系的服务器的证书配置,您可能需要添加额外的根证书。从适当的供应商处获取所需的特定根证书。

回答by OliLay

As I figured out, you can also use SSLContextBuilderclass from the Apache HttpComponents library to add your custom keystore to a SSLContext:

正如我发现的,您还可以使用SSLContextBuilderApache HttpComponents 库中的类将您的自定义密钥库添加到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();

回答by Johannes Brodwall

You can retrieve the default trust store by calling TrustManagerFactory.init((KeyStore)null)and get its X509Certificates. Combine this with your own certificate. You can either load the self-signed certificate from a .jksor .p12file with KeyStore.loador you can load a .crt(or .cer) file via CertificateFactory.

您可以通过调用TrustManagerFactory.init((KeyStore)null)并获取其X509Certificates来检索默认信任存储。将此与您自己的证书结合起来。您可以从加载自签名证书.jks.p12文件,KeyStore.load也可以加载一个.crt(或.cer通过)文件CertificateFactory

private static void installCertificate(File crtFile) {
    if (crtFile.exists()) {
        try {
            HttpsURLConnection.setDefaultSSLSocketFactory(createSSLSocketFactory(crtFile));
        } catch (GeneralSecurityException | IOException e) {
            logger.warn("Failed to install ca certificate, crossing fingers and proceeding as normal", e);
        }
    } else {
        logger.warn("No certificate file {}", crtFile.getAbsolutePath());
    }
}

private static SSLSocketFactory createSSLSocketFactory(File crtFile) throws GeneralSecurityException, IOException {
    SSLContext sslContext = SSLContext.getInstance("SSL");

    // Create a new trust store, use getDefaultType for .jks files or "pkcs12" for .p12 files
    KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
    // You can supply a FileInputStream to a .jks or .p12 file and the keystore password as an alternative to loading the crt file
    trustStore.load(null, null);

    // Read the certificate from disk
    X509Certificate result;
    try (InputStream input = new FileInputStream(crtFile)) {
        result = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(input);
    }
    // Add it to the trust store
    trustStore.setCertificateEntry(crtFile.getName(), result);

    // Add default Root CA certificates (generally, from JAVA_HOME/lib/cacerts) to trust store
    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init((KeyStore)null);
    for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) {
        if (trustManager instanceof X509TrustManager) {
            for (X509Certificate acceptedIssuer : ((X509TrustManager) trustManager).getAcceptedIssuers()) {
                trustStore.setCertificateEntry(acceptedIssuer.getSubjectDN().getName(), acceptedIssuer);
            }
        }
    }

    // Convert the trust store to trust managers
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(trustStore);
    TrustManager[] trustManagers = tmf.getTrustManagers();

    sslContext.init(null, trustManagers, null);
    return sslContext.getSocketFactory();
}