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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19005318/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 13:18:09  来源:igfitidea点击:

Implementing X509TrustManager - passing on part of the verification to existing verifier

javasecuritysslx509certificatepki

提问by user93353

I need to ignore the PKIX path building exception

我需要忽略 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

I know how to do this by writing my own class implementing X509TrustManagerwhere I always return truefrom isServerTrusted.

我知道如何通过编写我自己的类来实现X509TrustManager我总是return true来自的地方isServerTrusted

However, I don't want to trust all servers & all clients.

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

  • I want all the default verification to be done for clients as is done currently.
  • For servers, I want to ignore server cert verification only for one particular cert but want to go ahead and verify it as is done currently (for eg. using cacerts store).
  • 我希望像当前​​那样为客户完成所有默认验证。
  • 对于服务器,我只想忽略一个特定证书的服务器证书验证,但想继续并按照当前的方式进行验证(例如,使用 cacerts 存储)。

How can I achieve something like this - i.e. pass on part of the verification to whatever was the X509TrustFactory object before I replaced it.

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

i.e. this is what I want to do

即这就是我想要做的

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
}

Also I don't want to disturb the existing isClientTrustedverification.

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

How can I do this?

我怎样才能做到这一点?

回答by Bruno

You can get hold of the existing default trust manager and wrap it in your own using something like this:

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

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

You can then implement your own logic around finalTm.checkServerTrusted(chain, authType);.

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

However, you should make sure you're making an exception for the specific certificate you want to ignore.

但是,您应该确保对要忽略的特定证书进行例外处理。

What you're doing in the following is letting through anycertificate with these Issuer DN and Subject DN (which isn't difficult to forge):

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

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

You could instead load the X509Certificateinstance from a known reference and compare the actual value in the chain.

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

In addition, checkClientTrustedand checkServerTrustedare not methods that return trueor false, but voidmethods that will succeed silently by default. If there's something wrong with the certificate you expect, throw a CertificateExceptionexplicitly.

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

回答by Johannes Brodwall

Instead of implementing X509TrustManagerto trust anycertificate, you can create a trust manager from the specific certificate in question. Load the certificate from a .p12or .jkskeystore or from a .crt-file (you can copy a certificate from the browser into a file, in Chrome by clicking the padlock and selecting Certificate). The code is shorter than implementing your own X509TrustManager:

您可以从相关的特定证书创建信任管理器,而不是实施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();
}

You can use it by calling HttpsURLConnection.setSSLSocketFactory(createSSLSocketFactory(crtFile))(you probably want to initialize the socket factory once and reuse it, though).

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