121

我正在使用 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

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

4

9 回答 9

104

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

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

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);
}
于 2009-05-19T12:34:10.693 回答
84

虽然不推荐,但您也可以一起禁用 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) {}
  }
}
于 2009-05-18T08:58:31.237 回答
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 相同

于 2009-05-17T22:42:20.607 回答
13

如果您使用 Axis 框架处理 Web 服务调用,则有一个更简单的答案。如果您只想让您的客户端能够调用 SSL Web 服务并忽略 SSL 证书错误,那么只需在调用任何 Web 服务之前添加以下语句:

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

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

我在Axis wiki找到了这个。

于 2010-07-07T21:42:54.413 回答
6

对我来说,这就是使用 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();
}
于 2014-11-06T12:29:06.290 回答
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

于 2009-05-17T21:23:27.897 回答
4

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

将您的服务器证书导入到客户端 JRE 上的 trustcacerts 密钥库中。这很容易用keytool完成:

keytool
    -import
    -alias <provide_an_alias>
    -file <certificate_file>
    -keystore <your_path_to_jre>/lib/security/cacerts
于 2009-05-18T08:55:31.430 回答
1

使用下面的代码

-Djavax.net.ssl.keyStoreType=pkcs12

或者

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

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

我也遇到了同样的问题,在我的情况下,有一个问题是完整的证书链没有导入到信任库中。使用 keytool 工具权限从根证书导入证书,您也可以在记事本中打开 cacerts 文件,查看是否导入了完整的证书链。检查您在导入证书时提供的别名,打开证书并查看其中包含多少个证书,cacerts 文件中应该有相同数量的证书。

cacerts 文件也应该在您运行应用程序的服务器中配置,两台服务器将使用公钥/私钥相互验证。

于 2014-03-31T15:54:56.460 回答
0

尽管这个问题已有 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>
于 2021-10-24T21:36:31.173 回答