【问题标题】:How do I initialize a TrustManagerFactory with multiple sources of trust?如何初始化具有多个信任源的 TrustManagerFactory?
【发布时间】:2014-06-02 09:16:48
【问题描述】:

我的应用程序有一个个人密钥库,其中包含在本地网络中使用的受信任的自签名证书 - 比如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 就好了。如何获得指向默认密钥库的“默认信任管理器”?

【问题讨论】:

标签: java ssl x509 jsse


【解决方案1】:

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

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

【讨论】:

  • 我已经用 Java 1.8 (build 1.8.0_60-b27) 进行了尝试。但这对我不起作用。我遇到了同样的错误:PKIX 路径构建失败。
  • 但这不允许您在自定义密钥库中使用您的证书,只能使用系统上安装的 CA 证书。
  • 感谢您通过阅读源代码来解决这个问题!这简化了很多 cacerts 路径和名称处理杂耍!也让我感到惊讶的是,它也没有密码要求。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    我在 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();
        }
    
    }
    

    解决方案的详细说明在这里:https://blog.novoj.net/posts/2016-02-29-how-to-make-apache-httpclient-trust-lets-encrypt-certificate-authority/

    【讨论】:

    【解决方案4】:

    对于 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

    【讨论】:

      【解决方案5】:
      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;
      
          }
      
      }
      

      【讨论】:

      • 请不要只发布代码作为答案,还要解释您的代码的作用以及它如何解决问题的问题。带有解释的答案通常更有帮助、质量更好,并且更有可能吸引投票。
      【解决方案6】:

      虽然这个问题已有 6 年的历史,但我想分享我对这个挑战的解决方案。它使用与Cody A. Ray 相同的代码 sn-p,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

      【讨论】:

        猜你喜欢
        • 2017-04-06
        • 2021-10-02
        • 2011-04-22
        • 1970-01-01
        • 1970-01-01
        • 2016-08-23
        • 1970-01-01
        • 2014-08-15
        • 2017-01-09
        相关资源
        最近更新 更多