17

I am distributing a library jar for internal clients, and the library includes a certificate which it uses to call a service that is also internal to our network.

The trust manager is set up as follows

    TrustManagerFactory trustManagerFactory = 
      TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    KeyStore keystore = KeyStore.getInstance("JKS");
    InputStream keystoreStream = 
      clazz.getClassLoader().getResourceAsStream("certs.keystore"); // (on classpath)
    keystore.load(keystoreStream, "pa55w0rd".toCharArray());
    trustManagerFactory.init(keystore);
    TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
    SSLContext context = SSLContext.getInstance("SSL");
    context.init(null, trustManagers, null);

    SSLSocketFactory socketFact = context.getSocketFactory();
    connection.setSSLSocketFactory(socketFact);

All of this works fine except in cases where users need other certificates or the default certificate.

I tried this Registering multiple keystores in JVM with no luck (I am having trouble generalizing it for my case)

How can I use my cert and still allow user libraries to use their own certs as well?

Victor Grazi
  • 15,563
  • 14
  • 61
  • 94
  • 2
    Did you try the composite trust manager in the linked post? It sounds like just the thing, what error did you get? – ewramner May 23 '18 at 15:28
  • 1
    'The library includes a certificate which it uses to call a service'. Surely you mean the *service* uses it and your client needs to *trust* it? It isn't the same thing. – user207421 May 24 '18 at 06:50

2 Answers2

5

You are configuring a connection with a custom keystore acting as a truststore ( a certificate of your server that you trust). You are not overriding the default JVM behaviour, so the rest of the connection that other applications that include your library can make will not be affected.

Therefore you do not need a multiple keystore manager, in fact, your code works perfectly.

I've attached a full example below using a keystore google.jks which includes Google's root CA, and a connection using the default JVM truststore. This is the output

request("https://www.google.com/", "test/google.jks", "pa55w0rd"); //OK 
request("https://www.aragon.es/", "test/google.jks", "pa55w0rd");  // FAIL sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
request("https://www.aragon.es/", null, null); //OK

The problem is not in the code you have attached, so check the following in your code:

  • The truststore certs.keystore is really found in your classpath

  • Truststore settings are not set at JVM level using -Djavax.net.ssl.trustStore

  • The errors found (please include it in your question) are really related to the SSL connection


package test;

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyStore;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;


public class HTTPSCustomTruststore {

    public final static void main (String argv[]) throws Exception{
        request("https://www.google.com/", "test/google.jks", "pa55w0rd"); //Expected OK 
        request("https://www.aragon.es/","test/google.jks","pa55w0rd");  // Expected  FAIL
        request("https://www.aragon.es/",null,null); //using default truststore. OK 
    }

    public static void configureCustom(HttpsURLConnection connection, String truststore, String pwd)throws Exception{
        TrustManagerFactory trustManagerFactory = 
                TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        KeyStore keystore = KeyStore.getInstance("JKS");
        InputStream keystoreStream = HTTPSCustomTruststore.class.getClassLoader().getResourceAsStream(truststore);
        keystore.load(keystoreStream, pwd.toCharArray());
        trustManagerFactory.init(keystore);
        TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
        SSLContext context = SSLContext.getInstance("SSL");
        context.init(null, trustManagers,  new java.security.SecureRandom());

        SSLSocketFactory socketFact = context.getSocketFactory();
        connection.setSSLSocketFactory(socketFact);
    }


    public static void request(String urlS, String truststore, String pwd) {
        try {
            URL url = new URL(urlS);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            if (truststore != null) {
                configureCustom((HttpsURLConnection) conn, truststore, pwd);
            }   
            conn.connect();

            int statusCode = conn.getResponseCode();
            if (statusCode != 200) {
                System.out.println(urlS + " FAIL");
            } else {
                System.out.println(urlS + " OK");
            }
        } catch (Exception e) {
            System.out.println(urlS + " FAIL " + e.getMessage());
        }
    }
}
Ahmed Ashour
  • 5,179
  • 10
  • 35
  • 56
pedrofb
  • 37,271
  • 5
  • 94
  • 142
  • I haven't had a chance to try this but I accepted the answer so the bounty would not expire. Will get back to it after the holiday – Victor Grazi May 27 '18 at 21:15
-2

You could import the default certificates into your custom store to have a combined custom store and use that.

Sean F
  • 4,344
  • 16
  • 30
  • 1
    That wouldn't survive a JRE upgrade. If the standard `cacerts` changed, this client would never trust the new CAs, and would continue to trust any CAs that got removed. – user207421 May 24 '18 at 06:51
  • Correct you would need to automate the process, make it part of the build, so it is always up to date. – Sean F May 24 '18 at 06:53
  • 1
    ... and redeploy it every time there is a JDK update. Keeps you busy, with things you shouldn't have to do at all. – user207421 May 24 '18 at 10:29
  • It's not 'busy' if automated – Sean F May 24 '18 at 13:20