Does Java support Let's Encrypt certificates? Does Java support Let's Encrypt certificates? java java

Does Java support Let's Encrypt certificates?


[Update 2016-06-08: According to https://bugs.openjdk.java.net/browse/JDK-8154757 the IdenTrust CA will be included in Oracle Java 8u101.]

[Update 2016-08-05: Java 8u101 has been released and does indeed include the IdenTrust CA: release notes]


Does Java support Let's Encrypt certificates?

Yes. The Let's Encrypt certificate is just a regular public key certificate. Java supports it (according to Let's Encrypt Certificate Compatibility, for Java 7 >= 7u111 and Java 8 >= 8u101).

Does Java trust Let's Encrypt certificates out of the box?

No / it depends on the JVM. The truststore of Oracle JDK/JRE up to 8u66 contains neither the Let's Encrypt CA specifically nor the IdenTrust CA that cross signed it. new URL("https://letsencrypt.org/").openConnection().connect(); for example results in javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException.

You can however provide your own validator / define a custom keystore that contains the required root CA or import the certificate into the JVM truststore.

https://community.letsencrypt.org/t/will-the-cross-root-cover-trust-by-the-default-list-in-the-jdk-jre/134/10 discusses the topic as well.


Here is some example code that shows how to add a certificate to the default truststore at runtime. You'll just need to add the certificate (exported from firefox as .der and put in classpath)

Based on How can I get a list of trusted root certificates in Java? and http://developer.android.com/training/articles/security-ssl.html#UnknownCa

import java.io.BufferedInputStream;import java.io.IOException;import java.io.InputStream;import java.net.URL;import java.net.URLConnection;import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;import java.security.KeyStore;import java.security.cert.Certificate;import java.security.cert.CertificateFactory;import java.security.cert.PKIXParameters;import java.security.cert.TrustAnchor;import java.security.cert.X509Certificate;import javax.net.ssl.SSLContext;import javax.net.ssl.SSLHandshakeException;import javax.net.ssl.TrustManagerFactory;public class SSLExample {    // BEGIN ------- ADDME    static {        try {            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());            Path ksPath = Paths.get(System.getProperty("java.home"),                    "lib", "security", "cacerts");            keyStore.load(Files.newInputStream(ksPath),                    "changeit".toCharArray());            CertificateFactory cf = CertificateFactory.getInstance("X.509");            try (InputStream caInput = new BufferedInputStream(                    // this files is shipped with the application                    SSLExample.class.getResourceAsStream("DSTRootCAX3.der"))) {                Certificate crt = cf.generateCertificate(caInput);                System.out.println("Added Cert for " + ((X509Certificate) crt)                        .getSubjectDN());                keyStore.setCertificateEntry("DSTRootCAX3", crt);            }            if (false) { // enable to see                System.out.println("Truststore now trusting: ");                PKIXParameters params = new PKIXParameters(keyStore);                params.getTrustAnchors().stream()                        .map(TrustAnchor::getTrustedCert)                        .map(X509Certificate::getSubjectDN)                        .forEach(System.out::println);                System.out.println();            }            TrustManagerFactory tmf = TrustManagerFactory                    .getInstance(TrustManagerFactory.getDefaultAlgorithm());            tmf.init(keyStore);            SSLContext sslContext = SSLContext.getInstance("TLS");            sslContext.init(null, tmf.getTrustManagers(), null);            SSLContext.setDefault(sslContext);        } catch (Exception e) {            throw new RuntimeException(e);        }    }    // END ---------- ADDME    public static void main(String[] args) throws IOException {        // signed by default trusted CAs.        testUrl(new URL("https://google.com"));        testUrl(new URL("https://www.thawte.com"));        // signed by letsencrypt        testUrl(new URL("https://helloworld.letsencrypt.org"));        // signed by LE's cross-sign CA        testUrl(new URL("https://letsencrypt.org"));        // expired        testUrl(new URL("https://tv.eurosport.com/"));        // self-signed        testUrl(new URL("https://www.pcwebshop.co.uk/"));    }    static void testUrl(URL url) throws IOException {        URLConnection connection = url.openConnection();        try {            connection.connect();            System.out.println("Headers of " + url + " => "                    + connection.getHeaderFields());        } catch (SSLHandshakeException e) {            System.out.println("Untrusted: " + url);        }    }}


I know the OP asked for a solution without local configuration changes, but in case you want to add the trust chain to the keystore permanently:

$ keytool -trustcacerts \    -keystore $JAVA_HOME/jre/lib/security/cacerts \    -storepass changeit \    -noprompt \    -importcert \    -file /etc/letsencrypt/live/hostname.com/chain.pem

source: https://community.letsencrypt.org/t/will-the-cross-root-cover-trust-by-the-default-list-in-the-jdk-jre/134/13


Detailed answer for those of us willing to make local config changes that includes backing up the config file:

1. Test if it is working before the changes

If you don't have a test program already, you can use my java SSLPing ping program which tests the TLS handshake (will work with any SSL/TLS port, not just HTTPS). I'll use the prebuilt SSLPing.jar, but reading the code and building it yourself is a quick and easy task:

$ git clone https://github.com/dimalinux/SSLPing.gitCloning into 'SSLPing'...[... output snipped ...]

Since my Java version is earlier than 1.8.0_101 (not released at the time of this writing), a Let's Encrypt certificate will not verify by default. Let's see what failure looks like before applying the fix:

$ java -jar SSLPing/dist/SSLPing.jar helloworld.letsencrypt.org 443About to connect to 'helloworld.letsencrypt.org' on port 443javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target[... output snipped ...]

2. Import the certificate

I'm on Mac OS X with the JAVA_HOME environment variable set. Later commands will assume this variable is set for the java installation you are modifying:

$ echo $JAVA_HOME /Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/

Make a backup of the cacerts file we will be modifying so you can back out any change without reinstalling the JDK:

$ sudo cp -a $JAVA_HOME/jre/lib/security/cacerts $JAVA_HOME/jre/lib/security/cacerts.orig

Download the signing certificate we need to import:

$ wget https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.der

Perform the import:

$ sudo keytool -trustcacerts -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit -noprompt -importcert -alias lets-encrypt-x3-cross-signed -file lets-encrypt-x3-cross-signed.der Certificate was added to keystore

3. Verify that it is working after the changes

Verify that Java is now happy connecting to the SSL port:

$ java -jar SSLPing/dist/SSLPing.jar helloworld.letsencrypt.org 443About to connect to 'helloworld.letsencrypt.org' on port 443Successfully connected