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
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