How to initialize TrustManagerFactory with multiple sources of trust?

My application has a private keystore that contains trusted, self-signed certificates for use on a local network - say mykeystore.jks . I want to be able to connect to public sites (e.g. google.com), as well as to my local networks, using the self-signed certificates that were provided locally.

The problem is that when I connect to https://google.com , path creation fails because installing my own keystore overrides the default keystore containing the root CAs bundled with the JRE, reporting an exception

 sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 

However, if I import the CA certificate into my keystore ( mykeystore.jks ), it works fine. Is there a way to support both?

I have my own TrustManger for this purpose,

 public class CustomX509TrustManager implements X509TrustManager { X509TrustManager defaultTrustManager; public MyX509TrustManager(KeyStore keystore) { TrustManagerFactory trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustMgrFactory.init(keystore); TrustManager trustManagers[] = trustMgrFactory.getTrustManagers(); for (int i = 0; i < trustManagers.length; i++) { if (trustManagers[i] instanceof X509TrustManager) { defaultTrustManager = (X509TrustManager) trustManagers[i]; return; } } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { try { defaultTrustManager.checkServerTrusted(chain, authType); } catch (CertificateException ce) { /* Handle untrusted certificates */ } } } 

Then I initialize SSLContext, TrustManager [] trustManagers = new TrustManager [] {new CustomX509TrustManager (key store)}; SSLContext customSSLContext = SSLContext.getInstance ("TLS"); customSSLContext.init (null, trustManagers, null);

and install the factory socket,

 HttpsURLConnection.setDefaultSSLSocketFactory(customSSLContext.getSocketFactory()); 

Main program

 URL targetServer = new URL(url); HttpsURLConnection conn = (HttpsURLConnection) targetServer.openConnection(); 

If I don’t install my own trustees, it communicates perfectly with https://google.com . How to get a "default trust manager" that points to the default keystore?

+8
java ssl x509 jsse
Apr 17 '14 at 22:13
source share
4 answers

In trustMgrFactory.init(keystore); you configure defaultTrustManager with your own keystore, not the default keystore.

Based on reading the source code for sun.security.ssl.TrustManagerFactoryImpl, it looks like trustMgrFactory.init((KeyStore) null); will do exactly what you need (load the system storage by default), and based on quick testing, it seems to work for me.

+14
Apr 23 '14 at 20:53 on
source share

The answer here is how I understood how to do this. If you just want to accept CA system certificates, as well as a custom certificate store, I simplified it into one class with some convenient methods. The full code is available here:

https://gist.github.com/HughJeffner/6eac419b18c6001aeadb

  KeyStore keystore; // Get your own keystore here SSLContext sslContext = SSLContext.getInstance("TLS"); TrustManager[] tm = CompositeX509TrustManager.getTrustManagers(keystore); sslContext.init(null, tm, null); 
+6
Mar 02 '16 at 22:03
source share

I ran into the same issue with Commons HttpClient. The working solution for my case was to create a delegation chain for PKIX TrustManagers as follows:

 public class TrustManagerDelegate implements X509TrustManager { private final X509TrustManager mainTrustManager; private final X509TrustManager trustManager; private final TrustStrategy trustStrategy; public TrustManagerDelegate(X509TrustManager mainTrustManager, X509TrustManager trustManager, TrustStrategy trustStrategy) { this.mainTrustManager = mainTrustManager; this.trustManager = trustManager; this.trustStrategy = trustStrategy; } @Override public void checkClientTrusted( final X509Certificate[] chain, final String authType) throws CertificateException { this.trustManager.checkClientTrusted(chain, authType); } @Override public void checkServerTrusted( final X509Certificate[] chain, final String authType) throws CertificateException { if (!this.trustStrategy.isTrusted(chain, authType)) { try { mainTrustManager.checkServerTrusted(chain, authType); } catch (CertificateException ex) { this.trustManager.checkServerTrusted(chain, authType); } } } @Override public X509Certificate[] getAcceptedIssuers() { return this.trustManager.getAcceptedIssuers(); } } 

And initialize the HttpClient as follows (yes, this is ugly):

 final SSLContext sslContext; try { sslContext = SSLContext.getInstance("TLS"); final TrustManagerFactory javaDefaultTrustManager = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); javaDefaultTrustManager.init((KeyStore)null); final TrustManagerFactory customCaTrustManager = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); customCaTrustManager.init(getKeyStore()); sslContext.init( null, new TrustManager[]{ new TrustManagerDelegate( (X509TrustManager)customCaTrustManager.getTrustManagers()[0], (X509TrustManager)javaDefaultTrustManager.getTrustManagers()[0], new TrustSelfSignedStrategy() ) }, secureRandom ); } catch (final NoSuchAlgorithmException ex) { throw new SSLInitializationException(ex.getMessage(), ex); } catch (final KeyManagementException ex) { throw new SSLInitializationException(ex.getMessage(), ex); } SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext); PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager( RegistryBuilder.<ConnectionSocketFactory>create() .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", sslSocketFactory) .build() ); //maximum parallel requests is 500 cm.setMaxTotal(500); cm.setDefaultMaxPerRoute(500); CredentialsProvider cp = new BasicCredentialsProvider(); cp.setCredentials( new AuthScope(apiSettings.getIdcApiUrl(), 443), new UsernamePasswordCredentials(apiSettings.getAgencyId(), apiSettings.getAgencyPassword()) ); client = HttpClients.custom() .setConnectionManager(cm) .build(); 

In your case with a simple HttpsURLConnection, you can go through with a simplified version of the delegating class:

 public class TrustManagerDelegate implements X509TrustManager { private final X509TrustManager mainTrustManager; private final X509TrustManager trustManager; public TrustManagerDelegate(X509TrustManager mainTrustManager, X509TrustManager trustManager) { this.mainTrustManager = mainTrustManager; this.trustManager = trustManager; } @Override public void checkClientTrusted( final X509Certificate[] chain, final String authType) throws CertificateException { this.trustManager.checkClientTrusted(chain, authType); } @Override public void checkServerTrusted( final X509Certificate[] chain, final String authType) throws CertificateException { try { mainTrustManager.checkServerTrusted(chain, authType); } catch (CertificateException ex) { this.trustManager.checkServerTrusted(chain, authType); } } @Override public X509Certificate[] getAcceptedIssuers() { return this.trustManager.getAcceptedIssuers(); } } 
+1
Feb 17 '16 at 16:12
source share

When you initialize SSLContext, you provide an array of TrustManagers. You supply two: the default is the JRE trust trust, and the other is yours. The delegation models the wrong answer here.

-one
Apr 18
source share



All Articles