实施 X509TrustManager - 将部分验证传递给现有验证者

Posted

技术标签:

【中文标题】实施 X509TrustManager - 将部分验证传递给现有验证者【英文标题】:Implementing X509TrustManager - passing on part of the verification to existing verifier 【发布时间】: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 验证。

我该怎么做?

【问题讨论】:

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

【讨论】:

以上是关于实施 X509TrustManager - 将部分验证传递给现有验证者的主要内容,如果未能解决你的问题,请参考以下文章

java HttpsURLConnection怎么绕过证书,原理是啥

x509证书格式

在WCF服务上实现X509安全性时,不对加密体进行加密

Android5.0系统访问https要怎么做

KeyStore 和 KeyManager/TrustManager 的区别

Android 上的 HttpClient 和自定义 TrustManager