Raz's answer was a great start, but not flexible enough to meet my needs. MultiStoreKeyManager explicitly checks the custom KeyManager and then returns to jvm KeyManager if the operation fails. I really want to check jvm certificates first; The best solution should be able to handle any case. In addition, a working TrustManager cannot provide an answer.
I wrote several more flexible classes, CompositeX509KeyManager and CompositeX509TrustManager, which add support for any number of key stores in any order.
CompositeX509KeyManager
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; public class CompositeX509KeyManager implements X509KeyManager { private final List keyManagers; public CompositeX509KeyManager(List keyManagers) { this.keyManagers = ImmutableList.copyOf(keyManagers); } @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; } @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; } @Override public @Nullable PrivateKey getPrivateKey(String alias) { for (X509KeyManager keyManager : keyManagers) { PrivateKey privateKey = keyManager.getPrivateKey(alias); if (privateKey != null) { return privateKey; } } return null; } @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; } @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)); } @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)); } @Nullable private static <T> T[] emptyToNull(T[] arr) { return (arr.length == 0) ? null : arr; } }
CompositeX509TrustManager
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; public class CompositeX509TrustManager implements X509TrustManager { private final List trustManagers; public CompositeX509TrustManager(List 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;
Using
For the standard case of a single keystore + jvm keystore, you can connect it this way. I use Guava again, but in the 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); }
I extracted this from my blog post about this issue, which has a bit more details, motivation, etc. All code is there, so it is standalone. :)