以编程方式从PEM获取KeyStore

时间:2012-09-19 18:57:33

标签: java ssl ssl-certificate bouncycastle jsse

如何以编程方式从包含证书和私钥的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();
} 

我注意到服务器在客户端是TLSv1时在日志中输出SSLv3。如果我添加系统属性-Dhttps.protocols = SSLv3,那么客户端也将使用SSLv3,但我收到相同的错误消息。我也尝试添加-Dsun.security.ssl.allowUnsafeRenegotiation = true而不改变结果。

我已经google了,这个问题的通常答案是先使用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();

1 个答案:

答案 0 :(得分:0)

尽管 Ryan 的回答效果很好,但我想为其他开发人员提供一个替代方案,因为我过去面临类似的挑战,我还需要处理 pem 格式的加密私钥。我创建了一个库来简化加载 pem 文件并从中创建 SSLSocketFactory 或 SSLContext,请参见此处:GitHub - SSLContext Kickstart 我希望你喜欢它 :)

可以使用以下代码段加载 pem 文件:

var keyManager = PemUtils.loadIdentityMaterial("certificate-chain.pem", "private-key.pem");
var trustManager = PemUtils.loadTrustMaterial("some-trusted-certificate.pem");

var sslFactory = SSLFactory.builder()
          .withIdentityMaterial(keyManager)
          .withTrustMaterial(trustManager)
          .build();

var sslContext = sslFactory.getSslContext();
var sslSocketFactory = sslFactory.getSslSocketFactory();

回到您的主要问题,使用上面的代码片段,不需要从 pem 文件创建密钥库对象。它将在幕后处理这些并将其映射到 KeyManager 实例。