简体   繁体   English

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

[英]Java client certificates over HTTPS/SSL

I am using Java 6 and am trying to create an HttpsURLConnection against a remote server, using a client certificate.我正在使用 Java 6 并尝试使用客户端证书针对远程服务器创建HttpsURLConnection
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).我已将服务器根证书和客户端证书添加到我在/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts (OSX 10.5) 中找到的默认 java 密钥库中。 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.无论如何,将根证书添加到这家商店解决了臭名昭著的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:我试过跳过 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 not even sure that the client certificate is the problem here.我什至不确定客户端证书是这里的问题。

Finally solved it ;). 终于解决了它;)。 Got a strong hint here (Gandalfs answer touched a bit on it as well). 在这里得到了强烈的暗示(Gandalfs的回答也有点触及)。 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 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);
}

While not recommended, you can also disable SSL cert validation alltogether: 虽然不推荐,但您也可以完全禁用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) {}
  }
}

Have you set the KeyStore and/or TrustStore System properties? 您是否设置了KeyStore和/或TrustStore系统属性?

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 与javax.net.ssl.trustStore相同

If you are dealing with a web service call using the Axis framework, there is a much simpler answer. 如果您正在使用Axis框架处理Web服务调用,则有一个更简单的答案。 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: 如果您的客户端都希望能够调用SSL Web服务并忽略SSL证书错误,那么只需在调用任何Web服务之前放置此语句:

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 . 我在Axis维基上找到了这个。

For me, this is what worked using Apache HttpComponents ~ HttpClient 4.x: 对我来说,这是使用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: 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();
}

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). 我使用Apache commons HTTP Client包在我当前的项目中执行此操作,并且它可以正常使用SSL和自签名证书(在将其安装到您提到的cacerts之后)。 Please take a look at it here: 请看这里:

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

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

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): 我认为你的服务器证书有问题,不是有效证书(我认为这就是“handshake_failure”在这种情况下的意思):

Import your server certificate into your trustcacerts keystore on client's JRE. 将服务器证书导入客户端JRE上的trustcacerts密钥库。 This is easily done with keytool : 使用keytool可以轻松完成此操作:

keytool
    -import
    -alias <provide_an_alias>
    -file <certificate_file>
    -keystore <your_path_to_jre>/lib/security/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. 此外,您无需创建自己的自定义SSL工厂。

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. 使用keytool实用程序右边的根证书导入证书,也可以在记事本中打开cacerts文件,看看是否导入了完整的证书链。 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. 检查导入证书时提供的别名,打开证书并查看其中包含的别名,cacerts文件中应该有相同数量的证书。

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. 此外,应在运行应用程序的服务器中配置cacerts文件,这两个服务器将使用公钥/私钥进行相互身份验证。

Although this question is more than 12 years old and has a-lot of good answers I want to provide an alternative.虽然这个问题已经超过 12 年了,并且有很多很好的答案,但我想提供一个替代方案。 Here is a small snippet of loading the keystore and truststore and getting the sslsocketfactory or sslcontext:这是加载密钥库和信任库并获取 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();

This example code snippet is from the library: GitHub - SSLContext Kickstart You can add it with the following snippet:此示例代码片段来自库: GitHub - SSLContext Kickstart您可以使用以下片段添加它:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM