Saturday, July 18, 2009

Communicating using HTTPS/SSL in Java

Posted by Kris Gemborys

Writing Java client SSL code to communicate using HTTPS can be a frustrating experience.
You will need to complete the following two tasks to have your SSL handshake working and HTTPS communication going:
1) Create and configure keystores which are repositories storing X509 certificates
2) Write client java code
Depending on project objectives, you will also need to consider the following SSL configuration options:
1) Self-signed certificates - used in development environment and sometimes Intranets
2) Two-way SSL authentication - typically used to facilitate secured SOA communication over WAN
3) One-way SSL authentication - most common way to submit sensitive data to eCommerce websites

This article discusses various steps and challenges encountered when configuring SSL with IBM JDK 1.4.x, IBM JDK 1.6.x, and SUN JDK 1.6.x.



We should start with determining what type of SSL communication we will be handling. In the development environment, we typically deal with self-signed X509 certificates. The SSL/HTTPS server configuration steps and tools depend obviously on the server's vendor. For example, when configuring servers which use SUN's JDK we should use keytool located in SUN's JRE bin folder, and when configuring WebSphere or IBM HTTPS, we should use IBM's ikeyman.bat. When working with certificate repositories (keystores), we need to be aware of different providers such as CMS, JKS, PKCS12.




The most important difference between self-signed certificates and production certificates obtained from Certificate Authorities (CAs), such as Verisign is that self-signed certificates are not trusted. Every X509 certificate contains other certificate references (certificate chain). This certificate chain leads to a root CA certificate and proves that a certificate installed on a server and used for SSL communication has been legitimately obtained and the owner of this certificate is a registered entity with the CA. Obtaining certificates from CAs costs money and obviously, in the development environment, we do not care whether a certificate is legitimate or not; we just want to make sure that our SSL code works. The one problem with self-signed certificates is that when java code or a browser communicates with a server which uses a self-signed certificate the code returns certificate errors. The SSL Java handshake throws exceptions that a server certificate cannot be trusted. One of the required steps to fix this issue is to force Java code to trust this self-signed certificate.



Configuring server with HTTPS/SSL

The steps to configure the server's keystore depend on vendor so you will need to consult appropriate documentation. In the case of IBM HTTP server, you will use ikeyman to create a new keystore. When creating an IBM HTTPS keystore, you will need to use CMS provider and stash password (do not forget to select the stash password checkbox). To make everything even more difficult, IBM requires use of the CMS provider for both IBM HTTP 6 and IBM HTTP 7, but IBM HTTP 6 does not come with this provider in the default configuration. You will have to go to the java security configuration folder to enable the CMS provider. Fortunately, IBM HTTP 7 does not have this issue. Once you have your keystore updated/created, you will need to generate a new self-signed certificate. I suggest you select a maximum number of days, which is either 999 or 9999, depending on your keystore tooling. The last step is to export the public portion of the certificate. The export function works the same-way for self-signed as for CAs certificates. The export extracts public key. You will need this public key to configure your client.




Configuring trusted keystore on the client

When dealing with more recent JDK implementation, you need to be aware of two types of certificate repositories: regular keystores and trusted keystores. The trusted keystore is the source of all the grief when trying to configure a straight HTTPS using self-signed certificates. By default, a self-signed certificate is not trusted because a client cannot validate the server's certificate chain when establishing SSL connections. You need to use keytool or ikeyman to import a public portion of the server's self-signed certificate into the client's default trusted keystore. Internet mailing lists are full of postings reporting various chain certificate errors (and believe me you will get different errors depending on what JSSE APIs you are using). The SUN's and IBM's default trusted keystores have already all necessary entries that include root CAs, so when communicating with a site that uses a certificate obtained from CA you do not need to modify this default trusted keystore. A SUN's trusted keystore is located in <JAVA_HOME>/lib/security/jssecacerts. You should not modify this file but rather you should make a copy and use properties settings as in the sample Java code to override the location of your trusted keystore file. If you are not using self-signed certificates you do not need to import anything to the trusted keystore.



Two-way SSL authentication

If a client uses JDK 1.6. you have to configure both keystore and trusted keystore whether you plan to store any X509 certificates in the keystore or not. While the client's trusted keystore needs to have all certificates loaded to validate the server's certificate chain, the keystore itself can be empty. You will need certificates in this client's keystore only if you plan to use the two-way SSL authorization. The process for obtaining client certificates for the two-way authorization is similar to a process for obtaining server certificates. In the development environment, you can use keytool to generate a self-singed certificate and then export public key. You guessed it right, this public key needs to be imported in the trusted server keystore or otherwise you will be getting some nasty errors in server logs.



The sample client Java code will initialize SSL communication and verify that everything works.



This code configures keystores locations:




System.setProperty("javax.net.ssl.keyStore", properties.get("keystoreClient").toString());
System.setProperty("javax.net.ssl.keyStoreType", keyStoreType);
System.setProperty("javax.net.ssl.keyStorePassword", keyStorePassword);
if (trustedKeyStoreFlag) {
System.setProperty("javax.net.ssl.trustStore", properties.get("keystoreClientTrusted").toString());
System.setProperty("javax.net.ssl.trustStorePassword", trustedKeyStorePassword);
System.setProperty("javax.net.ssl.trustStoreType", keyStoreType);
}







This portion of code retrieves certificates from keystores






KeyManagerFactory kmf = KeyManagerFactory.getInstance(providerName);

KeyStore ks = null;

kmf.init(ks, null);

/* Initialize the trust manager */

TrustManagerFactory tmf = null;

if (trustedKeyStoreFlag) {

tmf = TrustManagerFactory.getInstance(providerName);

tmf.init(ks);

}





This portion of code initializes SSL Context with SSL or TLS protocol. You should be using TLS whenever available.




SSLContext sslContext = SSLContext.getInstance(sslContextName);

sslContext.init(kmf.getKeyManagers(), tmf != null?tmf.getTrustManagers():null, null);



This function will just send an HTTPS request and process an HTTPS response:




initialized = testHandshake(testURL);






Java SSL Example InitSSL.java

SUN Java Code to update keystore with missing X509 cert InstallCert.java


References:





No comments:

Post a Comment