前提介绍
在掉坑的过程中,面对老证书的CA为DST,而新证书的CA为ISRG,客户端可以正常调用老证书,而无法正确调用新证书。虽然购买了TrustAsia付费证书,但是对于到底为什么不行还可以进一步探索一下。
下图表示的是tls验证错误。切换华为CDN的时候忘记了打开TLS1开关。
下图表示的证书路径错误,是个大问题。
通过尝试,使用jdk7直接复现了该问题。这一点与客户端技术人员所说,客户端程序采用jdk8符合。
复现
复现的主要过程是,使用jetbrain开发工具,本地安装jdk7和jdk8,借助copilot编写简单的java程序,实现对站点www.op123.ren(ISRG证书)和www.sddts.cn(DST证书)和bapi.zcyun.cn(aaa)的测试。
java代码
import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
public class Main {
public static void main(String[] args) {
System.setProperty("javax.net.debug", "ssl");
try {
// URL url = new URL("https://bapi.zcyun.cn");
// URL url = new URL("https://www.op123.ren");
URL url = new URL("https://www.sddts.cn");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.connect();
Certificate[] certs = conn.getServerCertificates();
System.out.println("Number of certificates: " + certs.length);
for (Certificate cert : certs) {
X509Certificate x509Cert = (X509Certificate) cert;
System.out.println("Version: " + x509Cert.getVersion());
System.out.println("Subject: " + x509Cert.getSubjectDN());
System.out.println("Signature Algorithm: " + x509Cert.getSigAlgName());
System.out.println("Key: " + x509Cert.getPublicKey());
System.out.println("Validity: From " + x509Cert.getNotBefore() + " to " + x509Cert.getNotAfter());
System.out.println("Issuer: " + x509Cert.getIssuerDN());
System.out.println("------------------------");
}
// Fetch the website content
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
StringBuilder content = new StringBuilder();
while ((line = reader.readLine()) != null) {
content.append(line);
}
reader.close();
System.out.println("Website Content: " + content.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
针对jdk1.7版本测试
访问bapi.zcyun.cn
访问www.sddts.cn
访问www.op123.ren
采用jdk7最后一个版本b147测试依旧不能正常访问。而且,jdk7默认情况下只能使用TLSv1,所以,测试过程中使用的Nginx或者bt面板提供的Nginx服务需要在配置文件中为配置参数ssl_protocols加上TLSv1。
测试jdk8
从低版本测试
使用xxx版本访问三个测试站点都没有问题。但是,估计早起的jdk8版本中可能有不行的。
从jdk早起版本开始测试,直到测试到jdk8u131,出现了与问题一样的现象,CA为DST生效,但是CA ISRG失效。
测试将jdk8u151的caccert文件拷贝到jdk8u131的对应位置
不可以直接拷贝整个security文件夹内容,会报错
只是拷贝jre/lib/security/cacerts文件到对应目录覆盖原有文件后,可以正常访问CA为ISRG的测试站点。
总结,在复现现象中,测试多个jdk8版本,从jdk8u131开始,到jdk8u151之间的某一个版本,与客户端技术人员所使用的jdk版本一致。
第二天 ,使用脚本将jdk8u131和jdk8u151的jre/lib/security/cacerts文件进行了对比,发现u8131只有CA DST的内容且过期,而且没有CA ISRG的内容;相比之下,8u151既有CA DST也有CA ISRG。
最终,测试了jdk8中的112 121 131 141 151,发现包括131版本在内以及之前的jdk8版本,均具有CA DST,但是不具有CA ISRG。据此推测,第三方公司使用的jdk版本在jdk131之前。
拒gpt给出的jdk8大致版本历史如下:
JDK 8u5 (2014-04-15): 早期的更新,包括bug修复。
JDK 8u20 (2014-08-19): 引入了Java Mission Control 5.4。
JDK 8u40 (2015-03-03): 增加了新的JavaFX功能,以及性能和安全性改进。
JDK 8u60 (2015-08-18): 包括对JavaFX的改进,性能增强和bug修复。
JDK 8u65/8u66 (2015-10-20): 主要是安全更新和bug修复。
JDK 8u91/8u92 (2016-04-19): 引入了一些新的特性,包括安全性增强和性能改进。
JDK 8u101/8u102 (2016-07-19): 安全性更新和bug修复。
JDK 8u111/8u112 (2016-10-18): 安全性更新和bug修复。
JDK 8u121 (2017-01-17): 引入了额外的安全强化功能。
JDK 8u131 (2017-04-18): 安全性和性能更新。
JDK 8u141 (2017-07-18): 包含安全性更新和bug修复。
JDK 8u151/8u152 (2017-10-17): 安全性更新,以及一些新增特性和bug修复。
JDK 8u161/8u162 (2018-01-16): 安全性更新和性能改进。
JDK 8u171/8u172 (2018-04-17): 包括安全性更新和bug修复。
JDK 8u181 (2018-07-17): 安全性更新和一些小的改进。
...
JDK 8u292 (2021-04-20): 包含了安全性更新和性能改进。
从JDK 8u211开始,Oracle对JDK的许可政策进行了更改,对于商业使用,需要根据Oracle的新许可协议获得许可。
尝试使用keytool命令将CA文件加入到jdk的cacerts文件中
登录申请证书的服务器,进入到相应目录,可以看到ca.crt文件
查看根证书
openssl x509 -in ca.cer -text -noout
添加
keytool -import -trustcacerts -keystore /root/.jdks/jdk1.8.0_131/jre/lib/security/cacerts -storepass changeit -noprompt -alias isrgrootx1 -file /root/.jdks/jdk1.8.0_131/jre/lib/security/zcyun.cn.ca.crt
使用命令将
删除
keytool -delete -alias isrgrootx1 -keystore /root/.jdks/jdk1.8.0_131/jre/lib/security/cacerts -storepass changeit
进一步分析,为什么jdk中对于CA DST已经过期,依旧可以正常访问
jdk中的cacert文件中,包括很多根证书,这些根证书在https验证过程中,是通过哪个名称或者id对应起来的。
分析脚本
#!/bin/bash
export LANG=en_US.UTF-8
export LANGUAGE=en_US
# 设置Java的cacerts文件的路径
#CACERTS_PATH="/usr/java/jdk1.8.0_144/jre/lib/security/cacerts"
CACERTS_PATH="/root/.jdks/jdk1.8.0_131/jre/lib/security/cacerts"
#CACERTS_PATH="/root/.jdks/jdk1.8.0_131/jre/lib/security/cacerts.8u131"
#CACERTS_PATH="/root/.jdks/jdk1.8.0_131/jre/lib/security/cacerts.8u151"
# 默认的cacerts密码
STOREPASS="changeit"
#keytool -list -v -keystore $CACERTS_PATH -storepass $STOREPASS | awk '/Alias name:/ {alias=$3} /Valid from:/ {print "Certificate: " alias ", " $0; alias=""}'
keytool -list -v -keystore $CACERTS_PATH -storepass $STOREPASS | awk '/Alias name:/ {alias=$3} /Owner:/ {owner=$0} /Issuer:/ {issuer=$0} /Valid from:/ {print "Certificate: " alias "\n" owner "\n" issuer "\n" $0; alias=""; owner=""; issuer=""}'
在HTTPS验证过程中,cacerts文件中的根证书与服务器提供的证书链进行匹配,主要是通过证书的“主题”(Subject)和“颁发者”(Issuer)字段来实现的。
在一次https访问过程中,服务器返回的根证书为主要内容为:issuer=O=Digital Signature Trust Co., CN=DST Root CA X3,notBefore=Jan 20 19:14:03 2021 GMT notAfter=Sep 30 18:14:03 2024 GMT。而jdk中的同样的证书issuer具有不同的有效期,其有效期只是到2021年9月。为什么jdk依旧可以访问呢?
gpt给的回复:
在HTTPS通信过程中,当遇到服务器返回的根证书(如您提到的由Digital Signature Trust Co.颁发的DST Root CA X3)有效期与JDK中存储的同一根证书有效期不一致的情况时,连接依然可能成功。这种情况通常由以下几个因素中的一个或多个导致:
中间证书的使用:通常,在SSL/TLS握手过程中,服务器不仅返回其自身的证书,还可能返回一个或多个中间证书,这些中间证书形成了一个信任链,直至一个根证书。只要客户端信任链中的某个中间证书或根证书,且该证书未过期,客户端就能够建立信任关系。即使根证书在JDK的cacerts中过期,如果中间证书有效,并且由客户端信任的根证书颁发,连接仍然可以建立。
证书锚点(Trust Anchor)更新:某些情况下,证书颁发机构(CA)可能会更新根证书,延长其有效期。如果JDK或操作系统得到更新,以包含新的或更新的根证书,即使旧的根证书过期,新的信任锚(Trust Anchor)也可以用于验证证书链。在您的例子中,可能存在一个更新版本的DST Root CA X3证书,或者有一个接替的证书被JDK信任。
证书有效期宽容:在某些环境中,应用程序或库可能对证书的有效期有一定的宽容度,尤其是在证书链验证过程中。这不是最常见的情况,因为它可能引入安全风险。
交叉签名:根证书或中间证书可能通过交叉签名获得额外的信任路径。例如,一个根证书虽然在cacerts中过期,但它可能被另一个仍然有效且被信任的根证书交叉签名。这创建了一个备用的信任路径,允许证书链验证成功。
在您描述的情况下,最可能的解释是JDK信任的证书链中存在一个有效的中间证书,或者JDK已经包含了更新的根证书信任锚,这使得即使DST Root CA X3在cacerts中显示为已过期,HTTPS连接仍然可以成功建立。此外,某些SSL/TLS客户端实现可能在评估证书链时,优先考虑链中的有效证书,而不是直接依赖于根证书的有效性。
最后,对于购买的TrustAsia证书,jdk支持程度如何呢?
至少用到2029年
回头看
本次jdk访问异常的原因是由于,letsencrypt官方过去采用的根证书DST即将过期,而本身过期是没有问题的,jdk8u131依旧可以访问,但是问题出在官方更换了根证书isrg,而isrg证书直到8u141开始才加入。
jdk8u131及之前版本只有dst根证书,没有isrg根证书,而操蛋的letsencrypt近期更换了根证书,抛弃了dst。老版本只有dst没有isrg。即使jdk8u131版本已经很老,dst的日期可能过期,但是依旧可能可以正常使用。
所以,以后遇到pkix或者说证书链路径错误的问题,有以下几种解决办法
- 购买专业证书,兼容性更好,可用时间更长
- 通过keytool命令手动添加CA证书到cacerts文件
- 更新至jdk的更高版本
客户反馈jdk版本为aliyun出品
对方版本大概是dragonwell-8.4.7_jdk8u152-ga
,但是docker pull返回没有。
dragonwell-registry.cn-hangzhou.cr.aliyuncs.com/dragonwell/dragonwell:dragonwell-8.4.7_jdk8u152-ga
下载另一个版本测试
docker run -itd dragonwell-registry.cn-hangzhou.cr.aliyuncs.com/dragonwell/dragonwell:dragonwell-8.10.11_jdk8u322-ga
docker ps
docker exec -itd dragonwell-registry.cn-hangzhou.cr.aliyuncs.com/dragonwell/dragonwell:dragonwell-8.10.11_jdk8u322-ga
keytool -list -v -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit | awk '/Alias name:/ {alias=$3} /Owner:/ {owner=$0} /Issuer:/ {issuer=$0} /Valid from:/ {print "Certificate: " alias "\n" owner "\n" issuer "\n" $0; alias=""; owner=""; issuer=""}' | grep -i dst
keytool -list -v -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit | awk '/Alias name:/ {alias=$3} /Owner:/ {owner=$0} /Issuer:/ {issuer=$0} /Valid from:/ {print "Certificate: " alias "\n" owner "\n" issuer "\n" $0; alias=""; owner=""; issuer=""}' | grep -i isrg
返回结果正常
虽然openjdk从jdk8u131之后开始已经可以正常使用,但是这个jdk是ajdk,是aliyun出品,可能做了 稍许更改也不一定。
后来,对方兄弟使用命令查了下,果不其然,有dst 但是没有isrg。