最近公司打算将网站 HTTPS 证书更换为 Let’s Encrypt 的证书。虽然现在主流浏览器已经信任 Let’s Encrypt 证书了,但是对于一些 Java 老版本,还是会出现不兼容的情况。
为解决此问题,本文应运而生。
什么是 Let's Encrypt
Let's Encrypt 是一个免费,自动化和开放的证书颁发机构(CA),为公益而运行,由 Internet Security Research Group(ISRG) 提供服务。
Let's Encrypt 由 Mozilla、Cisco、Akamai、IdenTrust、EFF 等组织人员发起,主要的目的是为了推进网站从 HTTP 向 HTTPS 过度的进程,目前已经有越来越多的商家加入和赞助支持,其证书现在已经可以被所有主流的浏览器所信任。
证书兼容性
因 Let's Encrypt 证书较新,下列 JDK/JRE 旧版本会不信任 Let's Encrypt 证书
(查看 Java 版本:命令行输入 java -version ):
- Java 7 < 7u111
- Java 8 < 8u101
而抛出以下异常:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException
[... 以下输出省略 ...]
详细兼容性问题参考:https://letsencrypt.org/docs/certificate-compatibility/
检查 Java 环境是否兼容 Let's Encrypt 证书
自行编写程序测试
自行编写测试程序查看 Java 环境是否支持 Let's Encrypt 证书:
new URL("https://helloworld.letsencrypt.org").openConnection().connect();
然后查看是否抛出以下异常即可:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException
[... 以下输出省略 ...]
使用 SSLPing 进行测试
如还没有测试程序,可以使用 ping 测试程序:SSLPing(可测试任何 SSL/TLS 端口,不仅是 HTTPS)。下面将使用预先编译的 SSLPing.jar 进行测试(阅读源码后自行编译也非常容易):
在命令行输入以下内容以克隆 SSLPing 这个项目(请确保已安装 git)或点击链接下载 SSLPing.jar
git clone https://github.com/dimalinux/SSLPing.git
成功后进行测试:
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
[... 以下输出省略 ...]
问题解决
出现这种证书不兼容的情况,只是因为 Let’s Encrypt 证书太新,Java 老版本未将其加入根证书导致。
具体解决办法有两种共三个:
- 1.更新 Java update 版本
比如 JDK8_8u100,升级到 JDK8_8u101 及以上就可以了。但是搞 Java 的都是老学究,怕出现兼容问题,坚决不肯升级,所以不推荐。 - 2.自行将 Let's Encrypt 证书加入信任
Chrome 浏览器打开一个使用 Let's Encrypt 证书的网站:https://chengww.com,可以看到一共有三个证书:
现在关键是将哪个证书加入信任的问题。
参考 Let's Encrypt 官网 Chain of Trust 里面的这段说明:
Our roots are kept safely offline. We issue end-entity certificates to subscribers from the intermediates in the next section.
根证书 ISRG Root X1 (self-signed) 是离线安全保存的,在下一节中向中间人发放终端实体证书。
IdenTrust has cross-signed our intermediates. This allows our end certificates to be accepted by all major browsers while we propagate our own root.
Under normal circumstances, certificates issued by Let’s Encrypt will come from “Let’s Encrypt Authority X3”. The other intermediate, “Let’s Encrypt Authority X4”, is reserved for disaster recovery and will only be used should we lose the ability to issue with “Let’s Encrypt Authority X3”. The X1 and X2 intermediates were our first generation of intermediates. We’ve replaced them with new intermediates that are more compatible with Windows XP.
IdenTrust 和 Let's Encrypt 中间证书已经交叉签名,故所有主流浏览器都接受 Let’s Encrypt 的结束证书。
正常情况下,Let's Encrypt 颁发的证书将来自“Let's Encrypt Authority X3”。另一个中间件“Let's Encrypt Authority X4”保留用于灾难恢复,只有在Let's Encrypt 失去发出“Let's Encrypt Authority X3”的能力时才会使用。X1和X2中间体是Let's Encrypt 的第一代中间体。Let's Encrypt 用与Windows XP更兼容的新中间体替换它们。
也就是现在只有 Let's Encrypt Authority X3 是正在签名用的。将其加入信任即可。
参考解决方法(任选其一):
往 JRE 中导入 Let's Encrypt 证书(无需修改代码,推荐)
可以将 Let's Encrypt 证书加入 JRE 的信任证书,这种方式无需修改代码,简单快捷,推荐使用。
操作系统为 Mac OS X 或 Linux
- 检查 JAVA_HOME 已经正确配置
在终端上输入以下内容:
echo $JAVA_HOME
出现以下类似回应即为正确配置:
/Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/
如未配置请自行搜索配置 Java 环境变量即可。
- 下载 Let's Encrypt 中间证书
wget https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem
或直接复制上述链接下载即可
- 导入证书
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.pem"
出现
Certificate was added to keystore
即可
(注:当出现 java.io.FileNotFoundException... 时可能要检查相关文件路径是否正确)
操作系统为 Windows
- 检查 JAVA_HOME 已经正确配置
在命令行上输入以下内容:
echo %JAVA_HOME%
出现以下类似回应即为正确配置:
C:\Program Files (x86)\Java\jdk1.8.0_92
如未配置请自行搜索配置 Java 环境变量即可。
- 下载 Let's Encrypt 中间证书
wget https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem
或直接复制上述链接下载即可
- 导入证书
cd %JAVA_HOME%\bin
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.pem"
出现
Certificate was added to keystore
即可
(注:当出现 java.io.FileNotFoundException... 时可能要检查相关文件路径是否正确)
该方法至此已经完成,请检测是否成功。
程序运行时添加 Let's Encrypt 证书为信任证书
也可以在程序初始化或网络初始化时将 Let's Encrypt 证书添加进信任证书。
使用火狐浏览器访问 https://helloworld.letsencrypt.org ,然后将 Let's Encrypt Authority X3 导出为 .cer 文件,或点击下载 Let's Encrypt Authority X3.cer
将文件地址替换下述文件地址中: "Let's Encrypt Authority X3.cer"
具体请参考以下示例添加:
/**
* Created by chengww on 2018/9/18.
*/
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import javax.net.ssl.SSLHandshakeException;
public class SSLExample {
private static void initTrustManager() {
// Enter the path of the file named 'Let's Encrypt Authority X3.cer'
System.setProperty("javax.net.ssl.trustStore", "Let's Encrypt Authority X3.cer");
}
public static void main(String[] args) throws IOException {
initTrustManager();
// signed by default trusted CAs.
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"));
// qingstorage
testUrl(new URL("https://stor.qingstorage.com"));
// qingcloud
testUrl(new URL("https://www.qingcloud.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);
}
}
}
该方法至此已经完成,请检测是否成功。
升级 JDK/JRE 版本
可以直接升级的 JDK/JRE update 版本,7u111 及 8u101 之后已将 Let's Encrypt 证书加入信任。
前往 https://www.oracle.com/technetwork/java/javase/downloads/index.html 下拉到最后一项 Java Archive,点击 DOWNLOAD
选择 Accept License Agreement,下载对应的 JDK/JRE 版本后安装即可
该方法至此已经完成,请检测是否成功。
检查是否成功
重复操作上述 检查 Java 环境是否兼容 Let's Encrypt 证书 的内容即可。
文章作者: chengww
文章链接: https://chengww.com/archives/Java_compatible_certificate_of_Lets_Encrypt.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 chengww's blog!