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


116

我正在使用Java 6,并尝试HttpsURLConnection使用客户端证书针对远程服务器创建一个。
服务器正在使用自签名的根证书,并且要求提供受密码保护的客户端证书。我已将服务器根证书和客户端证书添加到在/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts(OSX 10.5)中找到的默认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

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


我已经给出了两个证书从客户端如何确定哪些人需要在密钥库添加和信任请你有助于确定该问题,你有过类似的问题的种类已经消失了stackoverflow.com/questions/61374276/...
henrycharles

Answers:


100

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

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

keytool-导入-alias gridserver-文件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);
}

我使用的网址是:localhost:8443 / Application_Name / getAttributes。我有一个带有/ getAttribute url映射的方法。此方法返回元素列表。我使用了HttpsUrlConnection,连接响应代码为200,但是当我使用inputStream时,它没有给我属性列表,它给了我登录页面的html内容。我已经完成身份验证,并将内容类型设置为JSON。请提出建议
迪帕克

83

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

72
应当注意,这样禁用证书验证将打开与可能的MITM攻击的连接:请勿在生产中使用
布鲁诺

3
幸运的是,代码无法编译。这种“解决方案”是完全不安全的。
洛恩侯爵,

5
@ neu242,不,它实际上并不取决于您使用它的目的。如果要使用SSL / TLS,则要确保连接不受MITM攻击,这就是重点。如果可以保证没有人能够更改流量,则不必进行服务器身份验证,但是很少有人怀疑还存在窃听者也无法更改网络流量的情况。
布鲁诺

1
@ neu242感谢您的代码片段。我实际上正在考虑将其用于生产中的特定目的(Web爬网),我在一个问题中提到了您的实现。(stackoverflow.com/questions/13076511/…)。如果有时间,可以请您看看,让我知道是否错过了任何安全风险?
2012年

1
@Bruno如果我仅对服务器进行ping操作,那是否真的会受到MITM攻击的影响?
PhoonOne 2015年

21

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

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

或来自代码

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

与javax.net.ssl.trustStore相同


13

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

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

通常认为这是在生产环境中要做的非常不好的事情。

我在Axis Wiki上找到了这个。


OP正在处理HttpsURLConnection,而不是Axis
neu242'3

2
我明白。我无意暗示在一般情况下我的回答会更好。这就是,如果你正在使用Axis框架,你可能有OP的在这方面的问题。(这就是我首先发现此问题的方式。)在这种情况下,我提供的方法更简单。
Mark Meuer 2012年

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

keyStore是什么包含私钥和证书。
EpicPandaForce 2014年

1
您需要包括以下2个依赖关系,以使convertPEMtoP12代码起作用:<dependency> <groupId> org.bouncycastle </ groupId> <artifactId> bcprov-jdk15on </ artifactId> <version> 1.53 </ version> </ dependency> <依赖关系> <groupId> org.bouncycastle </ groupId> <artifactId> bcpkix-jdk15on </ artifactId> <version> 1.53 </ version> </ dependency>
BirdOfPrey 2015年

@EpicPandaForce我收到一个错误:抓到:org.codehaus.groovy.runtime.type.handling.GroovyCastException:无法将类为“ org.bouncycastle.jcajce.provider.asymmetric.x509.X509CertificateObject”的对象转换为ks行中的“ int”类。 setKeyEntry-任何线索可能出了什么问题
Vishal Biyani

是的,您使用的是Groovy而不是严格类型的语言。(从技术上讲,该方法需要一个ID和一个证书,而不仅仅是一个证书)
EpicPandaForce 2016年

4

我使用Apache commons HTTP Client软件包在当前项目中执行此操作,它与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和客户端证书加载到任何浏览器中,它就会成功。
1

1
Apache HTTP Client 4可以SSLContext直接使用,因此您可以通过这种方式进行配置,而不是使用AuthSSLProtocolSocketFactory
Bruno 2010年

1
有没有办法在内存中而不是通过外部密钥库来处理所有客户端证书内容?
Sridhar Sarnobat,

4

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

将服务器证书导入到客户端JRE上的trustcacerts密钥库中。这可以通过keytool轻松完成:

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

我尝试清理并重新开始,握手失败消失了。现在,在连接终止之前,我只剩下5分钟的沉默:o
1

1

使用下面的代码

-Djavax.net.ssl.keyStoreType=pkcs12

要么

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

完全不需要。同样,也无需创建自己的自定义SSL工厂。

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

另外,还应在运行应用程序的服务器中配置cacerts文件,这两个服务器将使用公钥/私钥对彼此进行身份验证。


与设置两个系统属性相比,创建自己的自定义SSL工厂要复杂得多且容易出错。
罗恩侯爵
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.