我正在开发一个Java应用程序,它通过HTTP查询远程服务器上的REST API。出于安全原因,应将此通信切换为HTTPS。
现在Let's Encrypt已开始公开测试版,我想知道Java目前是否正常工作(或者确认将来有效),并且默认使用其证书。
让我们的加密获得他们的中间cross-signed by IdenTrust,这应该是个好消息。但是,我在这个命令的输出中找不到这两个中的任何一个:
keytool -keystore "..\lib\security\cacerts" -storepass changeit -list
我知道可以在每台机器上手动添加可信任的CA,但由于我的应用程序可以免费下载和执行而无需进一步配置,因此我正在寻找“开箱即用”的解决方案。你对我有好消息吗?
答案 0 :(得分:133)
[更新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的加密证书只是一个普通的公钥证书。 Java支持它(根据Let's Encrypt Certificate Compatibility,对于Java 7> = 7u111和Java 8> = 8u101)。
Java是否相信让开箱即用加密证书?
否/它取决于JVM。 Oracle JDK / JRE的信任库最高为8u66,既不包含Let的加密CA,也不包含交叉签名的IdenTrust CA.例如new URL("https://letsencrypt.org/").openConnection().connect();
会产生javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException
。
但是,您可以提供自己的验证器/定义包含所需根CA的自定义密钥库,或将证书导入JVM信任库。
以下是一些示例代码,演示如何在运行时将证书添加到默认信任库。您只需要添加证书(从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);
}
}
}
答案 1 :(得分:55)
我知道OP要求提供没有本地配置更改的解决方案,但是如果您想永久地将信任链添加到密钥库:
$ keytool -trustcacerts \
-keystore $JAVA_HOME/jre/lib/security/cacerts \
-storepass changeit \
-noprompt \
-importcert \
-file /etc/letsencrypt/live/hostname.com/chain.pem
答案 2 :(得分:49)
我们这些愿意进行本地配置更改的详细解答,包括备份配置文件:
如果您还没有测试程序,可以使用我的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的加密证书。在应用修复程序之前,让我们看看失败的样子:
$ 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 ...]
我在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
验证Java现在是否满意连接到SSL端口:
$ java -jar SSLPing/dist/SSLPing.jar helloworld.letsencrypt.org 443
About to connect to 'helloworld.letsencrypt.org' on port 443
Successfully connected
答案 3 :(得分:8)
对于尚不支持我们的加密证书的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