Java 如何使用多个信任源初始化 TrustManagerFactory?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/23144353/
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-13 21:02:36  来源:igfitidea点击:

How do I initialize a TrustManagerFactory with multiple sources of trust?

javasslx509jsse

提问by varrunr

My application has a personal keystore containing trusted self-signed certificates for use in the local network - say mykeystore.jks. I wish to be able to connect to public sites(say google.com) as well as ones in my local network using self-signed certificates which have been provisioned locally.

我的应用程序有一个包含在本地网络中使用的可信自签名证书的个人密钥库 - 例如mykeystore.jks. 我希望能够使用本地提供的自签名证书连接到公共站点(比如 google.com)以及本地网络中的站点。

The problem here is that, when I connect to https://google.com, path building fails, because setting my own keystore overrides the default keystore containing root CAs bundled with the JRE, reporting the exception

这里的问题是,当我连接到https://google.com 时,路径构建失败,因为设置我自己的密钥库会覆盖包含与 JRE 捆绑的根 CA 的默认密钥库,报告异常

sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

However, if I import a CA certificate into my own keystore(mykeystore.jks) it works fine. Is there a way to support both?

但是,如果我将 CA 证书导入我自己的密钥库(mykeystore.jks),它就可以正常工作。有没有办法支持两者?

I have my own TrustManger for this purpose,

为此,我有自己的 TrustManger,

public class CustomX509TrustManager implements X509TrustManager {

        X509TrustManager defaultTrustManager;

        public MyX509TrustManager(KeyStore keystore) {
                TrustManagerFactory trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                trustMgrFactory.init(keystore);
                TrustManager trustManagers[] = trustMgrFactory.getTrustManagers();
                for (int i = 0; i < trustManagers.length; i++) {
                    if (trustManagers[i] instanceof X509TrustManager) {
                        defaultTrustManager = (X509TrustManager) trustManagers[i];
                        return;
                    }
                }

        public void checkServerTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
            try {
                defaultTrustManager.checkServerTrusted(chain, authType);
            } catch (CertificateException ce) {
            /* Handle untrusted certificates */
            }
        }
    }

I then initialize the SSLContext,

然后我初始化 SSLContext,

TrustManager[] trustManagers =
            new TrustManager[] { new CustomX509TrustManager(keystore) };
SSLContext customSSLContext =
        SSLContext.getInstance("TLS");
customSSLContext.init(null, trustManagers, null);

and set the socket factory,

并设置套接字工厂,

HttpsURLConnection.setDefaultSSLSocketFactory(customSSLContext.getSocketFactory());

The main program,

主程序,

URL targetServer = new URL(url);
HttpsURLConnection conn = (HttpsURLConnection) targetServer.openConnection();

If I don't set my own trust managers, it connects to https://google.comjust fine. How do I get a "default trust manager" which points to the default key store?

如果我不设置自己的信任管理器,它就可以很好地连接到https://google.com。如何获得指向默认密钥库的“默认信任管理器”?

回答by Pasi

In trustMgrFactory.init(keystore);you're configuring defaultTrustManager with your own personal keystore, not the system default keystore.

trustMgrFactory.init(keystore);您使用自己的个人密钥库而不是系统默认密钥库配置 defaultTrustManager 时。

Based on reading the source code for sun.security.ssl.TrustManagerFactoryImpl, it looks like trustMgrFactory.init((KeyStore) null);would do exactly what you need (load the system default keystore), and based on quick testing, it seems to work for me.

基于阅读 sun.security.ssl.TrustManagerFactoryImpl 的源代码,它看起来trustMgrFactory.init((KeyStore) null);完全符合您的需要(加载系统默认密钥库),并且基于快速测试,它似乎对我有用。

回答by Novoj

I've run into the same issue with Commons HttpClient. Working solution for my case was to create delegation chain for PKIX TrustManagers in following way:

我在 Commons HttpClient 中遇到了同样的问题。我的案例的工作解决方案是通过以下方式为 PKIX TrustManagers 创建委托链:

public class TrustManagerDelegate implements X509TrustManager {
    private final X509TrustManager mainTrustManager;
    private final X509TrustManager trustManager;
    private final TrustStrategy trustStrategy;

    public TrustManagerDelegate(X509TrustManager mainTrustManager, X509TrustManager trustManager, TrustStrategy trustStrategy) {
        this.mainTrustManager = mainTrustManager;
        this.trustManager = trustManager;
        this.trustStrategy = trustStrategy;
    }

    @Override
    public void checkClientTrusted(
            final X509Certificate[] chain, final String authType) throws CertificateException {
        this.trustManager.checkClientTrusted(chain, authType);
    }

