Essentially, get hold of the default trust manager, create a second trust manager that uses your own trust store. Wrap them both in a custom trust manager implementation that delegates call to both (falling back on the other when one fails).
1TrustManagerFactory tmf = TrustManagerFactory 2 .getInstance(TrustManagerFactory.getDefaultAlgorithm()); 3// Using null here initialises the TMF with the default trust store. 4tmf.init((KeyStore) null); 5 6// Get hold of the default trust manager 7X509TrustManager defaultTm = null; 8for (TrustManager tm : tmf.getTrustManagers()) { 9 if (tm instanceof X509TrustManager) { 10 defaultTm = (X509TrustManager) tm; 11 break; 12 } 13} 14 15FileInputStream myKeys = new FileInputStream("truststore.jks"); 16 17// Do the same with your trust store this time 18// Adapt how you load the keystore to your needs 19KeyStore myTrustStore = KeyStore.getInstance(KeyStore.getDefaultType()); 20myTrustStore.load(myKeys, "password".toCharArray()); 21 22myKeys.close(); 23 24tmf = TrustManagerFactory 25 .getInstance(TrustManagerFactory.getDefaultAlgorithm()); 26tmf.init(myTrustStore); 27 28// Get hold of the default trust manager 29X509TrustManager myTm = null; 30for (TrustManager tm : tmf.getTrustManagers()) { 31 if (tm instanceof X509TrustManager) { 32 myTm = (X509TrustManager) tm; 33 break; 34 } 35} 36 37// Wrap it in your own class. 38final X509TrustManager finalDefaultTm = defaultTm; 39final X509TrustManager finalMyTm = myTm; 40X509TrustManager customTm = new X509TrustManager() { 41 @Override 42 public X509Certificate[] getAcceptedIssuers() { 43 // If you're planning to use client-cert auth, 44 // merge results from "defaultTm" and "myTm". 45 return finalDefaultTm.getAcceptedIssuers(); 46 } 47 48 @Override 49 public void checkServerTrusted(X509Certificate[] chain, 50 String authType) throws CertificateException { 51 try { 52 finalMyTm.checkServerTrusted(chain, authType); 53 } catch (CertificateException e) { 54 // This will throw another CertificateException if this fails too. 55 finalDefaultTm.checkServerTrusted(chain, authType); 56 } 57 } 58 59 @Override 60 public void checkClientTrusted(X509Certificate[] chain, 61 String authType) throws CertificateException { 62 // If you're planning to use client-cert auth, 63 // do the same as checking the server. 64 finalDefaultTm.checkClientTrusted(chain, authType); 65 } 66}; 67 68 69SSLContext sslContext = SSLContext.getInstance("TLS"); 70sslContext.init(null, new TrustManager[] { customTm }, null); 71 72// You don't have to set this as the default context, 73// it depends on the library you're using. 74SSLContext.setDefault(sslContext);
You don't have to set that context as the default context. How you use it depends on the client library you're using (and where it gets its socket factories from).
This being said, in principle, you'd always have to update the truststore as required anyway. The Java 7 JSSE Reference Guide had an "important note" about this, now downgraded to just a "note" in version 8 of the same guide:
The JDK ships with a limited number of trusted root certificates in the java-home/lib/security/cacerts file. As documented in keytool reference pages, it is your responsibility to maintain (that is, add and remove) the certificates contained in this file if you use this file as a truststore.
Depending on the certificate configuration of the servers that you contact, you may need to add additional root certificates. Obtain the needed specific root certificates from the appropriate vendor.