通过 HTTPS/SSL 的 Java 客户端证书

发布于 2024-07-20 21:40:49 字数 1375 浏览 6 评论 0原文

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

无论如何,将根证书添加到此存储解决了臭名昭著的 javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX 路径构建失败的问题。

但是,我现在陷入困境如何使用客户端证书。 我尝试了两种方法,但都没有成功。
首先,也是首选,尝试:

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

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

I am using Java 6 and am trying to create an HttpsURLConnection against a remote server, using a client certificate.
The server is using an selfsigned root certificate, and requires that a password-protected client certificate is presented. I've added the server root certificate and the client certificate to a default java keystore which I found in /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts (OSX 10.5).
The name of the keystore file seems to suggest that the client certificate is not supposed to go in there?

Anyway, adding the root certificate to this store solved the infamous javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed' problem.

However, I'm now stuck on how to use the client certificate. I've tried two approaches and neither gets me anywhere.
First, and preferred, try:

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

I've tried skipping the HttpsURLConnection class (not ideal since I want to talk HTTP with the server), and do this instead:

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

I am not even sure that the client certificate is the problem here.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

酒解孤独 2024-07-27 21:40:49

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

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

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

Finally solved it ;). Got a strong hint here (Gandalfs answer touched a bit on it as well). The missing links was (mostly) the first of the parameters below, and to some extent that I overlooked the difference between keystores and truststores.

The self-signed server certificate must be imported into a truststore:

keytool -import -alias gridserver -file gridserver.crt -storepass $PASS -keystore gridserver.keystore

These properties need to be set (either on the commandline, or in code):

-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

Working example code:

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);
}
看轻我的陪伴 2024-07-27 21:40:49

虽然不建议,您也可以使用来自 Java 开发人员年鉴

导入javax.net.ssl.*;   导入 java.security.SecureRandom;   进口 
  java.security.cert.X509Certificate; 

  公共类 SSLTool { 

    公共静态无效禁用证书验证(){ 
      // 创建一个不验证证书链的信任管理器 
      TrustManager[] trustAllCerts = 新 TrustManager[] {  
        新的 X509TrustManager() { 
          公共 X509Certificate[] getAcceptedIssuers() {  
            返回新的 X509Certificate[0];  
          } 
          公共无效checkClientTrusted(X509Certificate []证书,字符串authType){} 
          公共无效checkServerTrusted(X509Certificate []证书,字符串authType){} 
      }}; 

      // 忽略给定主机名和证书主机名之间的差异 
      主机名验证器 hv = new 主机名验证器() { 
        公共布尔验证(字符串主机名,SSLSession会话){返回true;   } 
      }; 

      // 安装全信任的信任管理器 
      尝试 { 
        SSLContext sc = SSLContext.getInstance("SSL"); 
        sc.init(null, trustAllCerts, new SecureRandom()); 
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); 
        HttpsURLConnection.setDefaultHostnameVerifier(hv); 
      } catch (异常 e) {} } } 
  

While not recommended, you can also disable SSL cert validation altogether, using the following code that came from The Java Developers Almanac:

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) {}   } }
缪败 2024-07-27 21:40:49

您是否设置了 KeyStore 和/或 TrustStore 系统属性?

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

或来自代码

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

与 javax.net.ssl.trustStore 相同

Have you set the KeyStore and/or TrustStore System properties?

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

or from with the code

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

Same with javax.net.ssl.trustStore

岁吢 2024-07-27 21:40:49

如果您正在使用 Axis 框架处理 Web 服务调用,则有一个更简单的答案。 如果您希望客户端能够调用 SSL Web 服务并忽略 SSL 证书错误,只需在调用任何 Web 服务之前放置此语句即可:

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

关于在生产环境中这样做是一件非常糟糕的事情的常见免责声明适用。

我在 Axis wiki 中找到了此内容。

If you are dealing with a web service call using the Axis framework, there is a much simpler answer. If all want is for your client to be able to call the SSL web service and ignore SSL certificate errors, just put this statement before you invoke any web services:

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

The usual disclaimers about this being a Very Bad Thing to do in a production environment apply.

