header

Torsten Curdt’s weblog

Client cert authentication with java

Connecting to https URL is easy in java. Just create a URL object and you are ready to go. If you need to provide a client certificate it gets a little more complicated to get it right. Basically you have to create a properly set up SSLSocketFactory to establish an authenticated connection. You have to load you PKCS12 certificate into a keystore and provide that store to the SSLContext.

private SSLSocketFactory getFactory( File pKeyFile, String pKeyPassword ) throws ... {
  KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509);
  KeyStore keyStore = KeyStore.getInstance("PKCS12");

  InputStream keyInput = new FileInputStream(pKeyFile);
  keyStore.load(keyInput, pKeyPassword.toCharArray());
  keyInput.close();

  keyManagerFactory.init(keyStore, pKeyPassword.toCharArray());

  SSLContext context = SSLContext.getInstance("TLS");
  context.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());

  return context.getSocketFactory();
}

URL url = new URL("https://someurl");
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.setSSLSocketFactory(getFactory(new File("file.p12"), "secret"));

If the client certificate was issued by your private CA you also need to make sure the full certificate chain is in your JVMs keystore.

STORE=/path/to/JRE/cacerts
sudo keytool -import -trustcacerts \
  -keystore $STORE \
  -storepass changeit \
  -noprompt \
  -file myca.pem \
  -alias myca

  • Sorry, Ahsan - don't think I've come across such an exception before. Looks like your store is not setup correctly though.
  • Ahsan Mohsin
    thanks for such a wonderful post
    I used the above method to autthenticate against the client and got the following exception can you assist ?

    xception in thread "main" java.security.UnrecoverableKeyException: excess private key
    at sun.security.provider.KeyProtector.recover(KeyProtector.java:311)
    at sun.security.provider.JavaKeyStore.engineGetKey(JavaKeyStore.java:120)
    at java.security.KeyStore.getKey(KeyStore.java:731)
    at com.sun.net.ssl.internal.ssl.SunX509KeyManagerImpl.<init>(SunX509KeyManagerImpl.java:111)
    at com.sun.net.ssl.internal.ssl.KeyManagerFactoryImpl$SunX509.engineInit(KeyManagerFactoryImpl.java:41)
    at javax.net.ssl.KeyManagerFactory.init(KeyManagerFactory.java:192)
    at Main.getFactory(Main.java:58)
    at Main.main(Main.java:41)
    </init>
  • Saravanakumar
    It is very useful...thanks a lot !!
  • Naveen Kapoor
    Thanks for this nice article, I am new to Client server certificate authentication.
    We have self Signed certificate on Server and client end and we are using Keyman to create the Keyfile. The file format is .kdb.
    We have stored the client and server certificate in the same key store.
    Now while executing the java class I am facing the below error "
    java.io.IOException: PFX parsing error, not a SEQUENCE.
    at com.ibm.security.pkcs12.BasicPFX.decode(BasicPFX.java:475)
    at com.ibm.security.pkcs12.PFX.decode(PFX.java:829)
    at com.ibm.security.pkcsutil.PKCSDerObject.decode(PKCSDerObject.java:259)
    at com.ibm.security.pkcs12.PFX.<init>(PFX.java:134)
    at com.ibm.crypto.provider.PKCS12KeyStore.engineLoad(Unknown Source)
    at java.security.KeyStore.load(KeyStore.java:1173)
    at com.nav.test.http.HttpsConnTest2.main(HttpsConnTest2.java:50)
    when calling keyStore.load(keyInput, pKeyPassword.toCharArray());
    Please suggest</init>
  • Denis
    I did run across some additional hurdles because my cert is not self-certified. first, the keytool does not import the cer file correctly. secondly the KeyManager refuses to present the certificate to the server. For those who are interested, the full solution is here:
    http://denistek.blogspot.com/2...

    anyway, many thanks to Torsten.
  • Denis, try the to use openssl client to figure out what certificates get presented on the SSL handshake.
  • Denis
    I followed the exact steps here but got a weird exception: javax.net.ssl.SSLException: HelloRequest followed by an unexpected handshake message

    It looks like that the client certificate was not presented to the server somehow according to the javax.net.debug logs. I am using Java 6. Do you have any idea how to further trouble shoot and pinpoint the error?

    I am also curious, if the keystore has more than one certificates, which one and how will the keystore choose the right certificate to present to the server?
    Many thanks.
  • poojarani
    Thanks a lot.... I spent yesterday as a whole day for this .... Today morning it was a luck charm, that I got this blog...

    -pooja
  • @aperez: no idea what classes that generates ...but with the above you should be able to work that out yourself.
  • aperez
    Hi, How can I present a client certificate for authentication when using the classes generated by the NetBeans jax-ws plugin?
  • Paul
    Thanks for your reply. I'm using Sun JRE 1.6.0_13. To follow up, I discovered that when I changed the alias in the client keystore to match the CN of the certificate issuer, the client sent the certificate back to the server. I'm working through a different problem, but I hope this bit of information might help someone.
  • Did you try the approach I described? Do both sides have the root certificate available? Usually it's best to test these things with the openssl client. Good luck!
  • Paul
    I have a KeyStore with a single PrivateKey/Certificate that I am supplying to
    [keyManagerFactory.init(keyStore, pKeyPassword.toCharArray());]. On the client side, in the debugger, I can see the correct key in the sslContextFactory/context/keyManager. On the server side, in the debugger, it does not appear that the client is passing this certificate to the server.

    Is there a special way that the entry should be added to the keystore? Right now I'm calling
    [ks.setKeyEntry("1", x509Adaptor.getPrivateKey(), passphrase.toCharArray(), certificateChain);].
  • P
    Hey, thanks for the prompt reply.

    The CA is VeriSign; using the default trust store.

    The app needs to be 'portable' so that I can use different PFXs. Perhaps a properties file.
  • @P: You can specify a different trust store with javax.net.ssl.trustStore Whether you need a different store depends on whether the CA is self signed or not.
  • PM
    Hi

    New to this so will really appreciate some help.

    I need to POST some data (XML) to a website over SSL (which is ok) & use client certificate. I have been given a PFX (both public & private keys exported).

    Do I need to import the PFX into a (new) keytstore? Is it possible to specify the certificate path as a -Djavax... path?

    Will appreciate a code sample.

    Thanks in advance.

    P

  • Ah, nice Alberto. This not-yet-commons-ssl looks nice. Wondering why this hasn't made to commons yet. I remember there were talks about it.
  • Alberto
    thanks for your example.
    I use it with the apache libraries not-yet-commons-ssl....jar
    and it works wonderful!

    SSLClient client = new SSLClient();

    // Let's trust usual "cacerts" that come with Java. Plus, let's also trust a self-signed cert
    // we know of. We have some additional certs to trust inside a java keystore file.
    client.addTrustMaterial( TrustMaterial.DEFAULT );
    client.addTrustMaterial( new TrustMaterial( ".........../jssecacerts","changeit".toCharArray() ) );
    client.addTrustMaterial( new KeyMaterial( "/home/..../XDS.p12", "secret".toCharArray() ) );

    // To be different, let's allow for expired certificates (not recommended).
    client.setCheckHostname( true ); // default setting is "true" for SSLClient
    client.setCheckExpiry( false ); // default setting is "true" for SSLClient
    client.setCheckCRL( true ); // default setting is "true" for SSLClient
    client.setWantClientAuth(true);

    // Let's load a client certificate (max: 1 per SSLClient instance).
    client.setKeyMaterial( new KeyMaterial( "....../home/XDS.p12", "secret".toCharArray() ) );


    URL url=new URL("https://XDSab_REG_A:9442" +
    "/ImagingGateway/DateServlet");

    HttpsURLConnection https;

    https = (HttpsURLConnection) url.openConnection();

    https.setSSLSocketFactory(client);

    https.connect();


    Ovviusly you have to set in hosts file XDSab_REG_A !!!! ;-)
  • PhilQ
    Great article, saved me heaps of time. Implemented in IBM Lotus Domino, if anyone reading this is using Domino also, you need to use "IbmX509" instead of "SunX509".

    Thanks for taking the time to share.

    Cheers.

    Phil.
  • @Chintan: Sorry, but that is way to less information to help.
  • Chintan
    Hello,
    I put wallet file .p12 file somewhere (not loaded anywhere in JRE). I used your example, website still complains bad certificate. Any idea?
    Thanks
    Chintan
  • Thanks for the article!
  • Marco
    Thank you for taking the time to put this together.
  • Thanks for posting this article. I was looking around for a way to set up the client keystore for an HTTPS client without using the system properties method that is recommended *everywhere* else. (My reason is that the HTTPS client is living inside a servlet, making the system properties method a really bad idea.) It took a good hour or so to track down your page, but eventually, Google provided...
blog comments powered by Disqus