Wednesday, December 4, 2013

Keystore vs. Truststore and Testing a Java SSL connection

I got the following note from here.

Keystore vs. Truststore

The SSL/TLS related Java classes have the concept of Keystores and Truststores. Initially I had no idea what a Truststore was and thought that a Keystore fulfilled both it's own role as well as that of a Truststore, but oh how wrong I was. 

  • A Keystore is used to contain and provide private key data. So whenever clients are connecting TO a java based server application (like Tomcat), the Keystore is used to present it's server certificate (public cert) to the clients and contains the private key associated with that server certificate. 
  • The Truststore on the other hand contains the list of trusted certificates that would be used when a java based application (also could be Tomcat) functions as a client and makes an outbound SSL connection to some other device. The contents of the Truststore would be used to validate the identity of the server certificate being presented to the client by the other device.
The Keystore and Truststore are Java System Properties that of course have defaults but that can also be overriden to provide a different value. The keystore and keystorepassword values within the Tomcat server.xml come to mind. You may also be able to specify a Truststore within the server.xml file but I have not validated this

The default Truststore for most Java installations is a keystore file named cacerts with a default password of changeit and usually resides in a directory like $JAVAHOME/lib/security, or something along those lines. If you are running an application with an older version of Java you might have to update the certificates inside of the cacerts file if you have problems making an outbound SSL connection, you would likely see and SSL related error in catalina.out as an indicator


Testing an SSL Connection

You can use the code and procedure below to create a very simple Java SSL Client that you can use to test connectivity to a Java based Server.
  1. Copy the code below into a file named HTTPSClient.java (case sensitive) 
  1. import java.net.*;
    import java.io.*;
    import java.security.*;
    import javax.net.ssl.*;
    
    public class HTTPSClient {
     public static void main(String[] args) {
    
    if (args.length == 0) {
    System.out.println("Usage: java HTTPSClient host"); return;
    }
    int port = 443; // default https port
    String host = args[0];
    try{
    Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
    SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();

    SSLSocket socket = (SSLSocket) factory.createSocket(host, port); Writer out = new OutputStreamWriter(socket.getOutputStream()); // https requires the full URL in the GET line out.write("GET / HTTP/1.0\\r\\\n"); out.write("\\r\\n"); out.flush(); // read response BufferedReader in = new BufferedReader(
    new InputStreamReader(socket.getInputStream()));
    int c; while ((c = in.read()) != -1) { System.out.write(c); } out.close(); in.close(); socket.close();
    }catch (IOException e) {
    System.err.println(e);
    }
    } }
  2. Compile the Java class file using the following command
    javac ./HTTPSClient.java
  3. Now you can use the compiled class to test an SSL server connection. The class takes just one parameter, the FQDN of the host that you want to test. NOTE: The client defaults to port 443 for the SSL server connection, however you can change the port number in the class before you compile it if you need to test an alternate port, like 8443 for example. Make sure that you are in the directory where you compiled class is, the code below is case sensitive
    java HTTPSClient login.yahoo.com
  4. The command above will use the default value for the Truststore, if you need to specify an alternate Truststore you can set the system properties like this
    java -Djavax.net.ssl.trustStore=/somedirectory/someKeystore -Djavax.net.ssl.trustStorePassword=somepassword HTTPSClient login.yahoo.com
  • A successful connection using either of the commands above should just return the HTTP response from the server, something like this. Just kind of indicating that you can successfully connect to the server but didn't really issue any valid request
    HTTP/1.1 400 Bad Request
    Date: Tue, 25 Oct 2011 20:30:24 GMT
    Vary: Accept-Encoding
    Connection: close
    Content-Type: text/html; charset=iso-8859-1
    Cache-Control: private
  • A failed connection would look something like this. Usually indicating that there is some type of certificate validation issue, most likely your Truststore doesn't contain the Trusted root certificates that it needs
    javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: 
    
    sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target