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

时间:2014-04-17 22:13:14

标签: java ssl x509 jsse

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

这里的问题是,当我连接到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

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

我为此目的拥有自己的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 */
            }
        }
    }

然后我初始化SSLContext,

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

并设置套接字工厂,

HttpsURLConnection.setDefaultSSLSocketFactory(customSSLContext.getSocketFactory());

主程序,

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

如果我没有设置自己的信任管理员,那就很好地连接到https://google.com。我如何获得&#34;默认信任经理&#34;哪个指向默认密钥库?

7 个答案:

答案 0 :(得分:14)

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

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

答案 1 :(得分:9)

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

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

答案 2 :(得分:2)

我遇到了与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();
    }

}

并按照以下方式初始化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();

在您使用简单的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();
    }

}

答案 3 :(得分:0)

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

步骤1:打开清单xml,然后添加属性。

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

步骤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>

注意:此xml可以支持许多其他用法,并且此解决方案仅适用于api24 +。

官方参考:here

答案 4 :(得分:0)

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;

import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

/**
 * Represents an ordered list of {@link X509TrustManager}s with additive trust. If any one of the composed managers
 * trusts a certificate chain, then it is trusted by the composite manager.
 *
 * This is necessary because of the fine-print on {@link SSLContext#init}: Only the first instance of a particular key
 * and/or trust manager implementation type in the array is used. (For example, only the first
 * javax.net.ssl.X509KeyManager in the array will be used.)
 *
 * @author codyaray
 * @since 4/22/2013
 * @see <a href="http://stackoverflow.com/questions/1793979/registering-multiple-keystores-in-jvm">
 *     http://stackoverflow.com/questions/1793979/registering-multiple-keystores-in-jvm
 *     </a>
 */
@SuppressWarnings("unused")
public class CompositeX509TrustManager implements X509TrustManager {

    private final List<X509TrustManager> trustManagers;

    public CompositeX509TrustManager(List<X509TrustManager> trustManagers) {
        this.trustManagers = ImmutableList.copyOf(trustManagers);
    }

    public CompositeX509TrustManager(KeyStore keystore) {

        this.trustManagers = ImmutableList.of(getDefaultTrustManager(), getTrustManager(keystore));

    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        for (X509TrustManager trustManager : trustManagers) {
            try {
                trustManager.checkClientTrusted(chain, authType);
                return; // someone trusts them. success!
            } catch (CertificateException e) {
                // maybe someone else will trust them
            }
        }
        throw new CertificateException("None of the TrustManagers trust this certificate chain");
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        for (X509TrustManager trustManager : trustManagers) {
            try {
                trustManager.checkServerTrusted(chain, authType);
                return; // someone trusts them. success!
            } catch (CertificateException e) {
                // maybe someone else will trust them
            }
        }
        throw new CertificateException("None of the TrustManagers trust this certificate chain");
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        ImmutableList.Builder<X509Certificate> certificates = ImmutableList.builder();
        for (X509TrustManager trustManager : trustManagers) {
            for (X509Certificate cert : trustManager.getAcceptedIssuers()) {
                certificates.add(cert);
            }
        }
        return Iterables.toArray(certificates.build(), X509Certificate.class);
    }

    public static TrustManager[] getTrustManagers(KeyStore keyStore) {

        return new TrustManager[] { new CompositeX509TrustManager(keyStore) };

    }

    public static X509TrustManager getDefaultTrustManager() {

        return getTrustManager(null);

    }

    public static X509TrustManager getTrustManager(KeyStore keystore) {

        return getTrustManager(TrustManagerFactory.getDefaultAlgorithm(), keystore);

    }

    public static X509TrustManager getTrustManager(String algorithm, KeyStore keystore) {

        TrustManagerFactory factory;

        try {
            factory = TrustManagerFactory.getInstance(algorithm);
            factory.init(keystore);
            return Iterables.getFirst(Iterables.filter(
                    Arrays.asList(factory.getTrustManagers()), X509TrustManager.class), null);
        } catch (NoSuchAlgorithmException | KeyStoreException e) {
            e.printStackTrace();
        }

        return null;

    }

}

答案 5 :(得分:0)

虽然这个问题已经有 6 年的历史了,但我还是想分享一下我对这个挑战的解决方案。它使用了 Cody A. Ray 的相同代码片段,Hugh Jeffner 也共享了该代码片段。

SSLFactory sslFactory = SSLFactory.builder()
    .withDefaultTrustMaterial() // --> uses the JDK trusted certificates
    .withTrustMaterial("/path/to/mykeystore.jks", "password".toCharArray())
    .build();

HttpsURLConnection.setDefaultSSLSocketFactory(sslFactory.getSslSocketFactory());

在 ssl 握手过程中,它将首先检查服务器证书是否存在于 jdk 可信证书中,如果没有,它将继续检查您的自定义密钥库,如果找不到匹配项,它将失败。您甚至可以将其与更多自定义密钥库、pem 文件或证书列表等进一步链接。其他配置请参见此处:other possible configurations

这个库由我维护,你可以在这里找到它:https://github.com/Hakky54/sslcontext-kickstart

答案 6 :(得分:-1)

初始化SSLContext时,您提供了TrustManagers的数组。您提供两个:默认的一个使用JRE信任库,另一个使用您的JRE信任库。代表团模型在这里是错误的答案。