Skip to content


Java SSL with Multiple KeyStores

For communication between internal services at BrightTag, we use self-signed certs on both the client and server. Simple and cheap (free!). Most of the time, these services only communicate over HTTPS with other internal services, so its been fine to use our own keystore; we didn’t need access to the “factory” certificates anyway. However, I ran into a case last week where I needed to be able to talk to both internal and external services and realized there’s no simple way to use multiple keystores in Java. We wanted to use both the standard JVM keystore and our custom keystore. The cleanest solution I found was to write my own CompositeKeyManager and CompositeTrustManagers. Creating a new keystore with both the standard JVM certs and our custom certs was also considered, but ultimately we didn’t want the responsibility of updating the standard certs in a bundled keystore.

Let’s dive right in. HTTP clients need an SSLContext or SSLSocketFactory (which can be retrieved from SSLContext#getSocketFactory). To initialize an SSLContext from your own keystore, you need an array of KeyManagers and an array of TrustManagers. A KeyManagerFactory and a TrustManagerFactory are used to extract the KeyManagers and TrustManagers from your keystore. The standard code looks something like this:

KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(keyStore, password);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

Okay, so I can just merge the KeyManager arrays  from each keystore, right?

Wrong, thanks to the fine-print associated with the  SSLContext initializer:

Only the first instance of a particular key and/or trust manager implementation type in the array is used. (For example, only the first javax.net.ssl.X509KeyManager in the array will be used.

In my case, both the JVM and our certs are X509, so this approach doesn’t work. Given that X509KeyManager is the only KeyManager implementation that ships with the JDK, I suspect this is the case for more or less everybody.

So what to do now?

As always, I turned to stackoverflow for an answer. This question addresses my problem and provides code showing a custom KeyManager implementation. Thanks to Raz for following up with an answer to his question, and everyone who chimed in to help him.

However, this solution wasn’t quite satisfactory to me. The MultiStoreKeyManager explicitly checks the custom KeyManager and then falls back to the jvm KeyManager if an operation fails. I actually want to check jvm certs first; the best solution should be able to handle either case. Additionally, the answer fails to provide a working TrustManager, so it doesn’t completely solve my multiple-keystore-with-SSL problem.

My approach was to apply the composite pattern a little more directly to create a CompositeX509KeyManager and CompositeX509TrustManager. They both take in a list of their delegates in order of preference. This adds support for any number of keystores in an arbitrary order. Whichever keystore (er, KeyManager/TrustManager) comes first in the injected list will be preferred to those coming later. My solutions use Guava because I heart Guava, but you should be able to replace them with another library or your own implementation of the few methods if you prefer.

Without further ado, ladies and gentlemen, I present to you the CompositeX509KeyManager.

And the accompanying CompositeX509TrustManager:

For the standard case of one keystore + jvm keystore, you can wire it up like this. I’m using Guava again, but in a Guicey wrapper this time:

@Provides @Singleton
SSLContext provideSSLContext(KeyStore keystore, char[] password) {
  String defaultAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
  X509KeyManager customKeyManager = getKeyManager("SunX509", keystore, password);
  X509KeyManager jvmKeyManager = getKeyManager(defaultAlgorithm, null, null);
  X509TrustManager customTrustManager = getTrustManager("SunX509", keystore);
  X509TrustManager jvmTrustManager = getTrustManager(defaultAlgorithm, null);
 
  KeyManager[] keyManagers = { new CompositeX509KeyManager(ImmutableList.of(jvmKeyManager, customKeyManager)) };
  TrustManager[] trustManagers = { new CompositeX509TrustManager(ImmutableList.of(jvmTrustManager, customTrustManager)) };
 
  SSLContext context = SSLContext.getInstance("SSL");
  context.init(keyManagers, trustManagers, null);
  return context;
}
 
private X509KeyManager getKeyManager(String algorithm, KeyStore keystore, char[] password) {
  KeyManagerFactory factory = KeyManagerFactory.getInstance(algorithm);
  factory.init(keystore, password);
  return Iterables.getFirst(Iterables.filter(
      Arrays.asList(factory.getKeyManagers()), X509KeyManager.class), null);
}
 
private X509TrustManager getTrustManager(String algorithm, KeyStore keystore) {
  TrustManagerFactory factory = TrustManagerFactory.getInstance(algorithm);
  factory.init(keystore);
  return Iterables.getFirst(Iterables.filter(
      Arrays.asList(factory.getTrustManagers()), X509TrustManager.class), null); 
}

Yep, Java is that verbose. But at least its clean, reusable, flexible, and future-proof.

I think this meets Raz’s initial request for “a solution that can dynamically register multiple keystores in addition to the default keystore/certs in jre into jvm.” What do you think?

Do you have a cleaner approach to mixing internal and external certs for SSL in Java?

Posted in Tutorials.


One Response

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

Continuing the Discussion

  1. Multiple Keystores in Apache Camel/HttpClient – Cody A. Ray linked to this post on May 9, 2013

    […] that we know how to use multiple SSL keystores in Java, how do we configure Apache HttpClient (embedded in Apache Camel) to use them? This is useful if […]



Some HTML is OK

or, reply to this post via trackback.

 



Log in here!