Java 是不是支持 Let's Encrypt 证书?
Posted
技术标签:
【中文标题】Java 是不是支持 Let\'s Encrypt 证书?【英文标题】:Does Java support Let's Encrypt certificates?Java 是否支持 Let's Encrypt 证书? 【发布时间】:2016-03-10 16:33:00 【问题描述】:我正在开发一个 Java 应用程序,它通过 HTTP 查询远程服务器上的 REST API。出于安全原因,此通信应切换到 HTTPS。
现在Let's Encrypt 开始了他们的公开测试版,我想知道默认情况下 Java 当前是否可以使用(或确认将来可以使用)他们的证书。
Let's Encrypt 得到了他们的中间地址cross-signed by IdenTrust,这应该是个好消息。但是,我在此命令的输出中找不到这两个中的任何一个:
keytool -keystore "..\lib\security\cacerts" -storepass changeit -list
我知道可以在每台计算机上手动添加受信任的 CA,但由于我的应用程序应该可以免费下载和执行,无需任何进一步的配置,我正在寻找“开箱即用”的解决方案。你有什么好消息要告诉我吗?
【问题讨论】:
也可以在这里查看 Let's Encrypt 兼容性letsencrypt.org/docs/certificate-compatibility @potame “使用 Java 8u131,您仍然需要将证书添加到您的信任库”,所以如果您从 Let's Encrypt 获得证书,您需要将获得的证书添加到信任库吗?包括他们的 CA 还不够吗? @mxro 嗨——感谢您引起我的注意。我上面的 cmets 根本不成立(实际上问题比这更复杂,并且与我们的基础设施有关),我将删除它们,因为它们确实只会导致混乱。因此,如果您的 jdk > Java 8u101,Let's Encrypt 证书应该可以正常工作并得到正确识别和信任。 @potame 太好了。谢谢你的澄清! 【参考方案1】:[2016-06-08 更新:根据https://bugs.openjdk.java.net/browse/JDK-8154757,IdenTrust CA 将包含在 Oracle Java 8u101 中。]
[2016-08-05 更新:Java 8u101 已经发布,并且确实包含 IdenTrust CA:release notes]
Java 是否支持 Let's Encrypt 证书?
是的。 Let's Encrypt 证书只是一个普通的公钥证书。 Java 支持它(根据Let's Encrypt Certificate Compatibility,对于 Java 7 >= 7u111 和 Java 8 >= 8u101)。
Java 是否信任 Let's Encrypt 开箱即用的证书?
否/这取决于 JVM。高达 8u66 的 Oracle JDK/JRE 的信任库既不包含 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 并放入类路径)
基于How can I get a list of trusted root certificates in 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);
【讨论】:
还有一件事:我需要使用哪个文件? Let's Encrypt 提供one root and four intermediate certificates 供下载。我试过isrgrootx1.der
和lets-encrypt-x1-cross-signed.der
,但似乎都不是正确的。
@Hexaholic 取决于您要信任的内容以及您使用的站点如何配置证书。例如,转到https://helloworld.letsencrypt.org
并检查浏览器中的证书链(单击绿色图标)。对于这个,您需要站点特定证书、X1 中间证书(由 IdenTrust 交叉签名)或 DSTRootCAX3 证书。 ISRG Root X1
不适用于 helloworld 站点,因为它不在链中,即替代链。我会使用 DSTRoot 一个,只需通过浏览器导出,因为我在任何地方都没有看到它可以下载。
感谢您的代码。不要忘记在 keyStore.load(Files.newInputStream(ksPath)) 中关闭 InputStream。
@adapt-dev 不,为什么软件应该信任未知系统来提供好的证书?软件需要能够信任它实际信任的证书。如果这意味着我的 java 程序可以为其他人的程序安装证书,那将是一个安全漏洞。但这不会发生在这里,代码只在它自己的运行时添加证书
+1 用于显示不仅通过设置 javax.net.ssl.trustStore
系统属性作弊的代码,而且 -1 用于设置 JVM 的默认 SSLContext
。最好创建一个新的SSLSocketFactory
,然后将其用于各种连接(例如HttpUrlConnection
),而不是替换 VM 范围的 SSL 配置。 (我意识到此更改仅对正在运行的 JVM 有效,不会持续存在或影响其他程序。我只是认为明确说明您的配置适用于何处是一种更好的编程实践。)【参考方案2】:
我知道 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
【讨论】:
是的,OP 没有要求,但这对我来说是迄今为止最简单的解决方案! 我将此证书letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem.txt 导入到我较旧的 java 8 版本中,并且无法访问使用 let encrypt 证书的 Web 服务!这个解决方案既快速又简单。【参考方案3】:对于还不支持 Let's Encrypt 证书的 JDK,您可以按照此过程将它们添加到 JDK cacerts
(感谢 this)。
下载https://letsencrypt.org/certificates/上的所有证书(选择der格式)并用这种命令逐一添加(以letsencryptauthorityx1.der
为例):
keytool -import -keystore PATH_TO_JDK\jre\lib\security\cacerts -storepass changeit -noprompt -trustcacerts -alias letsencryptauthorityx1 -file PATH_TO_DOWNLOADS\letsencryptauthorityx1.der
【讨论】:
这改善了这种情况,但随后出现连接错误:javax.net.ssl.SSLException: java.lang.RuntimeException: Could not generate DH keypair【参考方案4】:对于我们这些愿意进行本地配置更改(包括备份配置文件)的人的详细答案:
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/
备份我们将要修改的 cacerts 文件,这样您就可以在不重新安装 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
【讨论】:
您还可以查看这个更轻量级的 repo:github.com/commandercool/ssl-check。用法很简单:wget https://github.com/commandercool/ssl-check/raw/master/SSLCheck.class && java -cp . SSLCheck google.com 443
.以上是关于Java 是不是支持 Let's Encrypt 证书?的主要内容,如果未能解决你的问题,请参考以下文章
使用 Let's Encrypt 保护 GitLab 页面得到 404
设置 Let's encrypt with Go - 握手错误
带有 Let's Encrypt 的 Google App Engine SSL“无法插入”