技术

理解PKI(六):一张证书如何被信任——路径构建、吊销状态与用途校验

从证书使用方的视角拆解一条完整验证链:如何构建到本地信任锚的路径,检查约束、时间、吊销状态、名称与用途,并区分技术信任和业务授权。

发布
阅读约 15 分钟
理解PKI(六):一张证书如何被信任——路径构建、吊销状态与用途校验
章节索引页面导航1 / 29
一、先把“证书有效”拆成九个问题1 / 29

一家 CA 给服务器签发了一张证书。我们能正常解析它,证书里的签名也能被颁发者公钥验证。现在可以放心建立连接了吗?

还不行。

签名正确只说明证书内容没有在签发后被修改,而且某个私钥曾经为它签名。客户端仍要回答:这个颁发者是否能一路连接到自己认可的信任锚?链上的 CA 是否有权签发这类证书?证书是否过期或已作废?访问的域名、预期用途和本地策略是否匹配?即使这些技术检查全部通过,当前主体又是否拥有这次业务操作的权限?

因此,“证书有效”不是一个判断,而是一组不能互相替代的判断:

能解析,不等于签名正确;签名正确,不等于路径受信;路径受信,不等于状态新鲜;技术验证通过,也不等于业务已经授权。

第三篇已经介绍过 X.509 v3 的字段和扩展。这一篇不再逐项解释字段,而是站到证书使用方一侧,把它们放回真实的验证顺序。


一、先把“证书有效”拆成九个问题

面对一张终端实体证书,验证方至少要完成下面几层判断:

层次要回答的问题典型依据
1. 解析输入是不是结构合法、编码可接受的证书ASN.1、DER、X.509 结构
2. 单证书签名颁发者公钥能否验证这张证书的签名signatureAlgorithm、签名值、颁发者公钥
3. 路径构建能否找到一条通往本地信任锚的候选链issuer/subject、AKI/SKI、候选中间证书、信任库
4. 路径验证链上每一级是否有权签发下一级,并满足全部约束Basic Constraints、KU、路径长度、名称和策略约束
5. 时间验证时刻是否落在每张证书的有效期内notBeforenotAfter、本地时钟
6. 状态证书是否被作废,状态信息是否可信且足够新鲜CRL、OCSP、状态检查策略
7. 身份证书声明的服务身份是否与当前连接目标一致SAN、RFC 9525 名称匹配规则
8. 用途与政策这把公钥是否允许用于当前操作KU、EKU、Certificate Policies、算法与平台政策
9. 业务授权这个已验证身份现在能否执行当前动作账号、租户、角色、委托、风控和业务规则

这些步骤并不保证所有软件都按同一顺序实现。路径构建可能同时探索多条候选链,状态检查也可能受网络、缓存和平台策略影响。但把判断拆开非常重要:只有这样,错误日志才能说明失败发生在哪一层,系统也不会用“验签成功”掩盖后续检查缺失。

还有一项容易遗漏:收到证书并不能证明对方现在持有对应私钥。私钥控制通常由 TLS 握手签名、客户端认证、挑战应答或业务签名来证明。证书负责绑定身份与公钥,协议负责证明当前参与方能够使用私钥。


二、路径构建:先找出“可能可信”的链

假设服务器发送了下面两张证书:

www.example.test(终端证书)
        │ 由 Intermediate CA 签发

Intermediate CA(中间 CA 证书)
text

服务器通常不需要发送根证书。客户端会从自己的信任库中寻找能够接上这条链的信任锚:

本次连接提供                         客户端本地配置

终端证书 ──► 中间 CA ─────────────► Root CA(信任锚)
   不受信任输入     候选颁发者          预先认可的起点
text

这里有三个很容易混淆的事实。

1. 中间证书只是候选材料

服务器发来的中间证书可以帮助客户端构建路径,但它不会因为出现在握手消息中就自动获得信任。验证方仍要检查它的签名、有效期、CA 约束、用途和上级颁发关系。

issuer/subject 名称以及 AKI/SKI 可以帮助筛选颁发者,却不是最终证据。名称相同不代表拥有正确私钥,标识能对应也不代表具备签发权限;真正的路径验证还必须验证签名和约束。

2. 自签名不是信任来源

根证书常常是自签名证书,但“自签名”只描述它用自己的私钥签了自己,不能证明客户端应该相信它。

信任锚之所以可信,是因为它通过操作系统、浏览器、企业策略或应用配置被预先纳入本地信任,而不是因为它能验证自己的签名。

