如何创建不信任任何服务器证书的 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)
...
我见过很多构建 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)
...