証明書と秘密鍵の両方を含む PEM ファイルからキーストアをプログラムで取得するにはどうすればよいですか? HTTPS 接続でクライアント証明書をサーバーに提供しようとしています。openssl と keytool を使用して動的にロードする jks ファイルを取得すると、クライアント証明書が機能することを確認しました。p12 (PKCS12) ファイルを動的に読み込むことで動作させることもできます。
BouncyCastle の PEMReader クラスの使用を検討していますが、いくつかのエラーを回避できません。-Djavax.net.debug=all オプションを指定して Java クライアントを実行し、Debug 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();
}
クライアントが TLSv1 であるのに、サーバーがログに SSLv3 を出力していることに気付きました。システム プロパティ -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();