[英]Java client certificates over HTTPS/SSL
我正在使用 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
我什至不确定客户端证书是这里的问题。
终于解决了它;)。 在这里得到了强烈的暗示(Gandalfs的回答也有点触及)。 丢失的链接(大部分)是下面的第一个参数,在某种程度上,我忽略了密钥库和信任商店之间的区别。
必须将自签名服务器证书导入信任库:
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);
}
虽然不推荐,但您也可以完全禁用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) {}
}
}
您是否设置了KeyStore和/或TrustStore系统属性?
java -Djavax.net.ssl.keyStore=pathToKeystore -Djavax.net.ssl.keyStorePassword=123456
或者来自代码
System.setProperty("javax.net.ssl.keyStore", pathToKeyStore);
与javax.net.ssl.trustStore相同
如果您正在使用Axis框架处理Web服务调用,则有一个更简单的答案。 如果您的客户端都希望能够调用SSL Web服务并忽略SSL证书错误,那么只需在调用任何Web服务之前放置此语句:
System.setProperty("axis.socketSecureFactory", "org.apache.axis.components.net.SunFakeTrustSocketFactory");
通常的免责声明适用于在生产环境中做的非常糟糕的事情。
我在Axis维基上找到了这个。
对我来说,这是使用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();
}
我使用Apache commons HTTP Client包在我当前的项目中执行此操作,并且它可以正常使用SSL和自签名证书(在将其安装到您提到的cacerts之后)。 请看这里:
我认为你的服务器证书有问题,不是有效证书(我认为这就是“handshake_failure”在这种情况下的意思):
将服务器证书导入客户端JRE上的trustcacerts密钥库。 使用keytool可以轻松完成此操作:
keytool
-import
-alias <provide_an_alias>
-file <certificate_file>
-keystore <your_path_to_jre>/lib/security/cacerts
使用下面的代码
-Djavax.net.ssl.keyStoreType=pkcs12
要么
System.setProperty("javax.net.ssl.keyStore", pathToKeyStore);
根本不需要。 此外,您无需创建自己的自定义SSL工厂。
我也遇到了同样的问题,在我的情况下,存在一个问题,即没有将完整的证书链导入到信任库中。 使用keytool实用程序右边的根证书导入证书,也可以在记事本中打开cacerts文件,看看是否导入了完整的证书链。 检查导入证书时提供的别名,打开证书并查看其中包含的别名,cacerts文件中应该有相同数量的证书。
此外,应在运行应用程序的服务器中配置cacerts文件,这两个服务器将使用公钥/私钥进行相互身份验证。
虽然这个问题已经超过 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>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.