HTTPClient and Client Side Certificates
October 18, 2011
I had to develop an interface to a leading credit reference agency. The call was based on self signed certificates (no other certificates required), and we had to attach a client side certificate to the request. The payload was a SOAP XML block.
Certificates
I ended up with two keystores, named keystore.jks
and truststore.jks
. The keystore contained the client side key that I would use to connect to the service. The truststore contained the other certificates as required by the chain. In my situation, I had one client side key in the keystore and two certificates in the truststore.
You can list the contents of a keystore using the keytool
command. Here's the list for the truststore:
$ keytool -list -keystore truststore.jks Enter keystore password: Keystore type: JKS Keystore provider: SUN Your keystore contains 2 entries certuat, 12-Oct-2011, trustedCertEntry, Certificate fingerprint (MD5): 3E:1C:23:D8:DB:7C:AF:30:AB:2C:CD:AE:E0:3A:03:0C credituatroot, 12-Oct-2011, trustedCertEntry, Certificate fingerprint (MD5): 21:44:F6:7F:1E:F9:4A:81:29:C8:F7:51:C8:77:1A:4D
Here's the list for the keystore:
$ keytool -list -keystore keystore.jks Enter keystore password: Keystore type: JKS Keystore provider: SUN Your keystore contains 1 entry clientcert, 12-Oct-2011, PrivateKeyEntry, Certificate fingerprint (MD5): AB:F9:A1:8D:EB:35:63:99:A7:71:87:4D:1A:3E:12:FC
Required Libraries
This call was implemented using HTTPClient 4.1.2. The required java libraries are:
commons-codec-1.4.jar commons-logging-1.1.1.jar httpclient-4.1.2.jar httpclient-cache-4.1.2.jar httpcore-4.1.2.jar httpmime-4.1.2.jar
Constants
The code uses the following constants:
final String KEY_STORE_PATH = "keystore.jks"; final String KEY_STORE_PASSWORD = "changeit"; final String TRUST_STORE_PATH = "truststore.jks"; final String TRUST_STORE_PASSWORD = "changeit";
Load Two Keystores
We first need to load in the two keystores:
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); InputStream keystoreInput = new FileInputStream(KEY_STORE_PATH); keystore.load(keystoreInput, KEY_STORE_PASSWORD.toCharArray()); System.out.println("Keystore has " + keystore.size() + " keys"); // load the truststore, leave it null to rely on cacerts distributed with the JVM KeyStore truststore = KeyStore.getInstance(KeyStore.getDefaultType()); InputStream truststoreInput = new FileInputStream(TRUST_STORE_PATH); truststore.load(truststoreInput, TRUST_STORE_PASSWORD.toCharArray()); System.out.println("Truststore has " + truststore.size() + " keys");
Create Scheme Registry
Next, we need to set up the Scheme Registry, passing in both the keystore and the truststore:
SchemeRegistry schemeRegistry = new SchemeRegistry(); SSLSocketFactory lSchemeSocketFactory = new SSLSocketFactory(keystore, KEY_STORE_PASSWORD, truststore); schemeRegistry.register(new Scheme("https", 443, lSchemeSocketFactory));
Create HTTP Client
We now create a HTTPClient instance:
final HttpParams httpParams = new BasicHttpParams(); DefaultHttpClient lHttpClient = new DefaultHttpClient(new SingleClientConnManager(schemeRegistry), httpParams); String lUrl = "https://secure.address.soapservice.net/Service.asmx";
### Create XML Create the XML to send.
StringBuffer lXmlBuffer = new StringBuffer(); lXmlBuffer.append("<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"); lXmlBuffer.append("<SOAP-ENV:Header/>\n"); lXmlBuffer.append("<SOAP-ENV:Body>\n"); lXmlBuffer.append("<ABC xmlns=\"http://www.secure.com/ABC/\">\n"); lXmlBuffer.append("<payload>\n"); lXmlBuffer.append("</payload>\n"); lXmlBuffer.append("</ABC>\n"); lXmlBuffer.append("</SOAP-ENV:Body>\n"); lXmlBuffer.append("</SOAP-ENV:Envelope>\n"); String lXml = lXmlBuffer.toString();
Perform POST
Post to the URL with the XML as a parameter. The contents of the SOAPAction header are defined in the WSDL (look for <soap:operation soapAction="XXX">)
HttpPost lMethod = new HttpPost(lUrl); HttpEntity lEntity = new StringEntity(lXml, "text/xml", "UTF-8"); lMethod.setEntity(lEntity); lMethod.setHeader("SOAPAction", "http://www.secure.com/ABC/"); HttpResponse lHttpResponse = lHttpClient.execute(lMethod);
Process Response
We can now get hold of the response XML.
System.out.println("Response status code: " + lHttpResponse.getStatusLine().getStatusCode()); System.out.println("Response body: "); System.out.println(EntityUtils.toString(lHttpResponse.getEntity()));
Trouble Shooting
We got the following exception:
Exception in thread "main" javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated at com.sun.net.ssl.internal.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:352) at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:128) at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:397) at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:148) at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:149) at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:121) at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:573) at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:425) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:820) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:754) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:732)
This was down to the truststore/keystore key split being wrong. We worked this out using the java option -Djavax.net.debug=all
which outputs lots of debug for the SSL Handshake. See http://download.oracle.com/javase/1.5.0/docs/guide/security/jsse/ReadDebug.html for an explanation of the output.
References
- http://download.oracle.com/javase/1.5.0/docs/guide/security/jsse/ReadDebug.html
- http://stackoverflow.com/questions/3375121/mutual-authentication-with-x509-certificates-using-httpclient-4-0-1
- http://stackoverflow.com/questions/128263/how-do-you-determine-a-valid-soapaction
- http://shib.kuleuven.be/docs/ssl_commands.shtml
- http://maultech.com/chrislott/blog/20120107_httpscerts.html