Java 是否支持 Let's Encrypt 证书?

2022-08-31 08:10:54

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

现在 Let's Encrypt 开始了他们的公开测试版,我想知道 Java 当前是否默认使用他们的证书(或确认将来可以工作)。

Let's Encrypt得到了IdenTrust的中间交叉签名,这应该是个好消息。但是,我在此命令的输出中找不到这两个中的任何一个:

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

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


答案 1

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

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


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 并放入类路径)

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

}

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