I found this at the Axis wiki.

话少心凉 2024-07-27 21:40:49

对我来说,这就是使用 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();
}

For me, this is what worked using 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();
    }

The P12 file contains the client certificate and client private key, created with 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();
}
花心好男孩 2024-07-27 21:40:49

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

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

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

I use the Apache commons HTTP Client package to do this in my current project and it works fine with SSL and a self-signed cert (after installing it into cacerts like you mentioned). Please take a look at it here:

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

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

梦断已成空 2024-07-27 21:40:49

我认为您的服务器证书有问题,不是有效的证书(我认为这就是“handshake_failure”在这种情况下的含义):

将您的服务器证书导入客户端 JRE 上的 trustcacerts 密钥库。 使用 keytool 可以轻松完成此操作:

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

I think you have an issue with your server certificate, is not a valid certificate (I think this is what "handshake_failure" means in this case):

Import your server certificate into your trustcacerts keystore on client's JRE. This is easily done with keytool:

keytool
    -import
    -alias <provide_an_alias>
    -file <certificate_file>
    -keystore <your_path_to_jre>/lib/security/cacerts
风为裳 2024-07-27 21:40:49

使用下面的代码

-Djavax.net.ssl.keyStoreType=pkcs12

或者

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

根本不需要。 此外,无需创建您自己的自定义 SSL 工厂。

我也遇到了同样的问题,就我而言,存在完整证书链未导入信任库的问题。 使用 keytool 实用程序从根证书导入证书,您也可以在记事本中打开 cacerts 文件,查看是否导入了完整的证书链。 检查导入证书时提供的别名,打开证书并查看它包含多少个,cacerts 文件中应该有相同数量的证书。

此外,还应在运行应用程序的服务器中配置 cacerts 文件,两个服务器将使用公钥/私钥相互验证。

Using below code

-Djavax.net.ssl.keyStoreType=pkcs12

or

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

is not at all required. Also there is no need to create your own custom SSL factory.

I also encountered the same issue, in my case there was a issue that complete certificate chain was not imported into truststores. Import certificates using keytool utility right fom root certificate, also you can open cacerts file in notepad and see if the complete certificate chain is imported or not. Check against the alias name you have provided while importing certificates, open the certificates and see how many does it contains, same number of certificates should be there in cacerts file.

Also cacerts file should be configured in the server you are running your application, the two servers will authenticate each other with public/private keys.

笨笨の傻瓜 2024-07-27 21:40:49

虽然这个问题已有 12 年多了,并且有很多好的答案,但我想提供一个替代方案。 以下是加载密钥库和信任库并获取 sslsocketfactory 或 sslcontext 的小片段:

SSLFactory sslFactory = SSLFactory.builder()
        .withIdentityMaterial("clientcertificate.p12", "password".toCharArray(), "PKCS12")
        .withTrustMaterial("gridserver.keystore", "password".toCharArray(), "PKCS12")
        .build();

SSLSocketFactory sslSocketFactory = sslFactory.getSslSocketFactory();
SSLContext sslContext = sslFactory.getSslContext();

此示例代码片段来自库: GitHub - SSLContext Kickstart 您可以使用以下代码片段添加它:

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>sslcontext-kickstart</artifactId>
    <version>7.0.2</version>
</dependency>

Although this question is more than 12 years old and has a-lot of good answers I want to provide an alternative. Here is a small snippet of loading the keystore and truststore and getting the sslsocketfactory or sslcontext:

SSLFactory sslFactory = SSLFactory.builder()
        .withIdentityMaterial("clientcertificate.p12", "password".toCharArray(), "PKCS12")
        .withTrustMaterial("gridserver.keystore", "password".toCharArray(), "PKCS12")
        .build();

SSLSocketFactory sslSocketFactory = sslFactory.getSslSocketFactory();
SSLContext sslContext = sslFactory.getSslContext();

This example code snippet is from the library: GitHub - SSLContext Kickstart You can add it with the following snippet:

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>sslcontext-kickstart</artifactId>
    <version>7.0.2</version>
</dependency>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文