【发布时间】:2012-09-12 03:21:45
【问题描述】:
如何以编程方式从包含证书和私钥的 PEM 文件中获取 KeyStore?我正在尝试通过 HTTPS 连接向服务器提供客户端证书。如果我使用 openssl 和 keytool 来获取我动态加载的 jks 文件,我已经确认客户端证书有效。我什至可以通过动态读取 p12 (PKCS12) 文件来使其工作。
我正在考虑使用 BouncyCastle 的 PEMReader 类,但我无法克服一些错误。我正在运行带有 -Djavax.net.debug=all 选项的 Java 客户端和带有调试 LogLevel 的 Apache Web 服务器。我不确定要寻找什么。 Apache 错误日志指出:
...
OpenSSL: Write: SSLv3 read client certificate B
OpenSSL: Exit: error in SSLv3 read client certificate B
Re-negotiation handshake failed: Not accepted by client!?
Java客户端程序提示:
...
main, WRITE: TLSv1 Handshake, length = 48
main, waiting for close_notify or alert: state 3
main, Exception while waiting for close java.net.SocketException: Software caused connection abort: recv failed
main, handling exception: java.net.SocketException: Software caused connection abort: recv failed
%% Invalidated: [Session-3, TLS_RSA_WITH_AES_128_CBC_SHA]
main, SEND TLSv1 ALERT: fatal, description = unexpected_message
...
客户端代码:
public void testClientCertPEM() throws Exception {
String requestURL = "https://mydomain/authtest";
String pemPath = "C:/Users/myusername/Desktop/client.pem";
HttpsURLConnection con;
URL url = new URL(requestURL);
con = (HttpsURLConnection) url.openConnection();
con.setSSLSocketFactory(getSocketFactoryFromPEM(pemPath));
con.setRequestMethod("GET");
con.setDoInput(true);
con.setDoOutput(false);
con.connect();
String line;
BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
while((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
con.disconnect();
}
public SSLSocketFactory getSocketFactoryFromPEM(String pemPath) throws Exception {
Security.addProvider(new BouncyCastleProvider());
SSLContext context = SSLContext.getInstance("TLS");
PEMReader reader = new PEMReader(new FileReader(pemPath));
X509Certificate cert = (X509Certificate) reader.readObject();
KeyStore keystore = KeyStore.getInstance("JKS");
keystore.load(null);
keystore.setCertificateEntry("alias", cert);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(keystore, null);
KeyManager[] km = kmf.getKeyManagers();
context.init(km, null, null);
return context.getSocketFactory();
}
我注意到服务器在日志中输出 SSLv3,而客户端是 TLSv1。如果我添加系统属性 -Dhttps.protocols=SSLv3 那么客户端也将使用 SSLv3,但我会收到相同的错误消息。我也尝试添加 -Dsun.security.ssl.allowUnsafeRenegotiation=true 结果没有变化。
我用谷歌搜索了这个问题,通常的答案是首先使用 openssl 和 keytool。就我而言,我需要直接即时阅读 PEM。我实际上是在移植一个已经做到这一点的 C++ 程序,坦率地说,我很惊讶在 Java 中做到这一点有多么困难。 C++ 代码:
curlpp::Easy request;
...
request.setOpt(new Options::Url(myurl));
request.setOpt(new Options::SslVerifyPeer(false));
request.setOpt(new Options::SslCertType("PEM"));
request.setOpt(new Options::SslCert(cert));
request.perform();
【问题讨论】:
-
对于一次性转换,您可以试用我们的 SecureBlackbox 产品,从 PEM 加载证书并将其保存为 JKS 或 PKCS#12 格式。当然,您也可以完全用 SecureBlackbox 替换 BouncyCastle。
-
如果您成功地将 PEM 作为密钥库加载,构造了 KeyManager、SSLContext 和 SSLSocketFactory,则必须假定问题出在 信任证书本身,而不是任何加载的东西。
-
嗨@all,请有人提供代码实现以使用pem文件在java中开发服务器和客户端。
标签: java ssl ssl-certificate bouncycastle jsse