HTTPS证书域名没错、链也补齐了,OpenSSL verify却报unhandled critical extension。先别继续下载根证书:这次要查的是某层证书里的关键扩展,验证程序能不能理解并执行它。本文从error 34和depth入手,定位扩展、核对签发模板,再保持正常验证策略验收。
一、critical不是"这张证书很重要"
证书扩展带有OID、critical标志和编码后的值。RFC 5280第4.2节要求:遇到不认识的关键扩展,或其中有无法处理的信息,使用证书的系统必须拒绝。非关键扩展在不认识时可以忽略;认识时仍须处理。它不是"所有扩展都能随便忽略"的许可。
critical是"看懂规则才能继续",不是给字段加粗。常见原因是签发模板加入了验证器不支持的关键扩展。

二、先用depth找到证书,再看完整扩展
保存完整错误原文、subject和depth。depth=0指待验证叶子,depth=1指直接签发者,之后沿实际构建路径向上。不要只看网站证书:中间CA携带不支持的关键扩展,也可能让链失败。本机实验中的error 34是X.509错误号,Shell退出码是2,两者不是一回事。
以下约定leaf.pem、intermediate.pem、root.pem分别是叶子、直接签发CA和已确认可信根,每个观察文件一张证书。多级链逐张检查;x509读fullchain首张成功,不等于所有成员都检查完了。
bash
openssl version -a
for cert in leaf.pem intermediate.pem root.pem; do
openssl x509 -in "$cert" -noout -subject -issuer -text || exit 1
done
在X509v3 extensions区域记录critical、OID和值。无友好名称时保留OID交给CA维护方。-text能显示内容,不代表verify能处理扩展语义。
三、固定验证输入,别让错误互相遮挡
bash
openssl verify -no-CApath -CAfile root.pem \
-untrusted intermediate.pem -purpose sslserver \
-verify_hostname api.example.test -show_chain leaf.pem
-CAfile放已核实的可信根,-untrusted只提供候选中间链;-no-CApath排除默认目录参与。-purpose与-verify_hostname分别检查服务端用途和名称,api.example.test为保留测试域名,实际验收需替换为业务名称。
缺链、名称不匹配不是关键扩展问题;修复前项后才出现34,可能只是验证继续向前走了。
四、隔离复现:同一扩展只改critical
本次使用OpenSSL 1.1.1k FIPS,在临时目录创建实验根、CA和叶子,RSA密钥为2048位、签名为SHA-256,不修改系统信任库、不启动生产服务。实验用官方配置文档同类示例OID 1.2.3.4装入短字符串;它仅用于本地演示,不能当成生产OID分配。
ini
# 仅供隔离实验的扩展片段,不是生产签发模板
[lab_leaf]
basicConstraints = critical,CA:FALSE
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = DNS:api.example.test
1.2.3.4 = critical,ASN1:UTF8String:lab-only
这是扩展节,不是Shell命令或完整CA配置。实验脚手架用x509 -req配合-extfile与-extensions lab_leaf签发同一CSR,对比关键、非关键及无此扩展三种材料。
结果很直观:带未知关键扩展的叶子在depth=0报34;同值改成非关键扩展后通过,移除实验扩展也通过。再把未知关键扩展放到中间CA,叶子保持正常,错误出现在depth=1。签名和名称正常,不会替验证器补上扩展处理能力。
| 材料或错误 | 本次观察 | 排查重点 |
|---|---|---|
| 叶子未知关键扩展 | 34,depth 0 | 叶子签发模板与验证实现 |
| 中间CA未知关键扩展 | 34,depth 1 | CA扩展及下级链迁移 |
| 未知非关键实验扩展 | 本机离线通过 | 不等于业务语义已执行 |
| 缺中间链或错误名称 | 分别为20或62 | 不按扩展问题处理 |
五、为什么不把-ignore_critical当修复
OpenSSL提供-ignore_critical诊断选项。隔离对照中,它让带实验关键扩展的链通过;但这只是改变了接受条件,没有实现那条扩展规则。把报警器拔掉,屋子不会因此更安全。本文不把该选项放进正式验收命令,也不建议固化到生产脚本。
同样,不要把所有critical字段一律删除。basicConstraints、keyUsage等标准扩展有各自规范要求;一些扩展必须标为关键。即便改成非关键后能验证,也只说明当前验证器忽略了它,不能证明业务约束仍有效。对自定义约束尤其要谨慎。
六、修复要回到签发策略或验证实现
若扩展是模板误加,与CA维护方确认OID用途、预期客户端和critical要求,修正模板后重签;不要直接编辑证书字节,扩展在签名覆盖范围内,改动会破坏签名。
若它确实承载必要约束,应使用真正支持并执行该扩展的客户端或验证实现,并做正反例验收。仅注册一个OID名称或升级工具,不能自动证明已经支持其语义;升级前要看目标版本与应用文档。OpenSSL CLI和业务服务可能使用不同库,分别记录版本。
CA层故障可能需要新CA链、重签下级证书或迁移信任。编码无效或重复扩展不应全归为34,按实际错误继续查。
七、用正常策略重新验收
bash
if openssl verify -no-CApath -CAfile root.pem \
-untrusted intermediate.pem -purpose sslserver \
-verify_hostname api.example.test leaf.pem >verify.log 2>&1; then
printf 'offline verification passed\n'
else
rc=$?
printf 'verification failed: rc=%s; see verify.log\n' "$rc" >&2
exit "$rc"
fi
包装器不忽略关键扩展,并保留用途、名称和信任验证。日志路径不可写也会走失败分支,应与证书错误区分;本次已重放正文Shell块,验证异常证书和日志失败都不能输出通过。INI片段另由实验签发脚手架执行,不冒充Shell语法检查。
离线通过后,仍需取得真实TLS终点发送链,用目标客户端验证。本次只做本机离线实验,不宣称生产TLS兼容测试通过。
八、交付清单与参考
- 错误全文、depth、各层证书指纹及CLI和业务库版本。
- 失败层全部关键扩展的OID、值和签发模板来源。
- 扩展规范、客户端支持证据,以及修正前后的负例和正例。
- 不带忽略选项的离线结果、实际终点与目标客户端结果,分开记录。
参考资料与证书运维入口:OpenSSL verify、OpenSSL扩展配置、RFC 5280第4.2节、CertbotX。扩展是否可接受仍取决于规范与验证实现,运维入口不替代客户端校验。