跳转到内容
getnextpdf.com

安全与签名错误

本页记录 NextPDF\Security 命名空间树中的安全领域异常。每个条目都会指明类名、说明它在何时被抛出、列出其 getContext() 返回的字段,并给出一个恢复步骤。

这些类大多数都继承自 SecurityException,而后者继承 NextPdfException 并实现 ContextAwareExceptionInterface。这意味着 getContext(): array 返回的是结构化、不含机密的诊断信息,你可以把它路由到日志或应用性能监控(APM)流水线。捕获 SecurityException 即可用一个块拦截每一个安全领域的失败;当你需要某个具体子类的类型化负载时,请捕获它。

这棵树中有少数几个类直接继承 RuntimeException,而非 SecurityException。它们在下文中被标出;它们不暴露 getContext(), 而且大多数被记录为你不应指望在应用代码中捕获的内部控制流信号。

方面行为
基类契约NextPdfException::getContext() 返回 [];子类会重写它。
机密卫生消息和上下文会略去原始密钥材料、明文、PIN 和初始化向量(IV)字节。密钥仅以指纹前缀的形式呈现。
SecurityException抽象基类;自身不携带任何字段。子类定义负载。
  • When thrown. 从不被直接抛出;它是安全领域的抽象基类。 它的存在使得一个 catch (SecurityException $e) 块就能拦截认证加密的完整性失败、随机数重用防御、 PDF/A 与加密的绑定、密钥管理故障,以及 PKI 失败。
  • Context fields. 自身没有。它继承自 NextPdfException 的空默认实现;由子类填充负载。
  • Recovery. 为可操作的处理捕获具体子类,或为粗粒度的安全事件路由捕获 SecurityException

