这看起来是一个标准问题,但我在任何地方都找不到明确的方向。
我有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中执行的代码。
受下面annser的启发,我找到了一种信任自签名CA并保持信任默认CA的方法。
File file = new File(System.getProperty("java.home"), "lib/security/cacerts");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(file), "changeit".toCharArray());
InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream("testCer.cer");
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(resourceAsStream);
keyStore.setCertificateEntry("my-server-alias", certificate);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContexts.createDefault();
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
// check domain
// SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);
// not check domain
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext,
new String[]{"TLSv1","TLSv1.1","TLSv1.2","SSLv3"},null, NoopHostnameVerifier.INSTANCE);
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
factory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(factory);
有一个更好的替代信任所有证书的方法:创建一个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将只信任这个特定的证书。
在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.
这是第一个有太多答案的古老问题,我认为我可以提供一个更有用的想法:如果服务器所有者拒绝以可信任的方式离线向我提供他们的证书,我会使用这个选项:
从服务器本身检索证书(使用命令行工具而不是浏览器)
将该证书添加到java密钥存储库以信任它。将显示证书的详细信息,以验证它。
# HOSTNAME_PORT is the host that you want to connect to - example: HOSTNAME_PORT=stackoverflow.com:443
HOSTNAME_PORT=hostname_part_of_url_without_https:port
# whatever you want to call the key within the Java key store
MY_KEY_ALIAS=the_key_I_added_with_help_from_stackoverflow
openssl s_client -showcerts -connect $HOSTNAME_PORT </dev/null 2>/dev/null|openssl x509 -outform PEM >mycertfile.pem
sudo keytool -trustcacerts -keystore $JAVA_HOME/jre/lib/security/cacerts/pki/java/cacerts -storepass changeit -importcert -alias $MY_KEY_ALIAS -file mycertfile.pem
在出现提示时输入yes,但只有在您真正信任显示给您的证书并希望将其添加到计算机的全局java密钥存储库时才可以这样做。
通知你:$ JAVA_HOME / jre / lib /安全/除
在我的情况下(CentOS 7)指向:
/etc/pki/java/cacerts
我找到了一个证书提供者,该提供者不属于JDK 8u74的默认JVM受信任主机。提供者是www.identrust.com,但这不是我试图连接的域。该域已从该提供者获得其证书。请参见交叉根覆盖信任JDK/JRE中的默认列表?——读几个条目。另请参阅哪些浏览器和操作系统支持Let 's Encrypt。
因此,为了连接到我感兴趣的域名,它有一个由identrust.com颁发的证书,我执行了以下步骤。基本上,我必须获得identrust.com (DST根CA X3)证书才能被JVM信任。我可以使用Apache HttpComponents 4.5这样做:
1:从证书链下载说明中的indettrust获取证书。单击DST根CA X3链接。
2:保存到“DST Root CA X3.pem”文件中。确保在文件的开头和结尾添加行“-----BEGIN CERTIFICATE-----”和“-----END CERTIFICATE-----”。
3:创建java的keystore文件cacerts。JKS,命令如下:
keytool -import -v -trustcacerts -alias IdenTrust -keypass yourpassword -file dst_root_ca_x3.pem -keystore cacerts.jks -storepass yourpassword
4:复制生成的cacerts。将JKS密钥存储库保存到java/(maven)应用程序的资源目录中。
5:使用以下代码加载该文件,并将其附加到Apache 4.5 HttpClient。这将解决所有拥有indetrust.com颁发的证书的域的问题,util oracle将证书包含到JRE默认的密钥存储库中。
SSLContext sslcontext = SSLContexts.custom()
.loadTrustMaterial(new File(CalRestClient.class.getResource("/cacerts.jks").getFile()), "yourpasword".toCharArray(),
new TrustSelfSignedStrategy())
.build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[] { "TLSv1" },
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
当项目构建时,cacerts。JKS将被复制到类路径中并从那里加载。我没有,在这个时间点上,测试其他ssl站点,但如果上面的代码“链”在这个证书,那么他们也会工作,但是,我不知道。
参考:自定义SSL上下文和如何使用Java HttpsURLConnection接受自签名证书?