2009-05-17 106 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(); 
}