Java 支持 Let’s Encrypt 证书吗?

我正在开发一个 Java 应用程序,它通过 HTTP 在远程服务器上查询 REST API。出于安全原因,此通信应切换到 HTTPS。

既然 我们来加密吧已经开始了它们的公共测试,我想知道 Java 目前是否默认使用它们的证书工作(或者确认将来可以工作)。

让我们加密他们的中间 由 IdenTrust 交叉签名,这应该是好消息。但是,在这个命令的输出中,我找不到这两个中的任何一个:

keytool -keystore "..\lib\security\cacerts" -storepass changeit -list

我知道可以在每台机器上手动添加受信任的 CA,但是由于我的应用程序应该可以免费下载和执行,而不需要任何进一步的配置,所以我正在寻找“开箱即用”的解决方案。你有好消息要告诉我吗?

84129 次浏览

[ 更新2016-06-08: 根据 https://bugs.openjdk.java.net/browse/JDK-8154757,IdenTrust CA 将包含在 Oracle Java 8u101中。]

[ 更新2016-08-05: Java8u101已经发布,确实包括 IdenTrust CA: 释放通知书]


Java 支持 Let’s Encrypt 证书吗?

是的。Let’s Encrypt 证书只是一个普通的公钥证书。Java 支持它(根据 让我们加密证书兼容性,对于 Java7 > = 7u111和 Java8 > = 8u101)。

Java 是否信任“让我们开箱即用加密证书”?

不/这取决于 JVM。Oracle JDK/JRE 到8u66的信任存储区既不包含 Let’s Encrypt CA,也不包含交叉签名的 IdenTrust CA。例如,new URL("https://letsencrypt.org/").openConnection().connect();导致 javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException

但是,您可以提供自己的验证器/定义一个自定义密钥存储库,其中包含所需的根 CA 或将证书导入到 JVM 信任存储库中。

Https://community.letsencrypt.org/t/will-the-cross-root-cover-trust-by-the-default-list-in-the-jdk-jre/134/10也讨论了这个话题。


下面是一些示例代码,演示如何在运行时将证书添加到默认信任存储库。您只需要添加证书(从 firefox 导出为。Der 并放入类路径)

基于 如何在 Java 中获得受信任的根证书列表?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);
}
}


}

我知道 OP 要求在不改变本地配置的情况下提供解决方案,但是如果您想将信任链永久地添加到密钥存储库:

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

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

对于尚不支持 Let’s Encrypt 证书的 JDK,您可以按照这个过程(感谢 这个)将它们添加到 JDK cacerts中。

下载 https://letsencrypt.org/certificates/上的所有证书(选择 格式) ,并使用以下命令逐个添加它们(例如 letsencryptauthorityx1.der) :

keytool -import -keystore PATH_TO_JDK\jre\lib\security\cacerts -storepass changeit -noprompt -trustcacerts -alias letsencryptauthorityx1 -file PATH_TO_DOWNLOADS\letsencryptauthorityx1.der

对于那些愿意进行本地配置更改(包括备份配置文件)的人来说,详细的答案是:

1. 在更改之前测试它是否正常工作

如果您还没有测试程序,可以使用我的 java SSLPing ping 程序来测试 TLS 握手(可以使用任何 SSL/TLS 端口,而不仅仅是 HTTPS)。我将使用预先构建的 SSLPing.jar,但是阅读代码并自己构建它是一项快速而简单的任务:

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

由于我的 Java 版本早于1.8.0 _ 101(在撰写本文时还没有发布) ,因此在默认情况下,Let’s Encrypt 证书不会进行验证。在应用修复程序之前,让我们看看失败是什么样子:

$ java -jar SSLPing/dist/SSLPing.jar helloworld.letsencrypt.org 443
About to connect to 'helloworld.letsencrypt.org' on port 443
javax.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. 导入证书

我在 Mac OS X 上使用 JAVA _ HOME 环境变量。稍后的命令将假定这个变量是为您正在修改的 Java 安装设置的:

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

为我们将要修改的 ccerts 文件做一个备份,这样您就可以在不重新安装 JDK 的情况下取消任何更改:

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

下载需要导入的签名证书:

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

执行导入:

$ 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. 在更改后验证它是否正常工作

验证 Java 现在是否可以连接到 SSL 端口:

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