跳转到内容

已签署合约的工作流程

Spec: ETSI EN 319 142-2, §5.1 Spec: ISO 32000-2:2020, §12.8 Evidence: Mixed evidence

已签署的合约不是「一份上面有签名的 PDF」。它是一份经过准备、让签名覆盖正确字节的文件;它在与义务匹配的层级上完成签署;它的封装方式让它在签名证书过期后仍可验证。本页将带你走完整个场景:从一份空白文件到一份长期有效的合约,并如实说明引擎目前已连接哪些步骤,哪些能力仅以冻结接口公开。

一份在签署当天可验证、却在三年后的争议中失效的合约,比完全没有签名还糟,因为已经有人据此信赖了它。失败的原因很少出在密码学本身,通常是缺少时间戳或吊销证据,而这些问题往往要到签署者早已离开后才被裁定。

事先选定签名义务,并在签署当下产出该义务所需的证据,正是一份站得住脚的合约与一份悄然失效的合约之间的区别。这应当是在工作流程中一次性做出的决定,而不是每次发生争议时才重新发现的问题。

  • 先准备,再签署。 签名只覆盖它据以计算的那段字节范围。在签署「之前」就确认文件已是最终版本,包括任何原本打算稍后附加的修订,而不是签署之后才决定。
  • 为义务选择层级,而不是反过来。 PAdES 定义了一个递进:基本签名、加上时间戳的签名、嵌入验证材料的签名,以及为无限期封存有效性而重新加盖时间戳的签名(ETSI EN 319 142-2 §5.1)。
  • 长期有效性是一种结构,而不是一项属性。 它由写入文件中的文件安全存储区和文件时间戳组成(ISO 32000-2:2020 §12.8)。
  • 把边界说清楚。 NextPDF 的高阶 Document::setSignature() 冻结了公开 API,但会快速失败,而不会输出一份未签署的文件。已连接的路径是较低阶的协调器。本页不会假装事实并非如此。

NextPDF 将决策机制分开。决策是义务需要哪一个 PAdES 层级;机制是字节如何被签署, 以及验证材料如何被携带。把这两者混为一谈, 正是团队最终得到一个有效却不持久的签名的原因。

这个场景分为四个阶段。

  1. Prepare the document Compose the agreement and treat it as final. The signature will protect this exact byte range and nothing added outside it without a new revision.
  2. Choose the obligation B-B proves who. B-T adds trusted time. B-LT embeds the material to validate later. B-LTA re-stamps for indefinite validity.
  3. Sign A CMS signature is embedded in the signature dictionary over the byte range; a timestamp is requested from a TSA if the level needs one.
  4. Preserve For long-term levels, the Document Security Store and a document timestamp are written so the signature outlives its certificate.
从头到尾的已签署合约情境:每个阶段都增添一项保证,而你在第二阶段所选的层级,决定了第三与第四阶段必须产出多少证据。

这个层级枚举是真实存在的,而且它诚实地编码了义务。B-B 证明签署者的身份。B-T 加上一个受信任的时间戳,让「何时」和「是谁」同样明确。B-LT 嵌入在签名证书过期后验证签名所需的证书与吊销响应。 B-LTA 加上文件时间戳,让信任链得以延伸, 并在每张时间戳证书失效前重新加盖时间戳,以无限期维持下去。这个枚举知道哪些层级需要时间戳、哪些需要嵌入的验证材料,因此引擎可以拒绝一个不可能的组合, 而不是产出一份具有误导性的「已签署」文件。

这一递进关系有标准依据 Evidence: Standard-backed Spec: ETSI EN 319 142-2, §5.1 描述了 PAdES 层级: 它们叠加在 EN 319 142-1 的构件之上,从一个基本的嵌入式签名逐级向上递进。长期结构则由 Spec: ISO 32000-2:2020, §12.8 固定:长期验证建立在写入文件中的文件安全存储区与文件时间戳字典之上。

这个层级模型有代码支持 Evidence: Code-backed 这个 SignatureLevel 枚举具备四个 PAdES 枚举项(B-BB-TB-LTB-LTA),并附带用于「需要时间戳」、「需要嵌入验证材料」与「需要文件时间戳」的判定方法。这个高阶的 Document::setSignature() 连接到一个快速失败防护: 它会引发一个阻断性、可操作的诊断信息,而不是输出一份未签署的文件——这是一个可验证的行为,而非一项声称。

长期维护行为以能力层级记录于 Premium 级别:封存接口会写入 DSS 与各个签名的 VRI,通过健康检查查看封存完整性,并在时间戳证书过期前用文件时间戳重新加盖。文件明确指出,最终仍由验证者裁定。

这里展示的是决策——选择义务——使用真实的枚举。已连接的签署路径是较低阶的协调器。展示高阶调用,只是为了把快速失败行为说明清楚。

