实现X509TrustManager-将部分验证传递给现有的验证器

24

我需要忽略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返回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验证。

我应该如何做到这一点?

3个回答

55
您可以使用以下类似的方法获取现有的默认信任管理器并将其包装在自己的信任管理器中:
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

15
非常感谢您的评论_"// 在此处使用null将TMF初始化为默认信任存储。"_ 如果这在API文档中就更好了;) - Dori
@kaze,如果您想比较“精确”的证书,您可以从任何源加载X.509证书(例如通过CertificateFactory的密钥库或PEM文件),然后您可以将链中呈现的内容(元素0)与参考实例进行比较。不确定equals比较什么,但您肯定可以比较两个X509Certificate实例上的getEncoded()结果(当然是字节数组比较,而不是引用)。 - Bruno
你好 @bruno,我有一个问题,为什么我们要创建finalTm,不能在customTm的实现中编写证书检查逻辑吗?谢谢! - java_doctor_101
@nitinsh99 你可以这样做,但重新实现该逻辑可能会非常复杂(请参阅文档,并且您需要检查TLS等的正确属性...)。这就是为什么将部分验证传递给现有验证器是有意义的原因。 - Bruno
@Bruno,我刚刚遇到了与自签名证书相关的问题。最后我从包装 finalTm 中获取了 getAcceptedIssuers() 并将它们与链中的第一个证书进行了比较。 - Chakrit W
显示剩余5条评论

8

不要实现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)),您可以使用它(尽管您可能希望初始化套接字工厂一次并重复使用它)。


0

尽管这个问题有一个有效的答案,但我想提出一个替代方案,它需要更少的自定义代码。我也想提到我创建了这个库来简化ssl设置。

看起来,使用这种选项时,您希望在满足自定义条件时信任对方(客户端或服务器),否则回退到trustmanager的默认验证。您可以尝试以下片段:

SSLFactory sslFactory = SSLFactory.builder()
          .withDefaultTrustMaterial() // uses jdk trusted certificates
          .withTrustEnhancer(((X509Certificate[] certificateChain, String authType, SSLEngine sslEngine) -> "Foo".equals(certificateChain[0].getIssuerX500Principal().getName()))
          .build();

SSLContext sslContext = sslFactory.getSslContext();

它将为您创建一个自定义的TrustManager,并在SSLContext中使用它,因此您不需要指定自定义的TrustManager。更多信息请参见此处:GitHub - SSLContext Kickstart 我正在维护这个库,希望它对其他人有用。


2
请说明SSLFactory来自您自己的库。 - Franz Wong

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接