Frage

I was getting an SSL Handshake Exception error: PKIX "path does not chain" (described here). I fixed it by importing a certificate chain using openssl:

openssl s_client -host www.envmgr.com -port 443 -showcerts > cert_chain.crt

and installed it into my JDK's keystore:

keytool -import -alias envmgrchain -file cert_chain.crt -keystore cacerts -storepass changeit

Well this works. Hooray. The problem is we'll be putting our application up on a cloud server like rackspace or AWS and I think there is a good chance that we won't have access to modify the keystore of the JVM to add this chain.

I thought, "no problem, I'll just add this certificate chain to the keystore programmatically" so I removed it from my JVM:

keytool -delete -alias envmgrchain -keystore cacerts -storepass changeit

and added this code:

    KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
    //Create an empty keystore that we can load certificate into
    trustStore.load(null);
    InputStream fis = new FileInputStream("cert_chain.crt");
    BufferedInputStream bis = new BufferedInputStream(fis);

    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    while(bis.available()>0) {
        Collection<? extends Certificate> certs = cf.generateCertificates(bis);
        Iterator<? extends Certificate> iter = certs.iterator();
        //Add each cert in the chain one at a time
        for(int i=0; i<certs.size(); i++) {
            Certificate cert = iter.next();
            String alias = "chaincert"+((i>0)?i:"");
            trustStore.setCertificateEntry(alias, cert);
        }
    }
    bis.close();
    fis.close();
//Add custom keystore to TrustManager
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(trustStore);
    SSLContext ctx = SSLContext.getInstance("TLSv1");
    ctx.init(null, tmf.getTrustManagers(), null);

But when I run it, the PKIX error reappears. Is the above code not equivalent to keytool -import? I feel like I'm either adding certificates to the KeyStore incorrectly, or I'm not installing the Keystore into the TrustManager in the right way.

FYI: I am also attempting to address this issue by implementing an X509TrustManager.

War es hilfreich?

Lösung

Here's code you can use for clients to programatically add your CA at runtime. You don't need to put it in any store - just carry around the PEM encoded file. You can even hard code it into your program so there's no separate file to manage.

static String CA_FILE = "ca-cert.pem";
...

FileInputStream fis = new FileInputStream(CA_FILE);
X509Certificate ca = (X509Certificate) CertificateFactory.getInstance("X.509")
                        .generateCertificate(new BufferedInputStream(fis));

KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setCertificateEntry(Integer.toString(1), ca);

TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);

SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
...

You will need a trusted distribution channel to ensure your program is not modified while sitting on the server waiting to be picked or while traveling down the wire while being installed.


openssl s_client -host www.envmgr.com -port 443 -showcerts > cert_chain.crt

You should only need to trust the root certificate, and not the entire chain. The server is responsible for sending all intermediate certificates required to build the chain. If the server is not sending all intermediate certificates required to build the chain, then the server is misconfigured.

The problem you are experiencing is called the "Which Directory" problem. Its a well known problem in PKI. Essentially, it means a client does not know where to go to fetch a missing intermediate certificate. You solve it by having the server send all required intermediates along with the server's certifcate. See OWASP's TLS Cheatsheet and Rule - Always Provide All Needed Certificates.


Just bike shedding, but there's a whole nother can of worms here with Java (especially Java 7 and lower):

SSLContext ctx = SSLContext.getInstance("TLSv1");
ctx.init(null, tmf.getTrustManagers(), null);

You can improve upon it, if desired. See SSLSocketFactoryEx at Which Cipher Suites to enable for SSL Socket?. It closes some gaps in protocol versions, cipher suites, etc provided by default in Java SSLSocketFactory.

Andere Tipps

Don't. You shouldn't be modifying files that come with the JRE. Next upgrade, your updates are gone. You should ship your own truststore, built from the one that comes with the JRE plus whatever extra certificates you want to trust. This is part of your application build.

If you then want to modify your own truststore at runtime, go ahead, but then you need to be aware that the JVM won't necessarily see the changes until it's restarted: it certainly won't see them within the same SSLContext that you use to obtain the certificates you want to add.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top