<?php
declare(strict_types=1);
use NextPDF\Security\Signature\SignatureLevel;
/**
* Map a business obligation to a PAdES level.
*
* The obligation drives the level, not the reverse — choosing B-B for a
* 10-year contract is a decision you do not want to make implicitly.
*/
function levelForObligation(string $obligation): SignatureLevel
{
return match ($obligation) {
// Internal sign-off, short retention, signer identity is enough.
'internal_approval' => SignatureLevel::PAdES_B_B,
// Counterparty agreement: prove the moment of signing.
'counterparty_agreement' => SignatureLevel::PAdES_B_T,
// Regulated contract that must verify after cert expiry.
'regulated_contract' => SignatureLevel::PAdES_B_LT,
// Long-lived legal record: indefinite, re-stampable validity.
'long_term_legal_record' => SignatureLevel::PAdES_B_LTA,
default => throw new \InvalidArgumentException(
"Unknown obligation: {$obligation}",
),
};
}
$level = levelForObligation('regulated_contract');
// The enum carries the obligation's implications with it.
$needsTsa = $level->requiresTimestamp(); // true for B-T+
$needsDss = $level->requiresDss(); // true for B-LT+
$needsArchive = $level->requiresDocumentTimestamp(); // true only for B-LTA

层级对象并不是一个标签。它回答了义务意味着什么,因此下游连接不必重新推导。

反复出现的误解是:「签名有效,所以我们完成了。」 今日有效是必要的,但并不充分。签名是针对一段字节范围计算出来的。任何在稍后修订中附加到该范围之外的内容,都不在它的覆盖范围内,这正是准备必须先于签署的原因。而一个现在可验证的签名,在其证书过期后可能失效,除非验证材料已在签署当下嵌入。「有效」与「持久」是不同的保证,只有义务才能告诉你实际需要哪一种。

此引擎有一个相关的特定陷阱:假设高阶的 setSignature() 今日会产出一份已签署的文件。它不会。它会快速失败, 这是刻意设计的。请把这条诊断信息当作契约看待。

  • Document::setSignature() 是一个冻结的公开接口,而非已连接的签署器。 它会以一个阻断性的诊断信息快速失败。它绝不会把一份未签署的 PDF 当作已签署的 PDF 输出。已连接的路径是较低阶的两阶段协调器。
  • 长期验证维护(DSS/VRI、健康检查、封存时间戳循环)是 Premium 级别的能力。 Core 并不提供封存循环。请参见下方的边界。
  • 签名只保护它自己的字节范围。 稍后附加的修订是分开的。引擎不会回溯性地扩展覆盖范围。
  • NextPDF 产生并维护这些结构;它并不裁定。 一个签名是否受信任,取决于验证者的信任锚点与政策,而这些都在引擎之外。
  • B-LTA 本身并不会让一个签名永恒有效。 唯有当重新加盖时间戳的循环在每张时间戳证书过期前按时执行,它才能实现无限期的有效性。
  • 本页对 Premium 封存接口只作行为层级说明。它并未主张任何特定法院或机关会接受。
PAdES signing and long-term validity — edition availability
Edition Availability
Core

Core 公开了 SignatureLevel 枚举以及冻结的 Document::setSignature() 接口(快速失败)。已连接的较低阶协调器涵盖了基线签署。长期封存维护则不属于 Core。

Pro

通过协调器提供 PAdES 基线签署(B-B / B-T)。

Enterprise

加上 B-LT / B-LTA、文件安全存储区与各个签名的 VRI 维护、LTV 健康检查,以及用于无限期有效性的文件时间戳封存循环。

  • PAdES 基线配置文件——按递进方式说明 B-BB-TB-LTB-LTA,以及如何选择。
  • 长期验证——为什么一个今日可验证的签名在十年后可能失效,以及 LTV 如何嵌入证据。
  • 整合决策指南——哪一个生态系统套件适合一个签署工作流程,包括 NextPDF Connect 中的人工批准连接点。
  • PAdES — PDF Advanced Electronic Signatures(PDF 高级电子签名):定义高级电子签名如何承载于 PDF 中的 ETSI 配置文件家族。
  • Byte range(字节范围) — 签名据以计算的那段连续文件字节;位于其外的内容不受该签名保护。
  • 签名层级(B-B / B-T / B-LT / B-LTAPAdES 递进:谁签署、何时签署、附带嵌入的验证材料,以及可重新加盖时间戳以维持无限期封存有效性。
  • TSA — Time-Stamping Authority(时间戳机构):一项 RFC 3161 服务,声明某个文件状态在给定的 UTC 时刻存在过。
  • DSS(Document Security Store,文件安全存储区) — 文件内的存储区,存放在证书过期后验证签名所需的证书、OCSP 响应与 CRL。
  • LTV(Long-Term Validation,长期验证) — 通过嵌入验证证据并重新加盖时间戳,让一个签名随时间推移仍可验证。
  • Fail-fast(快速失败) — 拒绝产出一份具有误导性的产物,并改为引发一个可操作的错误,而不是悄悄输出一份错误的文件。