这些由 AES-GCM(Galois/Counter Mode)加密器和 PDF/A 守卫抛出。如需症状优先的指引,参见 加密与权限

  • When thrown. 一次带关联数据的认证加密(AEAD) 解密因某个非篡改原因而失败:截断的密文、缺失的 IV,或在 API 边界提供了错误的密钥,以致于没有足够的材料让完整性检查真正运行。这是一个配置或传输错误,而非一起安全事件。
  • Context fields. algorithm(例如 AES-256-GCM)、reason(例如 ciphertext shorter than IV+tag)。
  • Recovery. 核实密文、IV 和密钥都完整且组帧正确; 不要把它当作篡改。请与 TamperedDataException 对比。
  • When thrown. AEAD 认证标签未能通过验证。该标签覆盖密文加上关联认证数据(AAD);如果其中任何一项在加密之后被修改,底层的 openssl_decrypt() 会返回 false。这个独立的子类型让你可以浮现一个安全事件级别的告警,而非一个组帧错误。
  • Context fields. algorithmciphertext_length(被拒密文的长度,不含 IV 和标签)。
  • Recovery. 当作篡改或错误的密钥/IV 来处理。不要盲目重试; 调查该密文的来源。依据 ISO/TS 32003:2023 §5.2 和 NIST SP 800-38D §6.5,一次失败的标签检查意味着数据不是真实的。
  • When thrown. AES-GCM 被要求用同一对密钥和 IV 加密两次。加密器以每实例的单调计数器进行防御,并作为纵深防御,再用一个运行时的哈希集合记录每一对已发出的(密钥指纹、IV) 对。由于该计数器从构造上就排除了碰撞,这次触发是一个关键优先级的 bug 指示器,它在生产中绝不应出现。重用一对密钥/IV 会危及整个密钥流(ISO/TS 32003:2023 §5.2 NOTE 2; NIST SP 800-38D §8.3)。
  • Context fields. key_fingerprint_prefix(SHA-256(key) 前 8 个十六进制字符)、iv_length(对于 ISO/TS 32003 始终为 12)、reasonhashset-collisioncounter-rollover,用以区分一个击穿计数器的重构 bug 与那个 2^63 计数器触发线),以及 iv_fixed_field_hex(IV 固定字段,仅在被提供时存在,以它自己的键报告,绝不会被错误地标记为密钥指纹)。
  • Recovery. 立即中止并轮换密钥。提交一份缺陷报告;这表明加密器中存在 bug,而非调用方输入有误。
  • When thrown. 对某个给定的 AES-GCM 密钥,达到了一个可选启用的 NIST SP 800-38D §8.3 使用安全调用计数。这是一个纵深防御的遥测钩子,供那些希望比加密器内部的架构限制更早地强制执行规范推荐边界(每密钥约 2^32 次调用)的调用方使用。它默认不会触发;只有 assertWithinSafetyBound() 辅助方法才会抛出它。
  • Context fields. key_fingerprint_prefixinvocation_count(当前 encrypt() 计数,达到或高于限制)、invocation_limit(可选启用的边界)。
  • Recovery. 在累计碰撞与伪造概率不再可忽略之前,轮换文档密钥(用新的密钥材料构造一个全新的加密器), 或扩展调用方策略以拒绝继续服务。
  • When thrown. 在一个 PDF/A 标记的文档上尝试一次加密操作。PDF/A 家族(PDF/A-2、PDF/A-3、PDF/A-4)一致地禁止加密:依据 ISO 19005 §6.1.3,Encrypt 键不应出现在 trailer 中,而 ISO 19005-4:2020 附录 A 和 B 原封不动地继承了这一点。PDF/A 与加密之间不存在任何被允许的组合。
  • Context fields. pdfa_mode(例如 pdfa4pdfa3)、 encryption_operation(被拒绝的调用,例如 useAesGcm)。
  • Recovery. 要产生一个加密文档,请略去 enablePdfA() 调用;要产生一个归档文档,请略去加密调用。参见 PDF/A 与 PDF/UA 验证
  • When thrown. 某个已配置的密码学策略拒绝了由某次核心签名、 加密或哈希操作所选择的算法、密钥强度或密码套件。它是合规强制的失败即关闭边界(例如 FIPS 140-2/3、 eIDAS,或自定义企业策略),由 CryptoPolicyEnforcer 在任何签名或密文被产生之前抛出,因此一次违反策略的操作永远无法发出一个未经批准的产物。它有别于一次狭义的 OpenSSL 操作失败,也有别于一次签名原语失败:这是对一个在其他方面有效的请求的策略性拒绝。与 NIST SP 800-131A Rev. 2 和 ISO/IEC 19790:2025 §7 对齐。
  • Context fields. policy(策略名称,例如 FIPS 140-3 Strict)、 categoryhashsignatureencryptionkey-strength)、item(被拒绝的项,例如一个对象标识符(OID)、密码套件名称,或 rsa/1024)、reason
  • Recovery. 选择一个被所指名策略批准的算法、密钥长度或密码套件,或者如果该策略归你所有,则调整它。把结构化上下文路由到已记录的合规运行手册。

存在两个同名的类。它们共享 SecurityException 根,因此一个 catch (SecurityException $e) 块就能同时捕获二者,但它们携带不同的负载。 当你需要某个具体的形态时,请以完全限定名导入。

