2009-05-17 118 views
103

我正在使用Java 6,并试图使用客户端证书针对远程服务器创建HttpsURLConnection
服务器正在使用自签名根证书,并要求提供受密码保护的客户端证书。我已将服务器根证书和客户端证书添加到我在/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts(OSX 10.5)中找到的默认Java密钥库中。 密钥库文件的名称似乎暗示客户端证书不应该进入那里?通过HTTPS/SSL的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 

我甚至不确定客户端证书是否是这里的问题。

回答

82

终于解决了它;)。有一个强烈的暗示here(甘道夫的回答也触动了一下)。缺失的链接(主要)是下面的第一个参数,并且在某种程度上我忽略了密钥库和信任库之间的差异。

自签名的服务器证书必须导入到一个信任:

的keytool -import -alias -file gridserver -storepass gridserver.crt $ PASS -keystore gridserver。密钥库

这些属性需要进行设置(无论是在命令行或代码):

-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); 
} 
4

我使用Apache commons HTTP客户端软件包在我当前的项目中执行此操作,并且它可以正常使用SSL和自签名证书(将其安装到您提到的cacerts之后)。请看看这里:

http://hc.apache.org/httpclient-3.x/tutorial.html

http://hc.apache.org/httpclient-3.x/sslguide.html

+1

这看起来像一个非常整洁的软件包,但应该使它工作的类“AuthSSLProtocolSocketFactory”在4.0beta(尽管发行说明中指出它是)或3.1中都不是正式发布的一部分。 我已经绕过它了一下,现在似乎永久卡住了5分钟的挂起,然后才断开连接。这真的很奇怪 - 如果我将CA和客户端证书加载到任何浏览器中,它就会飞走。 – Jan 2009-05-19 11:58:30

+1

Apache HTTP Client 4可以直接使用`SSLContext`,所以你可以这样配置所有这些,而不是使用`AuthSSLProtocolSocketFactory`。 – Bruno 2010-09-14 22:00:26

18

你有设置密钥和/或信任库系统属性?

java -Djavax.net.ssl.keyStore=pathToKeystore -Djavax.net.ssl.keyStorePassword=123456 

或从与代码

System.setProperty("javax.net.ssl.keyStore", pathToKeyStore); 

同样的,javax.net.ssl.trustStore中

3

我认为你有一个问题,你的服务器证书,是不是一个有效的证书(我认为这是“handshake_failure”在这种情况下的意思):

将您的服务器证书导入客户端JRE上的trustcacerts密钥库。这是很容易与keytool完成:

keytool 
    -import 
    -alias <provide_an_alias> 
    -file <certificate_file> 
    -keystore <your_path_to_jre>/lib/security/cacerts 
+0

我尝试清理并重新开始,握手失败消失。现在,在连接终止之前,我只需要5分钟的静默沉默:o – Jan 2009-05-19 12:00:06

78

虽然不推荐,您也可以禁用产品总数的SSL证书验证:

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) {} 
    } 
} 
+0

如果您使用的是Axis框架,则可以以更简单的方式执行同样的操作。请参阅下面的答案。 – 2012-03-13 19:14:58

+62

应该注意的是,禁用像这样的证书验证会打开与可能的MITM攻击的连接:**不要在生产中使用**。 – Bruno 2012-04-30 09:13:37

+1

代码不会编译,谢天谢地。这个'解决方案'根本上不安全。 – EJP 2012-04-30 10:16:59

14

如果你正在处理一个Web服务使用Axis框架调用,有一个更简单的答案。如果所有想要的是你的客户端能够调用SSL的Web服务,而忽略SSL证书错误,只是把这个语句调用任何Web服务之前:这个是

System.setProperty("axis.socketSecureFactory", "org.apache.axis.components.net.SunFakeTrustSocketFactory");

通常免责声明非常不适合在生产环境中使用。

我在the Axis wiki发现了这个。

1

使用以下代码

-Djavax.net.ssl.keyStoreType=pkcs12 

System.setProperty("javax.net.ssl.keyStore", pathToKeyStore); 

完全不必须的。也不需要创建自己的定制SSL工厂。

我也遇到同样的问题,在我的情况下有一个问题,完整的证书链没有导入到信任库。使用keytool工具右键从证书导入证书,您也可以在记事本中打开cacerts文件,并查看是否导入了完整的证书链。请检查您在导入证书时提供的别名,打开证书并查看它包含的证书数量,在cacerts文件中应该有相同数量的证书。

另外,cacerts文件应该在运行应用程序的服务器上配置,两台服务器将使用公钥/私钥进行相互身份验证。

5

对于我来说,这是用什么工作的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的创建:

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(); 
}