Programowo Uzyskaj KeyStore od PEM

W jaki sposób można programowo uzyskać KeyStore z pliku PEM zawierającego zarówno certyfikat, jak i klucz prywatny? Próbuję dostarczyć certyfikat klienta do serwera w połączeniu HTTPS. Potwierdziłem, że certyfikat klienta działa, jeśli używam openssl i keytool do uzyskania pliku jks, który ładuję dynamicznie. Mogę nawet sprawić, by działał dynamicznie, czytając plik p12 (PKCS12).

Szukam do korzystania z klasy PEMReader z BouncyCastle, ale nie mogę ominąć błędów. Używam klienta Java z opcją -Djavax.net.debug = all i serwerem Apache z debugowaniem LogLevel. Nie jestem jednak pewien, czego szukać. Dziennik błędów Apache wskazuje:

...
OpenSSL: Write: SSLv3 read client certificate B
OpenSSL: Exit: error in SSLv3 read client certificate B
Re-negotiation handshake failed: Not accepted by client!?

Program klienta Java wskazuje:

...
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
...

Kod klienta:

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();
} 

Zauważyłem, że serwer wysyła SSLv3 w dzienniku, gdy klientem jest TLSv1. Jeśli dodam właściwość systemową -Dhttps.protocols = SSLv3, to klient użyje również SSLv3, ale otrzymuję ten sam komunikat o błędzie. Próbowałem także dodać -Dsun.security.ssl.allowUnsafeRenegotiation = true bez zmiany wyniku.

Poszukałem go w wyszukiwarce, a zwykłą odpowiedzią na to pytanie jest użycie najpierw openssl i keytool. W moim przypadku muszę przeczytać PEM bezpośrednio w locie. Właściwie portuję program C ++, który już to robi, i szczerze mówiąc, jestem bardzo zaskoczony, jak trudno jest to zrobić w Javie. Kod 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();

questionAnswers(1)

yourAnswerToTheQuestion