21

我需要忽略 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我总是return trueisServerTrusted.

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

  • 我希望像目前所做的那样为客户完成所有默认验证。
  • 对于服务器,我只想忽略一个特定证书的服务器证书验证,但想继续验证它,就像目前所做的那样(例如,使用 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验证。

我怎样才能做到这一点?

4

3 回答 3

49

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

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实例并比较链中的实际值。

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

于 2013-09-25T13:08:51.850 回答
7

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

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))(不过,您可能希望初始化一次套接字工厂并重用它)。

于 2019-10-25T11:10:16.350 回答
0

虽然这个问题有一个有效的答案,但我想提出一个需要较少自定义代码的替代方案。当自定义条件为真或回退到信任管理器的默认验证时,您似乎希望使用这种选项信任对方(客户端或服务器)。您可以尝试以下代码段:

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

SSLContext sslContext = sslFactory.getSslContext();

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

于 2022-03-01T15:08:19.583 回答