Update 2020: a kind contributor has transformed this blog post into an sslcontext library.
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");<br />
kmf.init(keyStore, password);<br />
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");<br />
tmf.init(keyStore);<br />
SSLContext sslContext = SSLContext.getInstance("SSL");<br />
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.
// CompositeX509KeyManager.java
package com.mycompany.ssl;
import java.net.Socket;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.List;
import javax.annotation.Nullable;
import javax.net.ssl.X509KeyManager;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
/**
* Represents an ordered list of {@link X509KeyManager}s with most-preferred managers first.
*
* This is necessary because of the fine-print on {@link SSLContext#init}:
* 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.)
*
* @author codyaray
* @since 4/22/2013
* @see http://stackoverflow.com/questions/1793979/registering-multiple-keystores-in-jvm
*/
public class CompositeX509KeyManager implements X509KeyManager {
private final List<X509KeyManager> keyManagers;
/**
* Creates a new {@link CompositeX509KeyManager}.
*
* @param keyManagers the X509 key managers, ordered with the most-preferred managers first.
*/
public CompositeX509KeyManager(List<X509KeyManager> keyManagers) {
this.keyManagers = ImmutableList.copyOf(keyManagers);
}
/**
* Chooses the first non-null client alias returned from the delegate
* {@link X509TrustManagers}, or {@code null} if there are no matches.
*/
@Override
public @Nullable String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
for (X509KeyManager keyManager : keyManagers) {
String alias = keyManager.chooseClientAlias(keyType, issuers, socket);
if (alias != null) {
return alias;
}
}
return null;
}
/**
* Chooses the first non-null server alias returned from the delegate
* {@link X509TrustManagers}, or {@code null} if there are no matches.
*/
@Override
public @Nullable String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
for (X509KeyManager keyManager : keyManagers) {
String alias = keyManager.chooseServerAlias(keyType, issuers, socket);
if (alias != null) {
return alias;
}
}
return null;
}
/**
* Returns the first non-null private key associated with the
* given alias, or {@code null} if the alias can't be found.
*/
@Override
public @Nullable PrivateKey getPrivateKey(String alias) {
for (X509KeyManager keyManager : keyManagers) {
PrivateKey privateKey = keyManager.getPrivateKey(alias);
if (privateKey != null) {
return privateKey;
}
}
return null;
}
/**
* Returns the first non-null certificate chain associated with the
* given alias, or {@code null} if the alias can't be found.
*/
@Override
public @Nullable X509Certificate[] getCertificateChain(String alias) {
for (X509KeyManager keyManager : keyManagers) {
X509Certificate[] chain = keyManager.getCertificateChain(alias);
if (chain != null && chain.length > 0) {
return chain;
}
}
return null;
}
/**
* Get all matching aliases for authenticating the client side of a
* secure socket, or {@code null} if there are no matches.
*/
@Override
public @Nullable String[] getClientAliases(String keyType, Principal[] issuers) {
ImmutableList.Builder aliases = ImmutableList.builder();
for (X509KeyManager keyManager : keyManagers) {
aliases.add(keyManager.getClientAliases(keyType, issuers));
}
return emptyToNull(Iterables.toArray(aliases.build(), String.class));
}
/**
* Get all matching aliases for authenticating the server side of a
* secure socket, or {@code null} if there are no matches.
*/
@Override
public @Nullable String[] getServerAliases(String keyType, Principal[] issuers) {
ImmutableList.Builder aliases = ImmutableList.builder();
for (X509KeyManager keyManager : keyManagers) {
aliases.add(keyManager.getServerAliases(keyType, issuers));
}
return emptyToNull(Iterables.toArray(aliases.build(), String.class));
}
private @Nullable <T> T[] emptyToNull(T[] arr) {
return (arr.length == 0) ? null : arr;
}
}
And the accompanying CompositeX509TrustManager:
// CompositeX509TrustManager.java
package com.mycompany.ssl;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
import javax.net.ssl.X509TrustManager;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
/**
* Represents an ordered list of {@link X509TrustManager}s with additive trust. If any one of the
* composed managers trusts a certificate chain, then it is trusted by the composite manager.
*
* This is necessary because of the fine-print on {@link SSLContext#init}:
* 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.)
*
* @author codyaray
* @since 4/22/2013
* @see http://stackoverflow.com/questions/1793979/registering-multiple-keystores-in-jvm
*/
public class CompositeX509TrustManager implements X509TrustManager {
private final List<X509TrustManager> trustManagers;
public CompositeX509TrustManager(List<X509TrustManager> trustManagers) {
this.trustManagers = ImmutableList.copyOf(trustManagers);
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
for (X509TrustManager trustManager : trustManagers) {
try {
trustManager.checkClientTrusted(chain, authType);
return; // someone trusts them. success!
} catch (CertificateException e) {
// maybe someone else will trust them
}
}
throw new CertificateException("None of the TrustManagers trust this certificate chain");
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
for (X509TrustManager trustManager : trustManagers) {
try {
trustManager.checkServerTrusted(chain, authType);
return; // someone trusts them. success!
} catch (CertificateException e) {
// maybe someone else will trust them
}
}
throw new CertificateException("None of the TrustManagers trust this certificate chain");
}
@Override
public X509Certificate[] getAcceptedIssuers() {
ImmutableList.Builder certificates = ImmutableList.builder();
for (X509TrustManager trustManager : trustManagers) {
certificates.add(trustManager.getAcceptedIssuers());
}
return Iterables.toArray(certificates.build(), X509Certificate.class);
}
}
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:
<br />
@Provides @Singleton<br />
SSLContext provideSSLContext(KeyStore keystore, char[] password) {<br />
String defaultAlgorithm = KeyManagerFactory.getDefaultAlgorithm();<br />
X509KeyManager customKeyManager = getKeyManager("SunX509", keystore, password);<br />
X509KeyManager jvmKeyManager = getKeyManager(defaultAlgorithm, null, null);<br />
X509TrustManager customTrustManager = getTrustManager("SunX509", keystore);<br />
X509TrustManager jvmTrustManager = getTrustManager(defaultAlgorithm, null);</p>
<p>KeyManager[] keyManagers = { new CompositeX509KeyManager(ImmutableList.of(jvmKeyManager, customKeyManager)) };<br />
TrustManager[] trustManagers = { new CompositeX509TrustManager(ImmutableList.of(jvmTrustManager, customTrustManager)) };</p>
<p>SSLContext context = SSLContext.getInstance("SSL");<br />
context.init(keyManagers, trustManagers, null);<br />
return context;<br />
}</p>
<p>private X509KeyManager getKeyManager(String algorithm, KeyStore keystore, char[] password) {<br />
KeyManagerFactory factory = KeyManagerFactory.getInstance(algorithm);<br />
factory.init(keystore, password);<br />
return Iterables.getFirst(Iterables.filter(<br />
Arrays.asList(factory.getKeyManagers()), X509KeyManager.class), null);<br />
}</p>
<p>private X509TrustManager getTrustManager(String algorithm, KeyStore keystore) {<br />
TrustManagerFactory factory = TrustManagerFactory.getInstance(algorithm);<br />
factory.init(keystore);<br />
return Iterables.getFirst(Iterables.filter(<br />
Arrays.asList(factory.getTrustManagers()), X509TrustManager.class), null); <br />
}<br />
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?
2 comments
Imported from the previous site.