信任锚甚至可以由本地配置的名称、公钥、算法和约束组成,不必完全等同于线上传输的一张根证书。不同平台拥有不同的根证书计划和附加政策,因此,同一张终端证书在不同设备上可能构建出不同路径,甚至得到不同结果。

3. 一张证书可能不止一条路径

交叉签发、多个同名中间 CA、不同根计划和历史兼容链都会产生多条候选路径。路径构建要从可用中间证书、本地缓存、AIA 获取结果和信任库中寻找组合;路径验证则逐条判断候选链是否满足规则。

这两个过程要分开理解:构建成功只代表找到了一条候选链,验证成功才代表这条链在当前输入和策略下可以接受。


三、路径验证:链上的每一级都有约束

找到候选链以后,验证方要从信任锚的约束出发,逐级验证到目标证书。核心不是“把每个签名验一遍”,而是确认每个颁发者既有正确的密钥,也有权签发下一级证书。

CA 身份与签发权限

中间 CA 通常要同时满足:

如果一张普通服务器证书恰好能验证另一张证书的签名,它也不会因此变成 CA。密码运算正确与证书授权范围是两件事。

约束会沿路径收紧

验证终端证书时,不能只看它自己的字段。上级 CA 可以通过 Name Constraints 限制下级能签发的命名空间,通过 Certificate Policies 和策略映射限制可接受政策;根计划还可能在信任库元数据中附加约束。

Key Usage 与 Extended Key Usage 也不是“满足一个即可”。前者限制密钥能执行哪类密码操作,后者限制证书能参与哪些应用场景;当两者都存在时,应按交集理解。一个证书拥有数字签名能力,不代表它自然适合 TLS 服务器认证、代码签名或客户端认证。

此外,RFC 5280 给出的是通用 PKIX 路径验证框架。浏览器、操作系统和应用还会叠加算法政策、根计划政策及场景要求,例如拒绝某些签名算法、密钥参数或不符合公开 TLS 规则的证书。


四、时间、名称和用途是三道不同的门

路径受信以后,验证方仍要确认“在这个时间、面对这个对象、为了这个用途”是否可以接受证书。

有效期:证书声明允许在哪段时间使用

验证时刻通常要落在链上相关证书的 notBeforenotAfter 之间。时钟错误、时区处理错误和边界值处理差异都会造成验证异常。

有效期检查不能代替吊销检查。证书尚未过期,仍可能因为私钥泄露、主体信息变化或 CA 事件而提前作废;证书已经过期,也不等于历史签名在签署时必然无效,历史证据还需要结合可信时间和当时状态分析。

名称匹配:链可信,但它是不是我要找的服务

TLS 服务身份匹配是路径验证之外的应用层判断。现行 RFC 9525 要求 DNS 服务身份使用 SAN 中的 dNSName,不再回退检查 Common Name。DNS 名称与 IP 地址也必须按对应标识类型检查,不能把字符串看起来相同当成匹配。

通配符的范围同样有限。按照 RFC 9525,它只能作为最左侧标签的完整内容,例如 *.example.com;它不匹配裸域名 example.com,也不应写成 f*.example.com 之类的部分通配。

这解释了一个常见现象:证书链完全可信,浏览器仍然报告名称错误。客户端相信 CA 的签发关系,却发现证书声明的服务不是当前要访问的服务。

用途匹配:这把密钥能不能做当前操作

TLS 服务器验证会检查与服务器认证相符的 EKU 和 KU;客户端认证、代码签名、邮件保护则有各自用途。Certificate Policies 还可表达证书依据的签发政策,但应用是否真正解释某个 policy OID,取决于协议、平台和本地策略。

因此,看到 serverAuth 不等于证书已经通过全部 TLS 验证;看不到某个字段也不能脱离所用协议和验证库直接下结论。用途校验必须放在完整路径和目标场景中进行。


五、吊销检查:查询到状态还不算结束

证书的有效期可能长达数月,但私钥泄露需要在几分钟或几小时内止损。CRL 和 OCSP 都在解决“尚未到期的证书是否已经被 CA 作废”,却有不同的数据和故障模型。

机制客户端获得什么主要优势验证时不能漏掉
CRL一段范围内的已吊销证书列表可缓存、可离线批量检查签名、颁发者与范围、更新时间、目标序列号、增量 CRL 的基础列表
OCSP针对证书标识的状态响应单次响应较小,可由服务端装订目标证书标识、响应签名、响应者授权、状态和新鲜度

