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.
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.

451908 次浏览

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

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

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

或者用密码

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

与 javax.net.ssl.trust Store 相同

我认为您的服务器证书有问题,不是一个有效的证书(我认为这就是在这种情况下“ handshake _ fault”的意思) :

将您的服务器证书导入到客户端 JRE 上的 trust ccerts 密钥存储库中。使用 keytool很容易做到这一点:

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

虽然不推荐,但是您也可以完全禁用 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) {}
}
}

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

必须将自签名的服务器证书导入信任存储区:

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

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

SetProperty (“ axis.socketSecureFactory”, “ org.apache.axis.components.net . SunfakeTrustSocketFactory”) ;

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

我在 the Axis wiki找到了这个。

使用以下代码

-Djavax.net.ssl.keyStoreType=pkcs12

或者

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

也不需要创建自己的定制 SSL 工厂。

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

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

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

虽然这个问题已经超过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 启动您可以用下面的片段添加它:

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