前提介绍

在掉坑的过程中,面对老证书的CA为DST,而新证书的CA为ISRG,客户端可以正常调用老证书,而无法正确调用新证书。虽然购买了TrustAsia付费证书,但是对于到底为什么不行还可以进一步探索一下。

下图表示的是tls验证错误。切换华为CDN的时候忘记了打开TLS1开关。
2024-02-19T08:37:18.png

下图表示的证书路径错误,是个大问题。
2024-02-20T06:20:11.png

通过尝试,使用jdk7直接复现了该问题。这一点与客户端技术人员所说,客户端程序采用jdk8符合。

2024-02-19T08:37:43.png

复现

复现的主要过程是,使用jetbrain开发工具,本地安装jdk7和jdk8,借助copilot编写简单的java程序,实现对站点www.op123.ren(ISRG证书)和www.sddts.cn(DST证书)和bapi.zcyun.cn(aaa)的测试。
2024-02-19T08:39:00.png

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

2024-02-19T08:52:45.png
2024-02-19T08:50:42.png
2024-02-19T08:51:06.png

访问www.sddts.cn

2024-02-19T08:53:06.png
2024-02-19T08:54:39.png
2024-02-19T08:53:42.png

访问www.op123.ren

2024-02-19T08:56:03.png
2024-02-19T08:55:56.png

采用jdk7最后一个版本b147测试依旧不能正常访问。而且,jdk7默认情况下只能使用TLSv1,所以,测试过程中使用的Nginx或者bt面板提供的Nginx服务需要在配置文件中为配置参数ssl_protocols加上TLSv1。
2024-02-19T09:05:59.png

测试jdk8

从低版本测试

使用xxx版本访问三个测试站点都没有问题。但是,估计早起的jdk8版本中可能有不行的。
从jdk早起版本开始测试,直到测试到jdk8u131,出现了与问题一样的现象,CA为DST生效,但是CA ISRG失效。
2024-02-19T09:24:34.png

测试将jdk8u151的caccert文件拷贝到jdk8u131的对应位置

不可以直接拷贝整个security文件夹内容,会报错
2024-02-19T09:31:16.png

只是拷贝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。
2024-02-20T01:38:12.png

最终,测试了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文件

2024-02-20T05:18:00.png

查看根证书

openssl x509 -in ca.cer -text -noout

2024-02-20T05:23:33.png
添加

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

使用命令将

2024-02-20T05:21:28.png

2024-02-20T05:22:20.png

删除

keytool -delete -alias isrgrootx1 -keystore /root/.jdks/jdk1.8.0_131/jre/lib/security/cacerts -storepass changeit

2024-02-20T05:20:33.png
2024-02-20T05:21:06.png

进一步分析,为什么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依旧可以访问呢?
2024-02-20T06:07:39.png
2024-02-20T06:08:07.png

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年
2024-02-20T06:10:31.png
2024-02-20T06:10:48.png

回头看

本次jdk访问异常的原因是由于,letsencrypt官方过去采用的根证书DST即将过期,而本身过期是没有问题的,jdk8u131依旧可以访问,但是问题出在官方更换了根证书isrg,而isrg证书直到8u141开始才加入。

jdk8u131及之前版本只有dst根证书,没有isrg根证书,而操蛋的letsencrypt近期更换了根证书,抛弃了dst。老版本只有dst没有isrg。即使jdk8u131版本已经很老,dst的日期可能过期,但是依旧可能可以正常使用。

所以,以后遇到pkix或者说证书链路径错误的问题,有以下几种解决办法

  • 购买专业证书,兼容性更好,可用时间更长
  • 通过keytool命令手动添加CA证书到cacerts文件
  • 更新至jdk的更高版本

客户反馈jdk版本为aliyun出品

2024-02-20T07:45:44.png

对方版本大概是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

返回结果正常
2024-02-20T07:47:16.png

虽然openjdk从jdk8u131之后开始已经可以正常使用,但是这个jdk是ajdk,是aliyun出品,可能做了 稍许更改也不一定。

后来,对方兄弟使用命令查了下,果不其然,有dst 但是没有isrg。

最后修改:2024 年 05 月 11 日
如果觉得我的文章对你有用,请随意赞赏