即使将我的自签名证书添加到cacerts之后,我仍然无法让Java信任它
我在本地计算机上运行nginx作为具有自签名证书的SSL反向代理。我已经生成了这样的证书:
openssl req -new -nodes -keyout server.key -out server.csr
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
我在提示输入Common Name时填写my-org.local
。在我的hosts文件中,my-org.local
是localhost的别名。
在浏览器中测试此设置,我收到一条警告,证明证书未经过已知权限的签名,这正是我所期望的。然后我告诉浏览器信任证书,这很有用。
接下来,我编写了这个小小的Java程序,以便能够验证我是否可以让Java信任该证书:
import java.net.*;
import java.io.*;
public class Main {
public static void main(String[] args) throws Exception {
String url = "https://my-org.local/";
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
int responseCode = con.getResponseCode();
System.out.println("\nSending 'GET' request to URL : " + url);
System.out.println("Response Code : " + responseCode);
}
}
如果我对此进行测试,例如https://google.com,一切都按预期工作。对于我的本地机器,我得到以下堆栈跟踪:
Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1884)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:276)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:270)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1341)
at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:153)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:868)
at sun.security.ssl.Handshaker.process_record(Handshaker.java:804)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1016)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1339)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1323)
at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:563)
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1300)
at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:468)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:338)
at Main.main(Main.java:10)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:385)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)
at sun.security.validator.Validator.validate(Validator.java:260)
at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:326)
at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:231)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:126)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1323)
... 13 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:196)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:268)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:380)
... 19 more
......这或多或少是我所期待的。
我的理解是,为了让Java信任我的自签名证书,我必须将证书添加到Java的cacerts中。在我的机器上(运行Mavericks的mac),这个文件可以在这里找到:
/System/Library/Frameworks/JavaVM.framework/Home/lib/security/cacerts
这是我尝试添加证书的方式:
sudo keytool -import -keystore /System/Library/Frameworks/JavaVM.framework/Home/lib/security/cacerts -storepass changeit -noprompt -alias myorg -file server.crt
然而这没有效果;我的小Java程序仍然死于相同的堆栈跟踪。我做错了什么?
答案 0 :(得分:0)
您收到SSLHandshakeException
这显然意味着证书未成功导入您的cacerts
文件。
因此,我们首先检查你的cacerts是否有证书:
echo 'changeit' | keytool -list -v -keystore $(find $JAVA_HOME -name cacerts) | grep 'Owner:'
changeit
是默认密码。这将只列出所有证书的所有者。您还可以使用自己的证书的关键字来检查它是否存在。
如果没有,this code在创建cacerts文件方面做得非常好。我正在添加代码以供参考:
import javax.net.ssl.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
public class CustomTrustManager implements X509TrustManager
{
private static final String JAVA_CA_CERT_FILE_NAME = "cacerts";
private static final String CLASSIC_JAVA_CA_CERT_FILE_NAME = "jssecacerts";
private static final int DEFAULT_HTTPS_PORT = 443;
private String[] hostsToTrust = {"server1.company.com", "server2.company.com"};
private char[] defaultCAKeystorePassphrase = "changeit".toCharArray();
private KeyStore certificateTrustStore;
private X509TrustManager defaultTrustManager;
public static void initSsl()
{
try
{
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[] { new CustomTrustManager() }, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
public CustomTrustManager()
{
try
{
initTrustStore();
addTrustedHosts();
initDefaultTrustManager();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException
{
defaultTrustManager.checkClientTrusted(chain, authType);
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException
{
defaultTrustManager.checkServerTrusted(chain, authType);
}
public X509Certificate[] getAcceptedIssuers()
{
return defaultTrustManager.getAcceptedIssuers();
}
private void initTrustStore() throws Exception
{
File javaTrustStoreFile = findJavaTrustStoreFile();
InputStream inputStream = new FileInputStream(javaTrustStoreFile);
certificateTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
certificateTrustStore.load(inputStream, defaultCAKeystorePassphrase);
inputStream.close();
}
private void addTrustedHosts() throws Exception
{
SSLContext tempConnectContext = SSLContext.getInstance("TLS");
ExtractX509CertTrustManager getX509CertTrustManager = new ExtractX509CertTrustManager();
tempConnectContext.init(null, new TrustManager[] { getX509CertTrustManager }, null);
SSLSocketFactory socketFactory = tempConnectContext.getSocketFactory();
for (String host : hostsToTrust)
{
SSLSocket socket = (SSLSocket) socketFactory.createSocket(host, DEFAULT_HTTPS_PORT);
// connect the socket to set the cert chain in getX509CertTrustManager
socket.startHandshake();
for (X509Certificate cert : getX509CertTrustManager.getCurrentChain())
{
if (!certificateTrustStore.isCertificateEntry(host))
{
certificateTrustStore.setCertificateEntry(host, cert);
}
}
}
}
private void initDefaultTrustManager() throws Exception
{
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(certificateTrustStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
for (TrustManager trustManager : trustManagers)
{
if (trustManager instanceof X509TrustManager)
{
defaultTrustManager = (X509TrustManager) trustManager;
break;
}
}
}
/**
* Trust Manager for the sole purpose of retrieving the X509 cert when a connection is made to a host we want
* to start trusting.
*/
private static class ExtractX509CertTrustManager implements X509TrustManager
{
private X509Certificate[] currentChain;
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { }
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException
{
currentChain = x509Certificates;
}
public X509Certificate[] getAcceptedIssuers() { return null; }
public X509Certificate[] getCurrentChain()
{
return currentChain;
}
}
private File findJavaTrustStoreFile()
{
File javaHome = new File(System.getProperty("java.home") + File.separatorChar + "lib" + File.separatorChar + "security");
File caCertsFile = new File(javaHome, JAVA_CA_CERT_FILE_NAME);
if (!caCertsFile.exists() || !caCertsFile.isFile())
{
caCertsFile = new File(javaHome, CLASSIC_JAVA_CA_CERT_FILE_NAME);
}
return caCertsFile;
}
}
快速入门:
将您的主机添加到hostsToTrust字符串数组。
调用CustomTrustManager.initSsl()。
- 醇>
与您的主机建立SSL连接。
话虽这么说,我不建议更新cacerts
,因为它位于一个公共JVM位置,因此每个Java应用程序都使用它。您应该为正在处理的特定应用程序创建自定义trustStore,并且只允许该应用程序信任自签名证书。结帐this answer了解如何操作。