Managing a Dynamic Java Trust Store

This is the latest (and probably last) in my series of client-side Java key and trust store management articles, and a good summary article for the topic, I hope.

It’s clear from the design of SSLContext in the JSSE that Java key and trust stores are meant to contain static data. Yet browsers regularly display the standard security warning dialog when connecting to sites whose certificates have expired or whose administrators haven’t bothered to purchase a CA-signed certificate. This dialog generally offers you three choices:

  • Get me out of here!
  • I understand the risks: add certificate for this session only
  • I understand the risks: add certificate permanently

In this article, I’d like to elaborate on what it means to “add certificate” – either temporarily or permanently.

Let’s start with a simple Java http(s) client:

public byte[] getContentBytes(URI uri, SSLContext ctx)
    throws Exception {
  URL url = uri.toURL();
  URLConnection conn = url.openConnection();
  if (conn instanceof HttpsURLConnection && ctx != null) {
    ((HttpsURLConnection)conn).setSSLSocketFactory(
        ctx.getSocketFactory());
  }
  InputStream is = conn.getInputStream();
  int bytesRead, bufsz = Math.max(is.available(), 4096);
  ByteArrayOutputStream os = new ByteArrayOutputStream(bufsz);
  byte[] buffer = new byte[bufsz];
  while ((bytesRead = is.read(buffer)) > 0)
    os.write(buffer, 0, bytesRead);
  byte[] content = os.toByteArray();
  os.close(); is.close();
  return content;
}

This client opens a URLConnection, reads the input stream into a byte buffer, and then closes the connection. If the connection is https – that is, an instance of HttpsURLConnection – it applies the SocketFactory from the supplied SSLContext.

NOTE: I’m purposely ignoring exception managment in this article to keep it short.

This code is simple and concise, but clearly there’s no way to affect what happens during application of the SSL certificates and keys at this level of the code. Certificate and key management is handled by the SSLContext so if we want to modify the behavior of the SocketFactory relative to key management, we’re going to have to do something with SSLContext before we pass it to the client. The simplest way to get an SSLContext is to call SSLContext.getDefault in this manner:

byte[] bytes = getContentBytes(
    URI.create("https://www.example.com/"), 
    SSLContext.getDefault());

The default SSLContext is fairly limited in functionality. It uses either default key and trust store files (and passwords!) or else ones specified in system properties – often via the java command line in this manner:

$ java -Djavax.net.ssl.keyStore=/path/to/keystore.jks \
 -Djavax.net.ssl.keyStorePassword=changeit \
 -Djavax.net.ssl.trustStorePath=/path/to/truststore.jks \
 -Djavax.net.ssl.trustStorePassword=changeit ...

In reality, there is no default keystore, which is fine for normal situations, as most websites don’t require X.509 client authentication (more commonly referred to as mutual auth). The default trust store is $JAVA_HOME/jre/lib/security/cacerts, and the default trust store password is changeit. The cacerts file contains several dozen certificate authority (CA) root certificates and will validate any server whose public key certificate is signed by one of these CAs.

More importantly, however, the default SSLContext simply fails to connect to a server in the event that a trust certificate is missing from the default trust store. But that’s not what web browsers do. Instead, they display the aforementioned dialog presenting the user with options to handle the situation in the manner that suits him or her best.

Assume the simple client above is a part of a larger application that adds certificates to the trust store during execution of other code paths and then expects to be able to use this updated trust store later during the same session. This dynamic reload functionality requires some SSLContext customization.

Let’s explore. SSLContext is a great example of a composite design. It’s built from several other classes, each of which may be specified by the user when initializing a context object. This practically eliminates the need to sub-class SSLContext in order to define custom behavior. The default context is eschewed in favor of a user-initialized instance of SSLContext like this:

public SSLContext getSSLContext(String tspath) 
    throws Exception {
  TrustManager[] trustManagers = new TrustManager[] { 
    new ReloadableX509TrustManager(tspath) 
  };
  SSLContext sslContext = SSLContext.getInstance("SSL");
  sslContext.init(null, trustManagers, null);
  return sslContext;
}

At the heart of this method is the instantiation of a new ReloadableX509TrustManager. The init method of SSLContext accepts a reference to an array of TrustManager objects. Passing null tells the context to use the default trust manager array which exihibits the default behavior mentioned above.

The init method also accepts two other parameters, to which I’ve passed null. The first parameter is a KeyManager array and the third is an implementation of SecureRandom. Passing null for any of these three parameters tells SSLContext to use the default. Here’s one implementation of ReloadableX509TrustManager:

