Java客户端证书通过HTTPS / SSL
我正在使用Java 6,并试图使用客户端证书在远程服务器上创buildHttpsURLConnection
。
服务器正在使用自签名根证书,并要求提供受密码保护的客户端证书。 我已经将服务器根证书和客户端证书添加到我在/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts
10.5)中find的默认Java密钥库。 密钥库文件的名称似乎表明客户端证书不应该在那里?
无论如何,将根证书添加到此商店解决了臭名昭着的javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed' problem.
但是,我现在坚持如何使用客户端证书。 我已经尝试了两种方法,并没有让我到任何地方。
首先,首选,尝试:
SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); URL url = new URL("https://somehost.dk:3049"); HttpsURLConnection conn = (HttpsURLConnection)url.openConnection(); conn.setSSLSocketFactory(sslsocketfactory); InputStream inputstream = conn.getInputStream(); // The last line fails, and gives: // javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
我试过跳过HttpsURLConnection类(不理想,因为我想与服务器通话HTTP),而不是这样做:
SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket("somehost.dk", 3049); InputStream inputstream = sslsocket.getInputStream(); // do anything with the inputstream results in: // java.net.SocketTimeoutException: Read timed out
我甚至不确定客户端证书是否是这里的问题。
虽然不推荐,但您也可以一起禁用SSL证书validation:
import javax.net.ssl.*; import java.security.SecureRandom; import java.security.cert.X509Certificate; public class SSLTool { public static void disableCertificateValidation() { // Create a trust manager that does not validate certificate chains TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } public void checkClientTrusted(X509Certificate[] certs, String authType) {} public void checkServerTrusted(X509Certificate[] certs, String authType) {} }}; // Ignore differences between given hostname and certificate hostname HostnameVerifier hv = new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; } }; // Install the all-trusting trust manager try { SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); HttpsURLConnection.setDefaultHostnameVerifier(hv); } catch (Exception e) {} } }
终于解决了它)。 这里有一个强烈的暗示(甘道夫的回答也触及了一下)。 缺失的链接(大部分)是下面的第一个参数,在某种程度上,我忽略了密钥库和信任库之间的差异。
自签名服务器证书必须导入到信任库中:
keytool -import -alias gridserver -file gridserver.crt -storepass $ PASS -keystore gridserver.keystore
这些属性需要设置(在命令行或代码中):
-Djavax.net.ssl.keyStoreType=pkcs12 -Djavax.net.ssl.trustStoreType=jks -Djavax.net.ssl.keyStore=clientcertificate.p12 -Djavax.net.ssl.trustStore=gridserver.keystore -Djavax.net.debug=ssl # very verbose debug -Djavax.net.ssl.keyStorePassword=$PASS -Djavax.net.ssl.trustStorePassword=$PASS
工作示例代码:
SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); URL url = new URL("https://gridserver:3049/cgi-bin/ls.py"); HttpsURLConnection conn = (HttpsURLConnection)url.openConnection(); conn.setSSLSocketFactory(sslsocketfactory); InputStream inputstream = conn.getInputStream(); InputStreamReader inputstreamreader = new InputStreamReader(inputstream); BufferedReader bufferedreader = new BufferedReader(inputstreamreader); String string = null; while ((string = bufferedreader.readLine()) != null) { System.out.println("Received " + string); }
您是否设置了KeyStore和/或TrustStore系统属性?
java -Djavax.net.ssl.keyStore=pathToKeystore -Djavax.net.ssl.keyStorePassword=123456
或从代码
System.setProperty("javax.net.ssl.keyStore", pathToKeyStore);
与javax.net.ssl.trustStore一样
如果您使用Axis框架处理Web服务调用,则有一个更简单的答案。 如果您希望您的客户端能够调用SSL Web服务并忽略SSL证书错误,只需在调用任何Web服务之前添加此语句即可:
System.setProperty("axis.socketSecureFactory", "org.apache.axis.components.net.SunFakeTrustSocketFactory");
关于这是一个非常糟糕的事情在生产环境中做通常的免责声明适用。
我在Axis wiki上find了这个。
对我来说,这是使用Apache HttpComponents〜HttpClient 4.x:
KeyStore keyStore = KeyStore.getInstance("PKCS12"); FileInputStream instream = new FileInputStream(new File("client-p12-keystore.p12")); try { keyStore.load(instream, "helloworld".toCharArray()); } finally { instream.close(); } // Trust own CA and all self-signed certs SSLContext sslcontext = SSLContexts.custom() .loadKeyMaterial(keyStore, "helloworld".toCharArray()) //.loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()) //custom trust store .build(); // Allow TLSv1 protocol only SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslcontext, new String[] { "TLSv1" }, null, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); //TODO CloseableHttpClient httpclient = HttpClients.custom() .setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER) //TODO .setSSLSocketFactory(sslsf) .build(); try { HttpGet httpget = new HttpGet("https://localhost:8443/secure/index"); System.out.println("executing request" + httpget.getRequestLine()); CloseableHttpResponse response = httpclient.execute(httpget); try { HttpEntity entity = response.getEntity(); System.out.println("----------------------------------------"); System.out.println(response.getStatusLine()); if (entity != null) { System.out.println("Response content length: " + entity.getContentLength()); } EntityUtils.consume(entity); } finally { response.close(); } } finally { httpclient.close(); }
P12文件包含用BouncyCastle创build的客户端证书和客户端私钥:
public static byte[] convertPEMToPKCS12(final String keyFile, final String cerFile, final String password) throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException, NoSuchProviderException { // Get the private key FileReader reader = new FileReader(keyFile); PEMParser pem = new PEMParser(reader); PEMKeyPair pemKeyPair = ((PEMKeyPair)pem.readObject()); JcaPEMKeyConverter jcaPEMKeyConverter = new JcaPEMKeyConverter().setProvider("BC"); KeyPair keyPair = jcaPEMKeyConverter.getKeyPair(pemKeyPair); PrivateKey key = keyPair.getPrivate(); pem.close(); reader.close(); // Get the certificate reader = new FileReader(cerFile); pem = new PEMParser(reader); X509CertificateHolder certHolder = (X509CertificateHolder) pem.readObject(); java.security.cert.Certificate x509Certificate = new JcaX509CertificateConverter().setProvider("BC") .getCertificate(certHolder); pem.close(); reader.close(); // Put them into a PKCS12 keystore and write it to a byte[] ByteArrayOutputStream bos = new ByteArrayOutputStream(); KeyStore ks = KeyStore.getInstance("PKCS12", "BC"); ks.load(null); ks.setKeyEntry("key-alias", (Key) key, password.toCharArray(), new java.security.cert.Certificate[]{x509Certificate}); ks.store(bos, password.toCharArray()); bos.close(); return bos.toByteArray(); }
我使用Apache commons HTTP Client软件包在我当前的项目中执行此操作,并且可以正常使用SSL和自签名证书(在像您提到的那样将其安装到cacerts中之后)。 请看这里:
我认为你的服务器证书有问题,不是有效的证书(我认为这是“handshake_failure”在这种情况下的意思):
将您的服务器证书导入客户端JRE上的trustcacerts密钥库。 这很容易使用keytool完成:
keytool -import -alias <provide_an_alias> -file <certificate_file> -keystore <your_path_to_jre>/lib/security/cacerts
使用下面的代码
-Djavax.net.ssl.keyStoreType=pkcs12
要么
System.setProperty("javax.net.ssl.keyStore", pathToKeyStore);
根本不需要。 也不需要创build自己的定制SSL工厂。
我也遇到同样的问题,在我的情况下有一个问题, 完整的证书链没有导入到信任库。 使用keytool实用工具右键导入证书,也可以在记事本中打开cacerts文件,查看是否导入完整的证书链。 检查您在导入证书时提供的别名,打开证书并查看它包含的证书数量,在cacerts文件中应该有相同数量的证书。
另外,cacerts文件应该在运行应用程序的服务器上configuration,两台服务器将使用公钥/私钥进行相互身份validation。