KeyManagementException(生命周期:NextPDF\Security\Exception

标题为“KeyManagementException(生命周期:NextPDF\Security\Exception)”的章节
  • When thrown. 一次密钥管理操作在密钥被某个签名或加密原语消费之前就失败:Privacy-Enhanced Mail(PEM)、PKCS#12 或 PKCS#11 密钥解析失败;密钥派生(HKDF、PBKDF2、scrypt)失败;在一个错误的密钥加密密钥上 AES Key Wrap(RFC 3394)被拒绝;某个硬件安全模块 (HSM)返回了格式错误的 Distinguished Encoding Rules(DER);或某个 Ed25519 种子长度不匹配。
  • Context fields. operation(例如 load_pemkek_derivekey_wrap)、key_type(例如 RSAEC-P256Ed25519AES-256)、 reason。绝不包含原始密钥材料。
  • Recovery. 检查所指名的操作和密钥类型,修正源密钥材料或派生输入,然后重试。

KeyManagementException(签名路径:NextPDF\Security\Signature\Exception

标题为“KeyManagementException(签名路径:NextPDF\Security\Signature\Exception)”的章节
  • When thrown. 某个签名提供者遇到一个密钥管理故障:所请求的密钥版本未知、被禁用、计划销毁、缺少签名权限,或在其他方面不可用。这就是 RsaPssSignerLocalKeySignerProvider 在实时密钥失败时抛出的内容。具名构造函数: unknownKeyVersion()keyVersionDisabled()
  • Context fields. providerIdkeyVersionreason。访问器: providerId()keyVersion()reason()
  • Recovery. 轮换或重新授权该密钥,或选择一个可用的密钥版本, 然后重试。它有别于 SignatureFailedException,后者表示签名原语本身失败了。

如需关于无法达到的层级和缺失能力的症状优先指引,参见 签名与时间戳失败

SignatureFailedException (R4-13: NextPDF\Security\Exception)

标题为“SignatureFailedException (R4-13: NextPDF\Security\Exception)”的章节
  • When thrown. 一次密码学签名操作失败:某个 RSA、ECDSA 或 Ed25519 签名原语返回 false 或长度错误的输出;某个 HSM 或 PKCS#11 令牌以非成功状态响应;Cryptographic Message Syntax(CMS) SignedData 装配在某个格式错误的证书或链上失败;或某个 Ed25519 往返自验证失败。新代码应优先使用这个 R4-13 子类型,而非旧式的、与 PAdES 耦合的签名异常。
  • Context fields. operation(例如 signverifybuild_cms)、 algorithm(例如 rsa-pkcs1v15-sha256ed25519)、reason。访问器: getOperation()getAlgorithm()getReason()
  • Recovery. 阅读操作和算法,纠正输入(密钥、 证书链,或后端可用性),然后重试。与 ETSI EN 319 142-1 的失败即关闭密钥处理姿态对齐。

SignatureFailedException (SPI: NextPDF\Security\Signature\Exception)

标题为“SignatureFailedException (SPI: NextPDF\Security\Signature\Exception)”的章节
  • When thrown. 某个 SignerProviderInterface 实现因任何不属于密钥管理类别的原因而无法完成一次签名操作:后端驱动错误、格式错误的密钥材料,或不可恢复的 HSM I/O。这是失败即关闭签名契约的万能捕获,在该契约下,每个原语都会在失败时抛出,而非返回 nullfalse 或空字符串。具名构造函数:forProvider()
  • Context fields. providerIdreason。访问器:providerId()reason()
  • Recovery. 检查提供者 id 和 reason,修正提供者后端或密钥材料,然后重试。在 KeyManagementException 与这个类型之间进行分支,以区分“密钥有问题”和“原语失败了”。
  • When thrown. 所请求的 PAdES 一致性层级无法在当前运行时基础设施下被满足(最常见的是 B-T 及以上层级缺少一个时间戳授权机构),且调用方没有授予降级的许可。默认是失败即关闭:引擎会拒绝,而非在对外宣称达到较高层级的同时静默地产生一个较低层级,那将会是一次 eIDAS 级别的回退。 与 ETSI EN 319 142-1 §6 对齐。请注意,这个类直接继承 NextPdfException (而非 SecurityException)。
  • Context fields. requestedLevelhighestAchievableLevelreason。 访问器:requestedLevel()highestAchievableLevel()reason()
  • Recovery. 阅读 reason 以识别缺失的基础设施并补上它(例如,配置一个时间戳授权机构),或把 allowDegradation: true 传给 PadesOrchestrator 以有意接受最高可达成的层级。
  • When thrown. SignerProviderRegistry::get() 被请求一个未注册的提供者 id。它实现 PSR-11 NotFoundExceptionInterface,因此该注册表符合 PSR-11 容器契约。具名构造函数: forId()。这个类继承 RuntimeException 且不暴露 getContext()
  • Context fields. 无。未注册的 id 会出现在消息中。
  • Recovery. 在请求该提供者之前,以期望的 id 注册它, 或纠正你传给注册表的 id。

这些类继承 RuntimeException 且不暴露 getContext()。SHAKE256 是某些 ISO/TS 32001 路径所要求的 SHA-3 可扩展输出函数。

  • When thrown. 在摘要计算时,当所选提供者无法满足该请求时。具名构造函数:noBackend()(在此主机上的所有尝试层级中都没有可用的 SHAKE256 后端)和 ffiCallFailed()(某个 FFI 绑定的 OpenSSL EVP 调用返回了非成功状态,例如来自一个被裁剪过的 libcrypto 构建)。
  • Context fields. 无。消息会指明所尝试的层级或失败的符号。
  • Recovery. 安装带有 OpenSSL 3.x 的 ext-ffi,或升级到一个在 hash_algos() 中暴露 shake256 的 PHP 构建。一个用户态的 Keccak 回退被有意地不予提供。
  • When thrown. 当能力探测失败、以致提供者无法被实例化时, 由某个 SHAKE256 提供者构造函数抛出。它是一个控制流信号: 提供者注册表会捕获它、记录该层级标签,并尝试下一个层级。 它绝不应逃逸进应用代码。具名构造函数:forTier()
  • Context fields. 无。消息会指明该层级和原因。
  • Recovery. 不可由调用方直接操作;如果整条层级链都被耗尽,注册表会改为浮现 Shake256NotAvailableException::noBackend(), 后者携带面向运维的修复方法。

这些覆盖存储于 /AuthCode 下的 ISO/TS 32004 文档级消息认证码 (MAC)。二者都继承 NextPdfException 并重写 getContext()

  • When thrown. 失败即关闭,由 MAC 令牌读取器在某个 CMS AuthenticatedData MAC 令牌结构上格式错误,或声明了一个在约定的 ISO/TS 32004 集合之外的算法时抛出。具名构造函数:malformed()algorithmMismatch()。标记为 @internal
  • Context fields. statusDocumentMacVerificationStatus 值,要么为 MalformedToken,要么为 AlgorithmMismatch)。公开的 readonly 属性:$status
  • Recovery. 把该文档当作未验证来处理。一个格式错误的令牌,或一个在约定集合之外的算法,都意味着该 MAC 无法建立信任;不要当作内容已受保护那样继续操作。
  • When thrown. 失败即关闭,当一次文档级 MAC 验证无法达到一个受信任的状态时:缺失或格式错误的 /AuthCode、一个在约定集合之外的算法、一次解包失败,或一次 MAC 不匹配(篡改)。验证器的 verify() 会返回一个用于分支的显式结果;这是由 assertVerified() 抛出的异常流对应物,以便那些“信任内容”的代码永远无法越过一个未验证的文档继续运行。具名构造函数:fromResult()
  • Context fields. statusDocumentMacVerificationStatus 值)。公开的 readonly 属性:$status
  • Recovery. 不要信任该文档内容。检查 status 以区分一次篡改(MAC 不匹配)与一个配置问题(缺失或格式错误的 /AuthCode、算法不匹配)。

