读证书看起来是一行代码,但格式容忍度、证书链文件的流读取、CertPath 编码与验证顺序都有不少细节。这篇用真实生成的根 → 中间 → 叶子证书链逐项验证。
[formats]
CertificateFactory.getInstance("X.509"):provider = SUN,type = X.509
PEM(-----BEGIN CERTIFICATE-----)→ CN=www.example.test;颁发者 CN=Test Intermediate CA;签名算法 SHA384withECDSA;公钥 EC;SAN = [[2, www.example.test], [2, example.test]];BasicConstraints = -1
DER(c.getEncoded(),长度 471)→ 1 张:[CN=www.example.test]
PEM 前面加一段无关文字:1 张:[CN=www.example.test]
PEM 用 CRLF 换行:1 张:[CN=www.example.test]
PEM 每行缩进两个空格:CertificateException: No certificate data found
BEGIN X509 CERTIFICATE(旧写法):1 张:[CN=www.example.test]
BEGIN TRUSTED CERTIFICATE(OpenSSL 的信任格式):1 张:[CN=www.example.test]
空输入:0 张:[]
只有头尾没有内容:CertificateException: Unable to initialize, java.io.IOException: Too short ← IOException: Too short
PEM 里 base64 被截断(去掉最后 30 个字符再补尾):CertificateException: Unable to initialize, java.io.EOFException: not enough content ← EOFException: not enough content
第二行(base64 正文)中间混入非法字符 '!':CertificateException: java.io.IOException: java.lang.IllegalArgumentException: Illegal base64 character 21 ← IOException: java.lang.IllegalArgumentException: Illegal base64 character 21
头尾标签换成别的名字(BEGIN FOO / END FOO):1 张:[CN=www.example.test];证书文件里的头是 “-----BEGIN CERTIFICATE-----”;头尾不一致(BEGIN CERTIFICATE / END FOO):CertificateException: java.io.IOException: Header and footer do not match: -----BEGIN CERTIFICATE----- -----END FOO----- ← IOException: Header and footer do not match: -----BEGIN CERTIFICATE-----
缺少 END 行:CertificateException: java.io.IOException: Incomplete data ← IOException: Incomplete data
DER 后面带 20 个字节的垃圾:1 张:[CN=www.example.test]
DER 被截掉最后 5 个字节:CertificateException: java.io.IOException: Incomplete BER/DER data ← IOException: Incomplete BER/DER data
随机字节:CertificateException: No certificate data found [bundle]
PEM 链文件(leaf + inter + root 依次拼接):generateCertificates → 3 张:[CN=www.example.test, CN=Test Intermediate CA, CN=Test Root CA]
generateCertificate(单数)读同一个文件 → CN=www.example.test(只取第一张)
对同一个 ByteArrayInputStream 连续调用 4 次 generateCertificate:[CN=www.example.test, CN=Test Intermediate CA, CN=Test Root CA, CertificateException: Could not parse certificate: java.io.IOException: Empty input ← IOException: Empty input]
对同一个 FileInputStream(不支持 mark)连续调用 4 次:[CN=www.example.test, CN=Test Intermediate CA, CN=Test Root CA, CertificateException: Could not parse certificate: java.io.IOException: Empty input ← IOException: Empty input]
FileInputStream.markSupported() = false;generateCertificates 读完后 available() = 0
两张 DER 直接拼接:2 张:[CN=www.example.test, CN=Test Intermediate CA]
PEM 之间夹着注释行(# comment):2 张:[CN=www.example.test, CN=Test Intermediate CA]
PEM 之间夹着 openssl -text 风格的 Certificate: 文本块:2 张:[CN=www.example.test, CN=Test Intermediate CA]
读 PEM 返回的集合类型:java.util.ArrayList;generateCertificates 对没有证书的输入 → CertificateException: No certificate data found [path]
generateCertPath([leaf, inter]) 长度 2;类型 X.509;编码格式 [PkiPath, PKCS7]
PkiPath 编码长度 913,PKCS7 编码长度 956,默认编码长度 913
从 PkiPath 字节读回:[CN=www.example.test, CN=Test Intermediate CA];equals 原路径? true
从 PKCS7 字节读回:[CN=Test Intermediate CA, CN=www.example.test]
验证 [leaf, inter](根作为信任锚):通过;信任锚 CN=Test Root CA,O=Demo;目标公钥 EC
顺序反了 [inter, leaf]:CertPathValidatorException: Path does not chain with any of the trust anchors
路径里带上根 [leaf, inter, root]:通过
缺中间证书 [leaf]:CertPathValidatorException: Path does not chain with any of the trust anchors
把中间证书当作信任锚验证 [leaf]:通过
过期证书 checkValidity:CertificateExpiredException: NotAfter: Fri Jan 31 00:00:00 CST 2020;notBefore = 2019-12-31T16:00:00Z;notAfter = 2020-01-30T16:00:00Z
验证 [expired, inter]:CertPathValidatorException: validity check failed ← CertificateExpiredException: NotAfter: Fri Jan 31 00:00:00 CST 2020
验证 [leaf, inter],验证时间设为叶子 notAfter 之后一天(叶子有效期 90 天,notAfter = 2027-01-01T19:15:47Z):CertPathValidatorException: validity check failed ← CertificateExpiredException: NotAfter: Sat Jan 02 03:15:47 CST 2027
验证时间设为叶子 notBefore 之前一天:CertPathValidatorException: validity check failed ← CertificateNotYetValidException: NotBefore: Sun Oct 04 03:15:46 CST 2026
验证时间在有效期内(notAfter 之前一天):通过
自签名根单独验证 [root] 以自己为锚:通过 [types]
CertificateFactory.getInstance("X.509" ) → X.509 / provider SUN
CertificateFactory.getInstance("X509" ) → X509 / provider SUN
CertificateFactory.getInstance("x.509" ) → x.509 / provider SUN
CertificateFactory.getInstance("PKIX" ) → CertificateException: PKIX not found ← NoSuchAlgorithmException: PKIX CertificateFactory not available
CertificateFactory.getInstance("X.509-CRL") → CertificateException: X.509-CRL not found ← NoSuchAlgorithmException: X.509-CRL CertificateFactory not available
支持的 CertPath 编码:[PkiPath, PKCS7]
服务:SUN → X.509
证书对象的类:sun.security.x509.X509CertImpl;toString 前 3 行:[[, [, Version: V3]
同一份 PEM 解析两次的两个对象 equals? true;hashCode 相同? true;是同一个对象? true
getEncoded() 与 PEM 里的 base64 解码结果一致? true;长度 471
换行风格不同(CRLF)但内容相同:equals true,同一对象 true