正确验证签名
Spec: RFC 5280, §6RFC 5280 §6Spec: RFC 6960RFC 6960Spec: RFC 5652RFC 5652
“签名有效”通常只表示检查了一件事:密码学计算是否匹配。一次正确的验证至少要检查五件彼此独立的事;其中任何一件出错,都可能让那个绿色对勾失去意义。本页列出完整的检查项,并说明为什么不完整的答案会带来风险。
为什么这很重要
标题为“为什么这很重要”的章节在这个主题中,单一布尔值是最危险的输出。它会让读者误把“有效”当成“值得信任”;然而“有效”可能只代表那些字节未经篡改,而签名所用的密钥,来自一张三年前就过期、上个月遭到吊销、且链接不到任何你认可的机构的证书。上述每一项都是独立检查。返回单一布尔值的软件,已经默默替你决定了哪些检查才重要,而且这个决定是替你做的。在受监管或具有合同效力的场合,如果工具只验证了成本最低的那项属性,“工具说它有效”并不能作为辩护。
简短版本
标题为“简短版本”的章节一次完整的验证会回答五个各自独立的问题。它们彼此独立——通过其中一项,并不能说明其他项的任何情况:
- 完整性——对已签名的字节计算哈希后,是否仍与签名所涵盖的值相符?(重新计算字节范围摘要,再进行比对。)
- 真实性——密码学签名是否能使用签名证书中的公钥,针对已签名属性完成验证?
- 证书路径——该证书是否能链接到你所选择的信任锚点,且每个环节都有效?
- 时间——在相关时间点,证书是否处于其有效期内,而那个时间是否属于受信任的时间、而非自行声称的时间?
- 吊销——在当时,证书是否未遭吊销,且相关证据(OCSP/CRL)是否是你实际能够获取的,或已经内嵌在文档中?
一个未执行全部五项检查的“有效”,是一个看起来像完整答案的不完整答案。
NextPDF 的处理方式
标题为“NextPDF 的处理方式”的章节NextPDF 的立场是:每一个问题都各自独立,且每一个都必须得到明确回答。绝不把一个问题压缩成单一的乐观标记,也绝不因为某项检查不方便而默默跳过。这一点由测试强制保证。这正是本页被标示为由测试支撑(test-backed)、而非由标准支撑(standard-backed)的原因:这项行为由测试套件固定,而不仅仅是从某条条款推导而来。
完整性与真实性通过端到端测试覆盖。一张已知的证书会对一个真实的已签名属性结构进行签名,测试套件再使用匹配的公钥,在多个时间向量上验证该签名。因此,任何破坏这种规范结构的变更都会让测试失败。证书路径验证由一组测试固定:这些测试会刻意篡改一个签名字节,并断言结果为非有效,且附带一个结构化原因——它不是被丢弃的异常,而是一条明确记录的失败。时间戳令牌的验证被拆解成数个离散步骤——解码、签名者信息、已签名属性、消息摘要、证书绑定、密钥用途、签名、产生时间(produced-at)——而每一个步骤都有独立测试,因此“时间戳通过验证”意味着每一个步骤都通过了验证。对于签名时间戳而言,其中有一个步骤既非可选、也非通用:令牌的消息压印(message imprint)必须绑定它所声称涵盖的那个签名——令牌内部的哈希必须等于签名者自身签名值的哈希,否则该令牌所证明的便是另一批不同的字节。NextPDF 将此实现为一项专门的 verifySignatureTimestamp 检查,它以签名值作为必需输入,因而无法被跳过;并在遇到空输入、无法识别或弱(SHA-1)的压印算法、格式错误的压印,或哈希不匹配时,一律判定为失败(fail closed)。吊销的软性失败(无法连接的响应端)在代码和测试中都与明确的“已吊销”区分开。这两者绝不会被混为同一个答案。
- IntegrityRecompute the byte-range digest and compare it to the value the signature covers.
- AuthenticityVerify the cryptographic signature against the certificate’s public key, over the signed attributes — not the raw content.
- Certificate pathBuild and validate the chain to a trust anchor you chose; every link’s signature, validity, and constraints must hold.
- TimeConfirm the certificate was valid at the relevant instant, and that the instant is trusted time, not the signer’s clock.
- RevocationConfirm the certificate was not revoked at that time, using obtainable or embedded OCSP/CRL evidence.
实践示例
标题为“实践示例”的章节真正有启发性的,不是某一次 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,判定一张证书在相关时间点是否曾遭吊销。
- 无法确定——一个既非“良好”也非“已吊销”的吊销结果,因为证据无法获取;既不算通过,也不算失败。
- 压印绑定(签名时间戳)——一项检查,用于确认签名时间戳的消息压印等于它所涵盖的签名者 SignerInfo 签名值的哈希,从而使该时间戳可被证明适用于那个签名,而非另一批不同的字节。