这些覆盖 RFC 5280 证书路径验证。该基类型及其子类都是失败即关闭的。

  • When thrown. 来自 RFC 5280 路径验证器的一次严格模式失败。它是那些更窄子类(ChainLengthExceededExceptionUnsupportedExtensionException)的非 final 基类,因此捕获这个类型的处理器也会通过里氏替换捕获那些子类。继承 SecurityException
  • Context fields. 不重写 getContext()(继承空默认实现)。它在被冻结的公开 readonly 数组属性 $reasons(一个由规则名加描述字符串组成的非空列表)中携带结构化原因。
  • Recovery. 阅读 $reasons 以识别失败的规则,修正证书链,然后重新验证。捕获这个类型以统一处理任何路径验证失败。
  • When thrown. 路径验证器被要求遍历一条其长度超出已配置上限的链。该上限会在任何解析开始之前被强制执行, 因此一个恶意供应方无法把验证器驱入二次方的工作量,也无法用一条任意深的链耗尽资源。默认上限 10 遵循 PKIX-CMP 配置文件(RFC 4210 §5.3.18);真实世界的链可以容纳在 5 到 6 个条目内。PkiPathValidationException 的子类。
  • Context fields. 继承空的 getContext();原因字符串 chain_length_exceeded: supplied=<n> cap=<n> 会被转发进父类的 $reasons。公开的 readonly 属性:$supplied$cap
  • Recovery. 提供一条在上限之内的链,或者如果预期会有一条合法地更长的链,则提高已配置的上限。
  • When thrown. 路径验证器遇到一个其强制执行尚未实现的关键 X.509 扩展。依据 RFC 5280 §4.2,一个无法识别的关键扩展必须失败即关闭;严格和宽松两种模式在此都失败即关闭,因为静默地跳过一个关键扩展将会是一次安全回退。该验证器覆盖链构建、AKI/SKI 匹配、密钥用法、扩展密钥用法、 基本约束、过期,以及签名验证;其他任何关键内容都会在此浮现。PkiPathValidationException 的子类。
  • Context fields. 继承空的 getContext();结构化原因会被转发进父类的 $reasons。公开的 readonly 属性: $extensionOid(点分 OID,例如名称约束的 2.5.29.30)、 $extensionName$clauseRef(指向 RFC 5280 条款和那条 deferred-items 日志条目的指针)。
  • Recovery. 在宽松模式下,捕获这个具体子类以回退到一个更粗的策略,而不会吞掉真正的路径验证失败。对照你的 PKI 夹具审计 $extensionOid$clauseRef,看看是哪个扩展在阻塞验证。
  • When thrown. OCSP 和证书撤销列表(CRL)端点都被耗尽却没有一个确定的裁决:OCSP 传输失败或格式错误的响应,以及 CRL 传输失败或格式错误的 CRL,且两个断路器都打开或两个缓存都缺失。严格模式把它当作失败即关闭;宽松模式则捕获它并发出一条带 revocation = null 的 PSR-3 警告。继承 SecurityException
  • Context fields. 不重写 getContext()(继承空默认实现)。它在公开的 readonly 属性 $ocspState$crlState(各自默认为 unknown)中携带状态。
  • Recovery. 恢复对某个撤销源的可达性、等待断路器关闭,或预热缓存,然后重试。不要为了获得一个长期验证产物而抑制它;该撤销断言是那个层级的一部分。
  • When thrown. 一个 RFC 6960 §4.2.2.2 BasicOCSPResponse 签名未能针对响应者证书通过密码学验证。解析器会解码 signatureAlgorithm(RSA-PSS、ECDSA 或 RSA-PKCS1v15),并在 tbsResponseData 上验证 signature;任何失败都会抛出这个类型化的异常,以便调用方能区分一个结构有效但密码学上被篡改的响应与一个格式错误的 DER 响应。它是非 final 的,因此下游包可以发布更具体的子类。继承 SecurityException
  • Context fields. 不重写 getContext()(继承空默认实现)。它在公开的 readonly 属性 $reason 中携带失败标签(例如 signature_mismatchresponder_cert_not_in_bundleunsupported_signature_algorithm);自由文本 detail 会被折入消息。
  • Recovery. 检查 $reason。对于 responder_cert_not_in_bundle,提供正确的信任锚点包和响应者证书。对于 signature_mismatch, 把该响应当作不可信来处理。参见 签名与时间戳失败
  • When thrown. RFC 3161 时间戳授权机构(TSA)通信或响应解析中的一次失败:TSA 返回一个错误状态、HTTP 请求失败,或 ASN.1 响应无法被解析。它是 TSA 故障层级的基类, 且是非 final 的,以便验证失败可以扩展它。继承 NextPdfException
  • Context fields. 不重写 getContext()(继承空默认实现)。
  • Recovery. 为任何 TSA 故障路径捕获 TsaException。核实 TSA 的可达性,以及该端点返回一个良构的 RFC 3161 响应。
  • When thrown. 一个 RFC 3161 TimeStampToken 的 CMS 验证在任何一个被强制的验证步骤上失败:RFC 5816 §3 ESSCertIDv2 绑定、RFC 5652 §11 签名属性完整性、RFC 3161 §2.4.2 producedAt 新鲜度,或 RFC 5652 §5.4 SignerInfo 签名。失败即关闭,带有一个类型化的步骤判别器,以便审计流水线能区分重放、时钟偏差与证书不匹配,而无需 grep 消息。它是 TsaException 的子类,因此旧式的 catch (TsaException) 处理器会继续触发。
  • Context fields. step(失败的流水线 Step 值)和 message。 访问器:getStep()
  • Recovery. 可由开发者(配置错误的 TSA 证书或偏差容忍度)或安全人员(疑似 MITM 或重放)操作。阅读 step 以定位失败的阶段,并修正对应的输入或信任配置。
  • When thrown. 内部信号,表示一次 DER 遍历击中了一个格式错误或截断的边界,由 TSA 令牌验证器内部的底层遍历器抛出。它总是在公开的 verify 边界被捕获,并被重新包装进一个携带正确步骤判别器的 TsaTokenVerificationException;它绝不会泄漏到调用方代码。继承 RuntimeException;标记为 @internal
  • Context fields. 无。
  • Recovery. 不面向调用方。请改为处理被包装的 TsaTokenVerificationException