class ReloadableX509TrustManager 
    implements X509TrustManager {
  private final String trustStorePath;
  private X509TrustManager trustManager;
  private List tempCertList 
      = new List();

  public ReloadableX509TrustManager(String tspath)
      throws Exception {
    this.trustStorePath = tspath;
    reloadTrustManager();
  }

  @Override
  public void checkClientTrusted(X509Certificate[] chain, 
      String authType) throws CertificateException {
    trustManager.checkClientTrusted(chain, authType);
  }

  @Override
  public void checkServerTrusted(X509Certificate[] chain, 
      String authType) throws CertificateException {
    try {
      trustManager.checkServerTrusted(chain, authType);
    } catch (CertificateException cx) {
      addServerCertAndReload(chain[0], true);
      trustManager.checkServerTrusted(chain, authType);
    }
  }

  @Override
  public X509Certificate[] getAcceptedIssuers() {
    X509Certificate[] issuers 
        = trustManager.getAcceptedIssuers();
    return issuers;
  }

  private void reloadTrustManager() throws Exception {

    // load keystore from specified cert store (or default)
    KeyStore ts = KeyStore.getInstance(
	    KeyStore.getDefaultType());
    InputStream in = new FileInputStream(trustStorePath);
    try { ts.load(in, null); }
    finally { in.close(); }

    // add all temporary certs to KeyStore (ts)
    for (Certificate cert : tempCertList) {
      ts.setCertificateEntry(UUID.randomUUID(), cert);
    }

    // initialize a new TMF with the ts we just loaded
    TrustManagerFactory tmf 
	    = TrustManagerFactory.getInstance(
            TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(ts);

    // acquire X509 trust manager from factory
    TrustManager tms[] = tmf.getTrustManagers();
    for (int i = 0; i < tms.length; i++) {
      if (tms[i] instanceof X509TrustManager) {
        trustManager = (X509TrustManager)tms[i];
        return;
      }
    }

    throw new NoSuchAlgorithmException(
        "No X509TrustManager in TrustManagerFactory");
  }

  private void addServerCertAndReload(Certificate cert, 
      boolean permanent) {
    try {
      if (permanent) {
        // import the cert into file trust store
        // Google "java keytool source" or just ...
        Runtime.getRuntime().exec("keytool -importcert ...");
      } else {
        tempCertList.add(cert);
      }
      reloadTrustManager();
    } catch (Exception ex) { /* ... */ }
  }
}

NOTE: Trust stores often have passwords but for validation of credentials the password is not needed because public key certificates are publicly accessible in any key or trust store. If you supply a password, the KeyStore.load method will use it when loading the store but only to validate the integrity of non-public information during the load – never during actual use of public key certificates in the store. Thus, you may always pass null in the second argument to KeyStore.load. If you do so, only public information will be loaded from the store.

A full implementation of X509TrustManager is difficult and only sparsely documented but, thankfully, not necessary. What makes this implementation simple is that it delegates to the default trust manager. There are two key bits of functionality in this implementation: The first is that it loads a named trust store other than cacerts. If you want to use the default trust store, simply assign $JAVA_HOME/jre/lib/security/cacerts to trustStorePath.

The second bit of functionality is the call to addServerCertAndReload during the exception handler in the checkServerTrusted method. When a certificate presented by a server is not found in the trust manager’s in-memory database, ReloadableX509TrustManager assumes that the trust store has been updated on disk, reloads it, and then redelegates to the internal trust manager.

A more functional implementation might display a dialog box to the user before calling addServerCertAndReload. If the user selects Get me out of here!, the method would simply rethrow the exception instead of calling that routine. If the user selects It’s cool: add permanently, the method would add the certificate to the file-based trust store, reload from disk, and then reissue the delegated request. If the user selects I’ll bite: add temporarily, the certificate would be added to a list of temporary certificates in memory.

The way I’ve implemented the latter case is to add the certificate to a temporary list and then reload from disk. Strictly speaking, reloading from disk isn’t necessary in this case since no changes were made to the disk file but the KeyStore built from the disk image would have to be kept around for reloading into the trust manager (after the new cert was added to it), so some modifications would have to be made to avoid reloading from disk.

This same code might as well be used in a server-side setting but the checkClientTrusted method would have to be modified instead of the checkServerTrusted method as in this example.

18 comments on “Managing a Dynamic Java Trust Store

  1. Bob Fletcher says:

    Thanks- you’ve saved me a fair bit of time!

  2. bitsofinfo says:

    This post was helpful. Question: I don’t necessiarly have control to provide an SSLContext to those who want to use it. I would like to make this TrustManager usable anytime any piece of code does something that uses SSL. Such as people doing SSL JNDI sessions etc, or HTTP or anything.

    How can I set this up for system wide use?

  3. Daniel Jones says:

    This post has helped me a lot but I was wondering how the code would vary for the server side. I am currently using a keystore for encryption/signature on a server which is to be used by many people and their certificates must be dynamically added without causing disruptions i.e without restarting tomcat. Do you know of any way that this would be possible ? I have tried re-loading the keystore and re-initializing the SSLContext but this didn’t work. Any help would be greatly appreciated.

    • John Calcote says:

      Hi Daniel. Good question. When I mentioned the “server side” in the last paragraph of the article, I was thinking of a private http server implementation, not a servlet container like tomcat. To do what you want to do, I’m guessing you’d have to subclass servlet container components to add key/trust store reload functionality. It’s a lot harder than the client-side algorithm I mentioned. Not that it’s undoable, mind you. After all, this is software. Anything can be done if you’re willing to dig deeply enough.

      One suggestion that comes to mind is to store a root signing certificate in your server-side truststore. Then sign your client-side trust certificates with this root signing cert. Now, when your clients use their new certs, the root signing cert is already in your truststore. I think this is a rather common approach to this problem. Of course, your needs may vary, but it keeps you from having to add dozens of newly created trust certs to your server-side truststore, and it solves the reload problem.

      • Daniel Jones says:

        Thanks a lot for your response and I will be giving that a try 🙂

        • Daniel Jones says:

          Hi, I have got the server working now with updating the keystore in real-time and I thought I would share how I made it work. I tried the couple of methods that you suggested, the first seemed too troublesome and the second wasn’t successful because the client has to use their private key for a signature but I did come up with the idea of creating a modified WSS4J jar where I altered the WSDoAllReceiver to validate the x509 certificate against the keystore which I would load for each call.
          Its not quite as fast as using the keystore in memory but it saves the hassle of not having to restart after each alteration in the keystore.

  4. Keerthivasan says:

    keerthi here,

    these lines of code from above,
    // acquire X509 trust manager from factory
    TrustManager tms[] = tmf.getTrustManagers();
    for (int i = 0; i < tms.length; i++) {
    if (tms[i] instanceof X509TrustManager) {
    trustManager = (X509TrustManager)tms[i];
    return;
    }
    }

    i have seen them in so many places.. but i just wonder why we are gettin the first X509TrustManager instance only.. what are the other trust managers in that array.. thank you.

    • John Calcote says:

      Hi Keerthi – There are multiple kinds of trust managers. X509 is only one of them. But there’s only one of each in the trust manager array. So, in this case, we’re looking for *the* X509 trust manager within the array of trust managers that might exist. In practice, there’s often only one or two, but we still have to look for the one we want.

      • Keerthivasan says:

        thank u very much John.. I think I am fine understanding the concept now..
        Thank u once again for your voluntary help.. very much appreciated,.. 🙂

  5. George says:

    Hello,

    First of all, great tutorial! 🙂
    Second, I have a question regarding a certificate issue. My server application is running on a WAS Server which is configured to run on https. I followed your tutorial and I wanted to make my client app, which should connect to the WAS sever through https from my Java code. The problem is that I don’t know what certificate should I put in my trustedStorePath, I’ve tryied to access the WAS server via my browser and save the certificate using my browser, but the problem is that I get an exception:
    java.io.IOException: Invalid keystore format
    how can I get around this?

    Thanks!
    Your help is greatly appreciated!

  6. Grasshopper says:

    With this example, I encountered exceptions that looked like this:

    Caused by: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty

    These were completely valid exceptions thrown by the wrapped X509TrustManager’s checkServerTrusted method. I change the catch from CertificateException to just regular Exception and after that things worked quite nicely.

  7. Hi John,

    Thanks for the great post! I’d like to reuse the implementation of ReloadableX509TrustManager. Can you please let me know if there is any licensing or terms of use involved?

    – Nishant

  8. Rangan says:

    java.io.IOException: Error initializing server socket factory SSL
    context: Invalid keystore format

    I was trying to do an SSL implementation in my webservice call to JBOSS ESB server, i use a self signed certificate. I get the above error.

    Below is the small extract from jboss-esb.xml file, shows how i refer my keystore and truststore file inside the xml file. Please let me know where exactly i go wrong.


    • Geo says:

      What keystore format are you using?
      In what format do you store the certificate in your keystore and in what format are you reading it out?

      • Rangan says:

        format ? We are generating cert using command “keytool genkey” and storing as .keystore only. We are using self signed certificates only. I guess it is generating in the default format only, am not too familiar on that. I guess JBOSS ESB is having issues understandng it.
        Appreciate your help if you could tell what exactly the command/steps to create keystore and truststore, i can try once.
        Thanks in advance.

        • Geo says:

          Hmm…your keystore is probably right, but maybe the self-signed certificate that you add to your keystore does not have the right format.

  9. Lee Ann says:

    Thanks for your help. One note, javax.net.ssl.trustStorePath whould be javax.net.ssl.trustStore remove the “Path” from the name.

Comments are closed.