My current TCP client is building sockets using org.apache.http.conn.ssl.SSLSocketFactory
I am migrating to import javax.net.ssl.SSLSocketFactory;
However, I am not sure how to load the private key for the client certificate which is stored in the .p12 file that I already have, as the context for the new SSLSocketFactory?
Here is my existing code, using the deprecated SSLSocketFactory:
import org.apache.http.ssl.SSLContexts;
import org.apache.http.conn.ssl.SSLSocketFactory;
String ksPassphrase = "myKSpass";
String pkPassphrase = "myPKpass";
// Get a keystore instance of type PKCS12:
KeyStore keystore = KeyStore.getInstance("PKCS12");
// Load keystore file and password:
keystore.load(new FileInputStream("myFile.p12"), ksPassphrase.toCharArray());
// Create HTTPS connection socket factory context:
SSLContext context = SSLContexts.custom()
.loadKeyMaterial(keystore, pkPassphrase.toCharArray())
.build();
SSLSocketFactory sslScktFactory = new SSLSocketFactory(context);
Here is the best code I could manage from the docs:
import javax.net.ssl.*;
// not sure if this is the pass to KS or private key or, somehow both?
String passphrase = "myPassphrase"
SSLContext context = SSLContext.getInstance("TLS");
KeyManagerFactory keyMngFactory = KeyManagerFactory.getInstance("SunX509");
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream("myFile.jks"), passphrase.toCharArray());
keyMngFactory.init(keyStore, passphrase);
context.init(keyMngFactory.getKeyManagers(), null, null);
SSLSocketFactory sslScktFactory = context.getSocketFactory();
.custom()
method for the context under the new library.The password you pass to KeyStore.load(InputStream,char[])
is the store password; the password you pass to KeyManagerFactory.init(KeyStore,char[])
is the privatekey password ('the' singular; must be the same for all keys if there are more than one) and notice it is char[]
not String
-- your compiler (or IDE) should have told you this. You do need to change your new code to use KeyStore.getInstance("PKCS12")
if you want to continue using your existing (or any other/new) PKCS12 file(s).
Note the API allows you to use a keypass different from the storepass, but other software with which you might want to exchange or share PKCS12 files (like Windows, MacOSX, OpenSSL, I think NSS) does not support this, and to try to avoid such problems Java's commandline keytool
will not set keypass different from storepass for PKCS12.
What to use for the new SSLSocketFactory?
SSLContext.getSocketFactory()
-- exactly as you have posted. In fact that's actually what your previous, deprecated org.apache.http.conn.ssl.SSLSocketFactory.SSLSocketFactory(SSLContext)
does internally, except that it also defaults the HostNameVerifier.
Is it the case that JKS keystores don't have internal passwords for the private keys stored inside (I only used PKCS until now, not familiar with the JKS format) and that's why I can't see where the private key password is being referred to?
No; JKS stores do have separate storepass and keypass, which would be used in the code you posted if it were corrected as I describe above -- although the privatekey encryption JKS uses the keypass for is weaker than the one usually used in PKCS12. And since JKS files aren't interoperable with other software anyway, keytool
does support having keypass different from storepass. In fact keytool
(and JCA generally) supports having different keypass values for different privatekeys in one JKS store, but KeyManagerFactory
(for SSL/TLS i.e. JSSE) does not support that, and neither does Apache (which other than adding/wrapping the key choice strategy just uses JSSE underneath).
(OP) Edit: Here is some tested-working code resulting from your suggestion:
// Consider having both the same, for compatibility with Windows/MacOSX/OpenSSL/etc:
String ksPassphrase = "myKSpass";
String pkPassphrase = "myPKpass";
SSLContext context = SSLContext.getInstance("TLS");
KeyManagerFactory keyMngFactory = KeyManagerFactory.getInstance("SunX509");
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("myFile.p12"),
ksPassphrase.toCharArray());
keyMngFactory.init(keyStore, pkPassphrase.toCharArray());
context.init(keyMngFactory.getKeyManagers(), null, null);
SSLSocketFactory sslScktFactory = context.getSocketFactory();