    @Override
    public void checkServerTrusted(
            final X509Certificate[] chain, final String authType) throws CertificateException {
        if (!this.trustStrategy.isTrusted(chain, authType)) {
            try {
                mainTrustManager.checkServerTrusted(chain, authType);
            } catch (CertificateException ex) {
                this.trustManager.checkServerTrusted(chain, authType);
            }
        }
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return this.trustManager.getAcceptedIssuers();
    }

}

And initialize HttpClient in following way (yes it's ugly):

并以下列方式初始化 HttpClient (是的,它很丑):

final SSLContext sslContext;
try {
    sslContext = SSLContext.getInstance("TLS");
    final TrustManagerFactory javaDefaultTrustManager = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    javaDefaultTrustManager.init((KeyStore)null);
    final TrustManagerFactory customCaTrustManager = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    customCaTrustManager.init(getKeyStore());

    sslContext.init(
        null,
        new TrustManager[]{
            new TrustManagerDelegate(
                    (X509TrustManager)customCaTrustManager.getTrustManagers()[0],
                    (X509TrustManager)javaDefaultTrustManager.getTrustManagers()[0],
                    new TrustSelfSignedStrategy()
            )
        },
        secureRandom
    );

} catch (final NoSuchAlgorithmException ex) {
    throw new SSLInitializationException(ex.getMessage(), ex);
} catch (final KeyManagementException ex) {
    throw new SSLInitializationException(ex.getMessage(), ex);
}

SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext);

PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(
        RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", sslSocketFactory)
                .build()
);
//maximum parallel requests is 500
cm.setMaxTotal(500);
cm.setDefaultMaxPerRoute(500);

CredentialsProvider cp = new BasicCredentialsProvider();
cp.setCredentials(
        new AuthScope(apiSettings.getIdcApiUrl(), 443),
        new UsernamePasswordCredentials(apiSettings.getAgencyId(), apiSettings.getAgencyPassword())
);

client = HttpClients.custom()
                    .setConnectionManager(cm)
                    .build();

In your case with simple HttpsURLConnection you may get by with simplified version of delegating class:

在您使用简单 HttpsURLConnection 的情况下,您可以使用委托类的简化版本:

public class TrustManagerDelegate implements X509TrustManager {
    private final X509TrustManager mainTrustManager;
    private final X509TrustManager trustManager;

    public TrustManagerDelegate(X509TrustManager mainTrustManager, X509TrustManager trustManager) {
        this.mainTrustManager = mainTrustManager;
        this.trustManager = trustManager;
    }

    @Override
    public void checkClientTrusted(
            final X509Certificate[] chain, final String authType) throws CertificateException {
        this.trustManager.checkClientTrusted(chain, authType);
    }

    @Override
    public void checkServerTrusted(
            final X509Certificate[] chain, final String authType) throws CertificateException {
        try {
            mainTrustManager.checkServerTrusted(chain, authType);
        } catch (CertificateException ex) {
            this.trustManager.checkServerTrusted(chain, authType);
        }
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return this.trustManager.getAcceptedIssuers();
    }

}

回答by Hugh Jeffner

The answer hereis how I came to understand how to do this. If you just want to accept the system CA certs plus a custom keystore of certs I simplified it into a single class with some convenience methods. Full code available here:

这里的答案是我是如何理解如何做到这一点的。如果您只想接受系统 CA 证书以及自定义的证书密钥库,我会将其简化为具有一些便捷方法的单个类。完整代码可在此处获得:

https://gist.github.com/HughJeffner/6eac419b18c6001aeadb

https://gist.github.com/HughJeffner/6eac419b18c6001aeadb

KeyStore keystore; // Get your own keystore here
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManager[] tm = CompositeX509TrustManager.getTrustManagers(keystore);
sslContext.init(null, tm, null);

回答by KFJK

For Android developers, this can be much easier. In summary, you can add a xml res file to config your custom certs.

对于 Android 开发人员来说,这会容易得多。总之,您可以添加一个 xml res 文件来配置您的自定义证书。

Step 1: open your manifest xml add an attribute.

第 1 步:打开您的清单 xml 添加一个属性。

<manifest ... >
    <application android:networkSecurityConfig="@xml/network_security_config"
                    ... >
        ...
    </application>
</manifest>

Step 2: Add network_security_config.xml to res/xml, config certs as you want.

第 2 步:将 network_security_config.xml 添加到 res/xml,根据需要配置证书。

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config>
        <trust-anchors>
            <certificates src="@raw/extracas"/>
            <certificates src="system"/>
        </trust-anchors>
    </base-config>
</network-security-config>

Note: this xml can support many other usage, and this solution only works on api24+.

注意:这个xml可以支持很多其他的用法,这个解决方案只适用于api24+。

Official reference: here

官方参考:这里