CRL 不是下载后搜索序列号那么简单

验证方要确认 CRL 的签名有效、签发者有权发布该 CRL、适用范围覆盖目标证书,并检查 thisUpdatenextUpdate 等时间。若使用 delta CRL,还要找到匹配的基础 CRL 并正确合并。

一份由错误 CA 签发、已经过期或只覆盖其他分区的 CRL,即使没有目标序列号,也不能推出“证书未作废”。

OCSP 的 good 不是“证书全部有效”

OCSP 响应中的 good 更准确的含义是:对于请求中的证书序列号,响应者没有发现一张当前处于有效期且已作废的证书。它不保证该序列号对应的证书曾经被签发,也不保证响应生成时间落在证书有效期内。客户端仍要检查:

  1. 响应对应的是否正是当前目标证书;
  2. 响应签名是否正确,签名者是否被授权响应;
  3. thisUpdateproducedAt、本地最大缓存年龄,以及存在时的 nextUpdate,是否满足新鲜度策略;
  4. 响应是否在可接受时间窗口内,是否需要 nonce 或其他防重放措施;
  5. 路径、名称、用途和本地政策是否另行通过。

RFC 6960 将 nextUpdate 定义为可选字段;缺失时表示更新后的吊销信息随时可用,客户端仍要按本地策略限制响应年龄。RFC 9654 更新了 nonce 扩展;2026 年发布的 RFC 9919 则取代 RFC 5019,为高容量环境给出轻量 Profile。不同部署是否使用 nonce、是否允许缓存以及响应有效期多长,必须按所用 Profile 和应用策略判断,不能从“这是 OCSP”推出统一答案。

状态服务不可用时怎么办

CRL 下载失败、OCSP 超时或服务端没有返回 stapled response 时,验证方必须执行明确的失败策略:

没有一种策略适用于所有系统。真正危险的是实现中默认忽略错误,日志却仍写成“证书有效”。是否强制 OCSP stapling、浏览器是否在线查询以及私有 PKI 如何处理离线环境,也都属于目标平台和业务场景的政策选择。


六、根计划和本地策略决定“谁值得信任”

公开 TLS 证书不仅受 RFC 约束,还受 CA/Browser Forum Baseline Requirements 和浏览器根证书计划管理。CA 进入某个根库,不等于自动进入所有平台;进入根库也不代表它签发的所有用途都会被接受。

截至 2026 年 8 月,Chrome、Mozilla 与 Apple 均维护各自的根证书计划或政策。Chrome Root Program 可以通过根库元数据附加名称约束,并明确其公开 TLS 范围;Mozilla 根库通过 trust bits 区分网站与 S/MIME 等信任用途;企业内部 CA 则通常由设备管理、操作系统策略或应用信任库分发。

由此可以得到一个重要结论:

证书里没有一个 trusted: true 字段。信任是证书声明、候选路径、本地信任锚、验证时刻、应用目的和平台政策共同计算出来的结果。

在排查“我的电脑能访问,另一台不能访问”时,除了检查服务器是否漏发中间证书,还要比较两端的信任库、根计划版本、算法政策、系统时间、状态查询能力和名称匹配输入。


七、用 OpenSSL 把验证步骤显式写出来

OpenSSL 可以帮助复现实验,但命令行参数本身也是验证策略的一部分。下面以 OpenSSL 3.6 文档为准。

1. 构建并验证 TLS 服务器证书路径

openssl verify -show_chain \
  -CAfile trusted-root.pem \
  -untrusted intermediates.pem \
  -purpose sslserver \
  -verify_hostname api.example.test \
  leaf.pem
bash

这里的两个证书输入承担不同角色:

-purpose sslserver 检查 TLS 服务器用途,-verify_hostname 按 OpenSSL 默认兼容规则执行主机名检查,-show_chain 输出最终采用的链。仅执行 openssl x509 -text 或对终端证书做一次签名验算,都达不到这个验证范围。

这里还要注意一个实现差异:OpenSSL 3.6 的默认主机名检查在没有对应 SAN 时仍可能回退到 subject Common Name,也允许 w*.example.com 这样的部分标签通配符。因此,上面的命令适合排查 OpenSSL 的默认验证结果,却不能证明应用已经严格落实 RFC 9525。

生产 TLS 客户端使用 OpenSSL API 时,需要显式禁用 Common Name 回退和部分通配符,再设置目标主机名:

