基于 HTTPS/SSL 的 Java 客户端证书
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/875467/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java client certificates over HTTPS/SSL
提问by Jan
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?
我正在使用 Java 6 并尝试HttpsURLConnection
使用客户端证书针对远程服务器创建一个。
服务器正在使用自签名根证书,并要求提供受密码保护的客户端证书。我已将服务器根证书和客户端证书添加到我在/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts
(OSX 10.5) 中找到的默认 java 密钥库中。密钥库文件的名称似乎表明客户端证书不应该放在那里?
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.
我什至不确定客户端证书是这里的问题。
采纳答案by Jan
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
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);
}
回答by Taylor Leese
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:
我使用 Apache commons HTTP Client 包在我当前的项目中执行此操作,它适用于 SSL 和自签名证书(在将其安装到您提到的 cacerts 之后)。请看这里:
http://hc.apache.org/httpclient-3.x/tutorial.html
http://hc.apache.org/httpclient-3.x/tutorial.html
回答by Gandalf
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 相同
回答by sourcerebels
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. This is easily done with keytool:
将您的服务器证书导入客户端 JRE 上的 trustcacerts 密钥库。这很容易用keytool完成:
keytool
-import
-alias <provide_an_alias>
-file <certificate_file>
-keystore <your_path_to_jre>/lib/security/cacerts
回答by neu242
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) {}
}
}
回答by Mark Meuer
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:
如果您正在使用 Axis 框架处理 Web 服务调用,则有一个简单得多的答案。如果您只想让您的客户端能够调用 SSL Web 服务并忽略 SSL 证书错误,只需在调用任何 Web 服务之前放置以下语句:
System.setProperty("axis.socketSecureFactory",
"org.apache.axis.components.net.SunFakeTrustSocketFactory");
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 wiki 上找到了这个。
回答by Ankur Singhal
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 chainwas 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.
我也遇到了同样的问题,就我而言,有一个问题是完整的证书链没有导入到信任库中。使用 keytool 实用程序从根证书导入证书,您也可以在记事本中打开 cacerts 文件,查看是否导入了完整的证书链。检查您在导入证书时提供的别名,打开证书并查看它包含多少个,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 文件,两台服务器将使用公钥/私钥相互验证。
回答by EpicPandaForce
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();
}