基于 HTTPS/SSL 的 Java 客户端证书

2022-08-31 08:51:01

我正在使用Java 6,并尝试使用客户端证书针对远程服务器创建一个。
服务器使用自签名的根证书,并要求提供受密码保护的客户端证书。我已将服务器根证书和客户端证书添加到我在(OSX 10.5)中找到的默认java密钥库。密钥库文件的名称似乎表明客户机证书不应该进入那里?HttpsURLConnection/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts

无论如何,将根证书添加到此存储中解决了臭名昭着的问题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

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


答案 1

终于解决了;)。这里有一个强烈的提示(Gandalfs的答案也触及了它)。缺失的链接(大部分)是下面的第一个参数,在某种程度上,我忽略了密钥库和信任库之间的区别。

必须将自签名服务器证书导入到信任库中:

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

答案 2

虽然不建议这样做,但您也可以完全禁用 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) {}
  }
}