这些类继承 RuntimeException 且不暴露 getContext()。二者都是失败即关闭的解码器。

  • When thrown. 名称约束解码器遇到一个它无法忠实解码的、 可强制执行的 GeneralSubtree 元素。RFC 5280 §4.2.1.10 要求一个依赖方处理一个可强制执行的名称约束,否则拒绝该证书;把先前的静默丢弃转换为这个类型化的失败,可以防止一次会静默地放宽被接受名称集合的失败即开放。范围限于可强制执行的名称形式(directoryName、dNSName、iPAddress、 rfc822Name、uniformResourceIdentifier);不可强制执行的形式仍可被忽略,绝不会抛出它。具名构造函数:undecodableEnforceableBase()。标记为 @internal
  • Context fields. 无。一个对日志安全的细节字符串携带在消息中。
  • Recovery. 强制器会浮现一个失败即关闭的 name_constraints: 原因, 且该链被拒绝。调查该证书的名称约束编码; 不要放宽强制。
  • When thrown. qcStatements 扩展在结构上格式错误: 截断的 DER、错误的标签,或长度溢出。该解码器是失败即关闭的, 当它无法确定该扩展究竟在表达什么时,会抛出,而非返回一个部分或启发式的结果。标记为 @api
  • Context fields. 无。
  • Recovery. 仅当你打算容忍格式错误的编码时,才显式捕获它;否则,把该证书的合格证书声明当作不可确定来处理,并拒绝或重新签发该证书。
  • When thrown. 一个 PKCS#11 v3.1 会话管理缺陷。每个具名构造函数都映射到一个具体的缺陷类别,并映射到一个 PKCS#11 CKR_* 返回值,通过类型化的 $kind 判别器暴露,以便调用方在一个稳定的枚举字符串上分支,而非依赖脆弱的消息匹配。构造函数包括: cryptokiNotInitialized()userNotLoggedIn()userAlreadyLoggedIn()operationNotInitialized()operationActive()mechanismNotAllowed()tokenDisconnected()concurrentSessionLimitExceeded()sessionAlreadyClosed()stateTransitionInvalid()osLockingRequired()loginTtlExpired(),以及 signOperationTtlExpired()。继承 SecurityException
  • Context fields. 不重写 getContext()(继承空默认实现)。它在公开的 readonly 属性 $kind 中携带类型化的 kind,它是那些 KIND_* 常量之一(例如 KIND_USER_NOT_LOGGED_INKIND_TOKEN_DISCONNECTEDKIND_LOGIN_TTL_EXPIRED)。槽位和会话标识符、机制以及 TTL 值都出现在消息中。PIN 和证书字节绝不会被包含。
  • Recovery.$kind 上进行 switch。对于 user_not_logged_in,在初始化一个签名操作之前用用户 PIN 登录。对于 token_disconnected,把该槽位上的所有会话当作孤儿来处理。对于那些 TTL kind,重新认证或重新初始化该操作。对于 mechanism_not_allowed,扩展已配置的机制允许列表,或选取一个被允许的机制。