【问题标题】:Implementing X509TrustManager - passing on part of the verification to existing verifier实施 X509TrustManager - 将部分验证传递给现有验证者
【发布时间】:2013-10-01 01:38:50
【问题描述】:

我需要忽略 PKIX 路径构建异常

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException:
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderExc
ption: unable to find valid certification path to requested target

我知道如何通过编写自己的实现 X509TrustManager 的类来做到这一点,而我总是从 isServerTrusted 实现 return true

但是,我不想信任所有服务器和所有客户端。

  • 我希望像当前​​一样为客户完成所有默认验证。
  • 对于服务器,我只想忽略一个特定证书的服务器证书验证,但想继续验证它,就像目前所做的那样(例如,使用 cacerts 存储)。

我怎样才能实现这样的事情 - 即在我替换之前将部分验证传递给 X509TrustFactory 对象。

即这就是我想做的事情

public boolean isServerTrusted(X509Certificate[] chain)
{
    if(chain[0].getIssuerDN().getName().equals("MyTrustedServer") && chain[0].getSubjectDN().getName().equals("MyTrustedServer"))
        return true;

    // else I want to do whatever verification is normally done
}

另外我不想打扰现有的isClientTrusted 验证。

我该怎么做?

【问题讨论】:

    标签: java security ssl x509certificate pki


    【解决方案1】:

    您可以获取现有的默认信任管理器,并使用以下方式将其包装在您自己的中:

    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 x509Tm = null;
    for (TrustManager tm : tmf.getTrustManagers()) {
        if (tm instanceof X509TrustManager) {
            x509Tm = (X509TrustManager) tm;
            break;
        }
    }
    
    // Wrap it in your own class.
    final X509TrustManager finalTm = x509Tm;
    X509TrustManager customTm = new X509TrustManager() {
        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return finalTm.getAcceptedIssuers();
        }
    
        @Override
        public void checkServerTrusted(X509Certificate[] chain,
                String authType) throws CertificateException {
            finalTm.checkServerTrusted(chain, authType);
        }
    
        @Override
        public void checkClientTrusted(X509Certificate[] chain,
                String authType) throws CertificateException {
            finalTm.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);
    

    然后您可以围绕finalTm.checkServerTrusted(chain, authType); 实现您自己的逻辑。

    但是,您应该确保为要忽略的特定证书设置例外。

    您在下面所做的是让具有这些颁发者 DN 和主题 DN(不难伪造)的任何证书通过:

    if(chain[0].getIssuerDN().getName().equals("MyTrustedServer") && chain[0].getSubjectDN().getName().equals("MyTrustedServer"))
        return true;
    

    您可以改为从已知引用加载 X509Certificate 实例并比较链中的实际值。

    此外,checkClientTrustedcheckServerTrusted 不是返回 truefalse 的方法,而是默认情况下会静默成功的 void 方法。如果您期望的证书有问题,请明确抛出CertificateException

    【讨论】:

    • +1 非常感谢您的评论 "// 在此处使用 null 会使用默认信任库初始化 TMF。"。如果这在 api 文档中会很好;)
    • 您能否解释一下并给我看这部分的示例:您可以改为从已知引用加载 X509Certificate 实例并比较链中的实际值。跨度>
    • @kaze,如果你想比较 exact 证书,你可以从任何你想要的来源加载 X.509 证书(例如,通过 CertificateFactory 的 PEM 文件的密钥库),然后您可以将链(元素 0)中呈现的内容与参考实例进行比较。不确定equals 比较什么,但您当然可以比较getEncoded() 在两个X509Certificate 实例上的结果(当然是字节数组比较,而不是引用)。
    • @Dori 说真的。仅凭这一点,这篇文章就值得一票。
    • @OnePunchMan 您可以将PEM 编码的证书存储在您的类路径中,并比较X509Certificate 实例本身。一旦攻击者无法使用假证书篡改您的 jar,您就可以完全控制您的信任库
    【解决方案2】:

    您可以从相关的特定证书创建信任管理器,而不是实施 X509TrustManager 来信任 任何 证书。从.p12.jks 密钥库或.crt 文件加载证书(您可以在Chrome 中单击挂锁并选择证书,将证书从浏览器复制到文件中)。代码比实现自己的X509TrustManager要短:

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

    您可以通过调用 HttpsURLConnection.setSSLSocketFactory(createSSLSocketFactory(crtFile)) 来使用它(不过,您可能希望初始化一次套接字工厂并重用它)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-07-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多