如何创建不信任任何服务器证书的 SSLContext?

How to create an SSLContext that will trust no server certificates?

我见过很多构建 SSLContext 接受所有服务器证书的示例。对于我的测试用例,我试图做完全相反的事情并强制客户端拒绝服务器的证书。

所以我试图创建一个不包含根证书的 KeyStore 对象,但是当我尝试使用它时,我得到一个带有消息 'the trustAnchors parameter must be non-empty' 的 InvalidAlgorithmParameterException。我试过这个:

KeyStore emptyTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
emptyTrustStore.load(null, null);
sslcontext = SSLContexts.custom().loadTrustMaterial(emptyTrustStore, new TrustNoOneStrategy()).build();

还有这个:

KeyStore emptyTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
emptyTrustStore.setCertificateEntry("notreal", null);
sslcontext = SSLContexts.custom().loadTrustMaterial(emptyTrustStore, new TrustNoOneStrategy()).build();

和(来自评论中的一个想法),这个:

KeyStore emptyTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
sslcontext = SSLContexts.custom().loadTrustMaterial(null, new TrustNoOneStrategy()).build();

但是 none 这些方法解决了问题。

显然我可以简单地创建一个包含虚拟根证书的 JKS 文件并使用 SSLContexts.loadTrustMaterial(File) 方法加载它,但这看起来真的很难看:肯定有一种方法可以在代码中做到这一点?

试试这个:

SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, new TrustManager[]{new X509TrustManager() {
    @Override
    public void checkClientTrusted(X509Certificate[] x509Certificates, String s)
            throws CertificateException {
    }

    @Override
    public void checkServerTrusted(X509Certificate[] x509Certificates, String s)
            throws CertificateException {
        throw new CertificateException();
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }
}}, null);

请注意,信任管理器将针对每个质询抛出异常。

您可以看到拒绝不良 Whosebug 证书的上下文:

HttpsURLConnection connection =
        (HttpsURLConnection)
                new URL("https://whosebug.com/").openConnection();
connection.setSSLSocketFactory(sslContext.getSocketFactory());
connection.getResponseCode();

您不需要为此创建 SSLContext。只需通过关联系统 属性.

将其指向一个空的信任库

感谢您的建议。我最终得到的解决方案是使用 OpenSSL 创建一个虚拟根 CA,并将 PEM 格式的证书作为 String 值包含在我的测试中,从 String 构建一个 X509Certificate ],将其安装在新创建的 KeyStore 对象中,然后使用该 KeyStore 作为信任库。

由于我创建了证书,然后在获得证书后立即删除了私钥,所以我知道永远不会有这个虚拟 CA 实际签署的服务器证书。

KeyStore dummyTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
    try {
        dummyTrustStore.load(null, null);
        dummyTrustStore.setCertificateEntry("dummyroot", buildDummyCertificate());
    } catch (CertificateException | IOException e) {
        // handle exception
    }
sslcontext = SSLContexts.custom().loadTrustMaterial(dummyTrustStore, new TrustNoOneStrategy()).build();

其中 buildDummyCertificate() 方法是:

private X509Certificate buildDummyCertificate() throws CertificateException {
    CertificateFactory cFactory = CertificateFactory.getInstance("X.509");
    ByteArrayInputStream is = new ByteArrayInputStream(PEM_CERTIFICATE.getBytes(StandardCharsets.US_ASCII));
    return (X509Certificate) cFactory.generateCertificate(is);
}

private static final String PEM_CERTIFICATE = "-----BEGIN CERTIFICATE-----\n"
                                                  + "MIIEMDCCAxigAwIBAgI..."
                                                  ....
                                                  + "...n1xJLO3k=-----END CERTIFICATE-----";

一个有趣的问题是 -----BEGIN CERTIFICATE----- 之后的换行符是必需的:没有这个,证书创建将失败并出现此错误:

java.security.cert.CertificateException: Could not parse certificate: java.io.IOException: Incomplete data
at sun.security.provider.X509Factory.engineGenerateCertificate(X509Factory.java:104)
at java.security.cert.CertificateFactory.generateCertificate(CertificateFactory.java:339)
...