SSL_set_hostflags(
    ssl,
    X509_CHECK_FLAG_NEVER_CHECK_SUBJECT |
        X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
SSL_set1_host(ssl, "api.example.test");
c

这两个 flag 分别要求名称检查永不读取 subject Common Name,以及只接受占据完整标签的通配符。应用仍要检查 API 返回值和最终证书验证结果。

2. 使用本地 CRL 检查整条链

openssl verify -show_chain \
  -CAfile trusted-root.pem \
  -untrusted intermediates.pem \
  -CRLfile chain-crls.pem \
  -crl_check_all \
  leaf.pem
bash

-crl_check_all 要求检查链上的相关证书,而不只是终端证书。chain-crls.pem 也必须包含由正确签发者发布、范围和时间均适用的 CRL;把任意 CRL 文件塞进命令不会自动得到可信状态。

3. 观察真实 TLS 握手与 OCSP stapling

openssl s_client \
  -connect api.example.test:443 \
  -servername api.example.test \
  -verifyCAfile trusted-root.pem \
  -verify_hostname api.example.test \
  -verify_return_error \
  -status
bash

-servername 设置 TLS SNI,-verify_hostname 检查证书身份,两者不是同一个选项。-status 请求服务端返回 stapled OCSP response 并在存在时显示;没有响应不必然代表证书无效,是否必须装订由应用政策决定。

还有一个排障陷阱:s_client 是诊断工具,默认即使证书验证发生错误也可能继续握手。加入 -verify_return_error 才会在验证错误时中止,脚本也不应只凭“握手输出很多内容”判断验证成功。

这些命令适合隔离输入和复现问题,但不能自动模拟浏览器的全部根计划、公开 TLS 政策和状态策略。生产应用应使用自身验证库的正式接口,并测试每一种失败路径。


八、技术验证通过之后,业务才开始授权

假设一个双向 TLS 接口已经完成客户端证书验证,并证明请求方控制对应私钥。系统现在可以从证书中取得一个已验证身份,但仍不能仅凭 subject 或序列号放行业务请求。

应用还要把证书身份映射到当前业务主体,并检查:

证书中的组织名、OU 或自定义扩展可以成为映射输入,却不应直接充当无限期业务权限。CA 负责按照证书政策声明身份和用途,业务系统才掌握账号状态、组织关系和实时授权。

这条边界也能解释两个常见误区:

误区实际上只证明了什么仍缺少什么
“证书链通过,所以可以下单”公钥与证书身份在当前验证策略下受信当前账号、角色和交易条件授权
“请求里带了证书,所以是本人操作”请求携带了公开证书对应私钥控制证明,以及用户对本次操作的意愿

PKI 把一个可验证身份交给业务系统;业务系统必须自己决定这个身份此刻可以做什么。


九、一份可落地的验证清单

路径与信任

时间、状态与身份

协议与业务


十、总结

一张证书被信任,不是因为它长得像证书,也不是因为某次签名运算返回了成功。验证方要先从不受信任输入中构建候选路径,再把路径连接到本地信任锚,逐级执行签名和约束验证,随后检查时间、吊销状态、服务名称、密钥用途与平台政策。

即使这些检查全部通过,结果也只是“在当前策略和时刻,这个证书身份及其公钥可以被接受”。私钥控制仍要由实际协议证明,业务权限仍要由应用根据实时状态决定。

证书没有携带绝对信任。信任是验证方在具体时间、用途、平台政策和业务上下文中计算出来的。

下一篇将继续进入应用层,看看 TLS 如何在握手中使用证书和私钥,数字信封怎样组合对称与非对称密码,以及时间戳如何为签名补上可验证的时间证据。


参考资料

现行资料核验于 2026-08-06。RFC 5280 的更新与勘误、公开 TLS 政策、根计划和软件行为仍会变化;生产系统应按目标平台与发布时间重新核验。


系列文章

笔记整理自《PKI、CA 与数字证书技术大全》第 2、9、16、19~20 章,并按现行 RFC、CA/Browser Forum、平台根计划与 OpenSSL 官方文档校正和补充。


正在加载留言…


上一篇
理解PKI(七):证书如何在应用中工作——TLS、数字信封与可信时间
下一篇
让 Obsidian 工作任务在 Mac 上反复提醒:用 EventKit 双向同步提醒事项

搜索文章...

⌘K / Ctrl K

使用上下方向键选择结果,按 Enter 打开,按 Esc 关闭

搜索结果