Java是否支持“让我们加密证书”?


124

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

现在,让我们加密开始了其公开测试版,我想知道Java默认情况下当前是否可以使用其证书(或被确认可以在将来使用)。

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

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

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


1
也可以在此处检查Let's Encrypt兼容性letsencrypt.org/docs/certificate-compatibility
potame

@potame“使用Java 8u131时,您仍然必须将证书添加到信任库中”,因此,如果您从Let's Encrypt获得证书,则需要将获得的证书添加到信任库中?包括他们的CA是否足够?
mxro

1
@mxro嗨-感谢您对此的关注。我上面的评论根本不成立(事实上,问题比这更复杂,并且与我们的基础架构有关),我将删除它们,因为它们确实只会导致混乱。因此,如果您使用的是jdk> Java 8u101,则“加密”证书应该可以正常工作并得到正确的识别和信任。
potame

@potame太好了。谢谢你的澄清!
mxro

Answers:


142

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

[ 更新2016-08-05:Java 8u101已发布,并且确实包含IdenTrust CA:发行说明 ]


Java是否支持“让我们加密证书”?

是。Let's Encrypt证书只是常规的公钥证书。Java支持它(根据让我们加密证书兼容性,对于Java 7> = 7u111和Java 8> = 8u101)。

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

否/它取决于JVM。最高8u66的Oracle JDK / JRE的信任库既不包含专门的Let's Encrypt CA,也没有交叉签名的IdenTrustCA。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并放入classpath中)

基于“ 如何获取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);
        }
    }

}

还有一件事:我需要使用哪个文件?让我们加密提供 一个根目录和四个中间证书供下载。我尝试了isrgrootx1.derlets-encrypt-x1-cross-signed.der,但似乎都不是正确的选择。
Hexaholic 2015年

@Hexaholic取决于您要信任的内容以及所使用站点的证书配置方式。https://helloworld.letsencrypt.org例如,转到并在浏览器中检查证书链(单击绿色图标)。为此,您需要特定于站点的证书,X1中间证书(由IdenTrust交叉签名)或DSTRootCAX3证书。的ISRG Root X1,因为它不是在链,这是替代链不为HelloWorld现场工作。我会使用DSTRoot,只是通过浏览器导出,因为我没有看到它可以在任何地方下载。
zapl

1
谢谢你的代码。不要忘记在keyStore.load(Files.newInputStream(ksPath))中关闭InputStream。
Michael Wyraz '16

3
@ adapt-dev不,为什么软件应该信任未知系统来提供良好的证书?软件需要能够信任它实际信任的证书。如果这将意味着我的java程序可以为其他人的程序安装证书,那将是一个安全漏洞。但这并没有发生,代码仅在其自己的运行时中添加了证书
zapl

3
+1表示不只是通过设置javax.net.ssl.trustStoresystem属性来欺骗代码,而-1表示随后设置JVM的default SSLContext。最好创建一个新的SSLSocketFactory,然后将其用于各种连接(例如HttpUrlConnection),而不是替换整个VM范围的SSL配置。(我知道此更改仅对正在运行的JVM有效,并且不会持久化或影响其他程序。我只是认为最好明确地说明您的配置适用于何处。)
Christopher Schultz

65

我知道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


6
是的,OP并没有要求,但这对我来说是最简单的解决方案!
Auspex

我将此证书letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem.txt导入到我的较早的Java 8构建中,并且无法访问使用let加密证书的Web服务!该解决方案既快速又简单。
ezwrighter

56

对于愿意进行本地配置更改的用户(包括备份配置文件)的详细解答:

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.导入证书

我在设置了JAVA_HOME环境变量的Mac OS X上。以后的命令将假定为您要修改的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

8

对于尚不支持“让我们加密”证书的JDK,您可以cacerts按照以下步骤将其添加到JDK 中(由于这样做)。

将所有证书下载到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:无法生成DH密钥对
nafg 2016年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.