这看起来是一个标准问题,但我在任何地方都找不到明确的方向。
我有java代码试图连接到一个可能自签名(或过期)证书的服务器。代码报告以下错误:
[HttpMethodDirector] I/O exception (javax.net.ssl.SSLHandshakeException) caught
when processing request: sun.security.validator.ValidatorException: PKIX path
building failed: sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target
根据我的理解,我必须使用keytool并告诉java允许此连接是OK的。
解决此问题的所有说明都假设我完全熟练使用keytool,例如
为服务器生成私有密钥并将其导入密钥存储库
有人能给我详细说明吗?
我正在运行unix,所以bash脚本将是最好的。
不确定这是否重要,但在jboss中执行的代码。
有一个更好的替代信任所有证书的方法:创建一个TrustStore,它专门信任给定的证书,并使用它来创建一个SSLContext,从中获取SSLSocketFactory在HttpsURLConnection上进行设置。以下是完整的代码:
File crtFile = new File("server.crt");
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(crtFile));
// Or if the crt-file is packaged into a jar file:
// CertificateFactory.getInstance("X.509").generateCertificate(this.class.getClassLoader().getResourceAsStream("server.crt"));
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("server", certificate);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
connection.setSSLSocketFactory(sslContext.getSocketFactory());
您也可以直接从文件加载KeyStore,或者从任何受信任的来源检索X.509证书。
注意,对于这段代码,将不使用cacerts中的证书。这个特定的HttpsURLConnection将只信任这个特定的证书。
您可以通过在RestTemplate级别禁用它来实现这一点。
注意,此TrustStrategy将信任所有证书,并且使用NoopHostnameVerifier()禁用了主机名验证。
public RestTemplate getRestTemplate() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
TrustStrategy acceptingTrustStrategy = (x509Certificates, s) -> true;
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
return new RestTemplate(requestFactory);
}
有一个更好的替代信任所有证书的方法:创建一个TrustStore,它专门信任给定的证书,并使用它来创建一个SSLContext,从中获取SSLSocketFactory在HttpsURLConnection上进行设置。以下是完整的代码:
File crtFile = new File("server.crt");
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(crtFile));
// Or if the crt-file is packaged into a jar file:
// CertificateFactory.getInstance("X.509").generateCertificate(this.class.getClassLoader().getResourceAsStream("server.crt"));
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("server", certificate);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
connection.setSSLSocketFactory(sslContext.getSocketFactory());
您也可以直接从文件加载KeyStore,或者从任何受信任的来源检索X.509证书。
注意,对于这段代码,将不使用cacerts中的证书。这个特定的HttpsURLConnection将只信任这个特定的证书。
Apache HttpClient 4.5支持接受自签名证书:
SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial(new TrustSelfSignedStrategy())
.build();
SSLConnectionSocketFactory socketFactory =
new SSLConnectionSocketFactory(sslContext);
Registry<ConnectionSocketFactory> reg =
RegistryBuilder.<ConnectionSocketFactory>create()
.register("https", socketFactory)
.build();
HttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg);
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(cm)
.build();
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse sslResponse = httpClient.execute(httpGet);
这将构建一个SSL套接字工厂,它将使用TrustSelfSignedStrategy,将其注册到自定义连接管理器,然后使用该连接管理器执行HTTP GET。
我同意那些高呼“不要在生产环境中这样做”的人,但是在生产环境之外接受自签名证书是有用例的;我们在自动化集成测试中使用它们,因此即使不在生产硬件上运行,我们也在使用SSL(就像在生产中一样)。
在RHEL上,您可以从RHEL 6的新版本开始使用update-ca-trust,而不是像上面的注释所建议的那样使用keytool。您需要拥有pem格式的证书。然后
trust anchor <cert.pem>
编辑/etc/pki/ca-trust/source/cert.P11-kit,将“证书类别:other-entry”修改为“证书类别:authority”。(或使用sed在脚本中完成此操作。)然后做
update-ca-trust
几点注意事项:
I couldn't find "trust" on my RHEL 6 server and yum didn't offer to install it. I ended up using it on an RHEL 7 server and copying the .p11-kit file over.
To make this work for you, you may need to do update-ca-trust enable. This will replace /etc/pki/java/cacerts with a symbolic link pointing to /etc/pki/ca-trust/extracted/java/cacerts. (So you might want to back up the former first.)
If your java client uses cacerts stored in some other location, you'll want to manually replace it with a symlink to /etc/pki/ca-trust/extracted/java/cacerts, or replace it with that file.