正确验证签名
Spec: RFC 5280, §6 RFC 5280 §6 Spec: RFC 6960 RFC 6960 Spec: RFC 5652 RFC 5652 Evidence: Test-backed
「签名有效」通常只表示检查了一件事:密码学计算是否匹配。一次正确的验证至少要检查五件彼此独立的事;其中任何一件出错,都可能让那个绿色对勾失去意义。本页列出完整的检查项,并说明为什么不完整的答案会带来风险。
为什么这很重要
标题为“为什么这很重要”的章节在这个主题中,单一布尔值是最危险的输出。它会让读者误把「有效」当成「值得信任」;然而「有效」可能只代表那些字节未经篡改,而签名所用的密钥,来自一张三年前就过期、上个月遭到吊销、且链接不到任何你认可的机构的证书。上述每一项都是独立检查。返回单一布尔值的软件,已经默默替你决定了哪些检查才重要,而且这个决定是替你做的。在受监管或具有合同效力的场合,如果工具只验证了成本最低的那项属性,「工具说它有效」并不能作为辩护。
简短版本
标题为“简短版本”的章节一次完整的验证会回答五个各自独立的问题。它们彼此独立——通过其中一项,并不能说明其他项的任何情况:
- 完整性——对已签名的字节计算哈希后,是否仍与签名所涵盖的值相符?(重新计算字节范围摘要,再进行比对。)
- 真实性——密码学签名是否能使用签名证书中的公钥,针对已签名属性完成验证?
- 证书路径——该证书是否能链接到你所选择的信任锚点,且每个环节都有效?
- 时间——在相关时间点,证书是否处于其有效期内,而那个时间是否属于受信任的时间、而非自行声称的时间?
- 吊销——在当时,证书是否未遭吊销,且相关证据(OCSP/CRL)是否是你实际能够获取的,或已经内嵌在文档中?
一个未执行全部五项检查的「有效」,是一个看起来像完整答案的不完整答案。
NextPDF 的处理方式
标题为“NextPDF 的处理方式”的章节NextPDF 的立场是:每一个问题都各自独立,且每一个都必须得到明确回答。绝不把一个问题压缩成单一的乐观标记,也绝不因为某项检查不方便而默默跳过。这一点由测试强制保证。这正是本页被标示为由测试支撑(test-backed)、而非由标准支撑(standard-backed)的原因:这项行为由测试套件固定,而不仅仅是从某条条款推导而来。
完整性与真实性通过端到端测试覆盖。一张已知的证书会对一个真实的已签名属性结构进行签名,测试套件再使用匹配的公钥,在多个时间向量上验证该签名。因此,任何破坏这种规范结构的变更都会让测试失败。证书路径验证由一组测试固定:这些测试会刻意篡改一个签名字节,并断言结果为非有效,且附带一个结构化原因——它不是被丢弃的异常,而是一条明确记录的失败。时间戳令牌的验证被拆解成数个离散步骤——解码、签名者信息、已签名属性、消息摘要、证书绑定、密钥用途、签名、产生时间(produced-at)——而每一个步骤都有独立测试,因此「时间戳通过验证」意味着每一个步骤都通过了验证。吊销的软性失败(无法连接的响应端)在代码和测试中都与明确的「已吊销」区分开。这两者绝不会被混为同一个答案。
- Integrity Recompute the byte-range digest and compare it to the value the signature covers.
- Authenticity Verify the cryptographic signature against the certificate’s public key, over the signed attributes — not the raw content.
- Certificate path Build and validate the chain to a trust anchor you chose; every link’s signature, validity, and constraints must hold.
- Time Confirm the certificate was valid at the relevant instant, and that the instant is trusted time, not the signer’s clock.
- Revocation Confirm the certificate was not revoked at that time, using obtainable or embedded OCSP/CRL evidence.
证据说明了什么
标题为“证据说明了什么”的章节Evidence: Test-backed 这项行为以测试作为依据,而这些测试实现了各项标准要求的内容。
完整性对应 Spec: ISO 32000-2, §12.8.1 ISO 32000-2 §12.8.1 :摘要会在字节范围上重新计算,并与存储的值比对,任何差异都意味着签名无效。针对已签名属性的真实性,由一项集成测试涵盖:它会对一组真实的已签名属性进行签名,并使用匹配的公钥跨多个时间向量加以验证。证书路径问题对应
Spec: RFC 5280, §6.1 RFC 5280 §6.1 :有效的路径始于一个信任锚点,而 Spec: RFC 5280, §6.2 RFC 5280 §6.2 指出,该算法定义了路径被视为有效所需的最低条件——一项路径验证器单元测试会断言:一个被篡改的签名会产生 valid = false,并附带一个明确的原因,绝不会默默接受。
吊销的检查顺序对应 Spec: RFC 6960, §3.2 RFC 6960 §3.2 :在客户端将一个已签名的吊销响应视为有效之前,它「应当」(SHALL)先确认该响应自身的签名为有效,且签名者当前已获授权——而 Spec: RFC 6960, §4.2.2.2 RFC 6960 §4.2.2.2 将该授权定义为由相关 CA 直接签发的 id-kp-OCSPSigning 委派授权。因此,一个本身尚未针对已获授权且可验证的签名者完成验证的吊销答案,是毫无意义的。证书绑定检查对应 Spec: RFC 5035, §5.4.2 RFC 5035 §5.4.2 :如果已签名的 signing-certificate-v2 属性中的证书哈希,与用来验证签名的证书不相符,那么该签名必须被视为无效。这阻断了替换漏洞——也就是签名针对一张由攻击者选择的证书通过验证的情况。时间戳令牌本身会以 Spec: RFC 5652 RFC 5652 的方式,作为一个 CMS 对象逐步验证,每一个步骤都有独立测试。
实践示例
标题为“实践示例”的章节真正有启发性的,不是某一次 API 调用,而是在你依据某个结果采取行动之前,你必须能够回答的那些问题。请把它当作审查会用来核查你的清单。
<?php
declare(strict_types=1);
// A correct validation produces a structured outcome, not one boolean.// Before you trust a signature, you must be able to answer ALL of these://// integrity : Does the byte-range digest still match? (tamper check)// authenticity: Does the signature verify over the SIGNED ATTRIBUTES,// not just the content?// path : Does the certificate chain to a trust anchor YOU chose,// with every link valid at the relevant time?// time : Is the relevant time TRUSTED (a timestamp), or merely the// signer's self-asserted clock?// revocation : Was the certificate not revoked at that time, by evidence// you obtained or that the document embedded?//// "valid: true" without an answer to every line above is an incomplete// result. A path-validation outcome carries a `valid` flag AND a structured// `reasons` list precisely so a failure says WHY — never a bare false.如果其中任何一行的答案是「我不知道」,那么诚实的状态就不是「有效」,而是「尚未确定」——把这两者当成同一回事,正是本页要防止的错误。
常见误解
标题为“常见误解”的章节陷阱在于把「密码学上有效」等同于「值得信任」。完整性与真实性合在一起,只能证明这些字节确实由持有这把密钥的人签名。至于这把密钥的证书是否受信任、是否仍在有效期内、或是否未遭吊销,它们都只字未提。使用自行生成的证书签名的文档,可以是「密码学上有效」却一文不值。相反的陷阱,则是把一个无法确定的吊销检查(响应端离线)当成通过——或当成失败。两者都不是。它是未知的,而一个正确的验证器会如实返回未知,而不是向任一方向猜测。一个隐藏了五项检查中实际执行了哪些检查的绿色对勾,并不是验证结果。它是别人替你做的一个决定。
限制与边界
标题为“限制与边界”的章节NextPDF 执行并测试结构性和密码学检查。它不会替你选择信任锚点,也不保证建立在其上的策略。你要信任哪些证书,是引擎无法替你做出的部署决策。一条验证到某个你本不该信任的锚点的链,仍然是一个你无法依赖的验证结果。吊销证据只有在可获取或已内嵌时才能被检查。一个离线的响应端会产生「无法确定」,而把它转换成一个判定结果是一项策略选择,而非引擎的选择。本页描述的是检查项,而非法律上的充分性。一个通过验证的签名是否具有某种特定的法律效力,取决于证书、签名者、司法管辖区,以及相关义务。内嵌证据如何让这些检查在多年后仍能被回答,在 长期验证 中有所说明;完整性检查背后的字节范围机制,则收录于 签名如何置于 PDF 中。
验证接口的方案版本可用性:
| Edition | Availability |
|---|---|
| Core | 覆盖针对已签名属性的完整性与真实性,外加 RFC 5280 §6 针对所提供信任锚点的证书路径验证。 |
| Pro | 新增 RFC 3161 时间戳令牌验证,也就是把受信任时间这个问题拆解为可独立检查的多个步骤。 |
| Enterprise | 新增吊销评估(OCSP/CRL),以及针对内嵌长期素材的验证,并将无法确定的结果与明确的结果区分开。 |
相关文档
标题为“相关文档”的章节- 长期验证——内嵌证据如何让时间与吊销检查在多年后仍能被回答。
- 签名如何置于 PDF 中——完整性检查所重新计算的字节范围机制。
- 时间戳与受信任时间——说明是什么让「时间」这个问题能够不依赖签名者时钟来回答。
词汇表
标题为“词汇表”的章节- 完整性检查——重新计算字节范围摘要,并将它与签名所涵盖的值比对。
- 真实性检查——使用签名证书的公钥,针对已签名属性验证该密码学签名。
- 已签名属性——签名实际用于计算的、经过鉴别的 CMS 属性(content-type、message-digest、signing-time、signing-certificate-v2)。
- 证书路径验证——构建并检查从签名证书到所选信任锚点的链(RFC 5280 §6)。
- 信任锚点——你决定要信任的一个证书颁发机构;一条可接受路径的根。
- 吊销检查——通过 OCSP 或 CRL,判定一张证书在相关时间点是否曾遭吊销。
- 无法确定——一个既非「良好」也非「已吊销」的吊销结果,因为证据无法获取;既不算通过,也不算失败。