跳转到内容
getnextpdf.com

Enterprise 版本

HSM 签名 — 深度参考

本页是 NextPDF Enterprise HSM 签名面的深度参考。它涵盖三个公有类型。NextPDF\Enterprise\Security\Signature\Hsm\Pkcs11Signer 通过 ext-pkcs11 扩展经由 PKCS#11 令牌签名。NextPDF\Enterprise\Security\Signature\Hsm\OpenSslCliSigner 在子进程中通过 openssl 二进制文件签名,适用于 PHP ext-openssl 无法加载的 provider 或 engine 支持的密钥。NextPDF\Enterprise\Security\Signature\Hsm\Provider\HsmSignerProviderAdapter 将任一具体实现暴露为统一的 SignerProviderInterface。在每条路径中,私钥都留在令牌边界之内;NextPDF 交出待签名的字节并接收签名。后量子路径(signPqs)是一项预览:默认禁用、不携带任何符合性声明,并且在当前 PDF 校验器中没有受支持的验证路径。NextPDF 不持有任何认证,也不授予任何认证;支持不等于符合,符合不等于认证。

此能力随 NextPDF Enterprisenextpdf/enterprise)发布,并以 Enterprise 层级的许可证封套激活。缺少该授权的部署不会加载此能力的类。比较各版本并获取许可证

三个类型都位于 NextPDF\Enterprise\Security\Signature\Hsm;适配器位于其 Provider 子命名空间。两个签名器都实现 Core 的 NextPDF\Contracts\HsmSignerInterface 契约。

符号参数默认行为返回抛出或失败于说明
Pkcs11Signer::__construct()string $libraryPath, int $slotId, string $pin, string $certLabel, ?string $keyLabel = null, array $chainDer = [], bool $enablePostQuantum = false, ?FipsSignatureEnforcer $fipsEnforcer = null打开厂商库、登录槽位,并从令牌加载证书与密钥算法元数据ext-pkcs11 缺失或令牌访问失败时抛出 HsmOperationException每个进程按库路径缓存一个模块句柄;PIN 与标签为 #[SensitiveParameter]
Pkcs11Signer::sign()string $data, string $algorithm = 'sha256WithRSAEncryption'在令牌上签名;原始 ECDSA 输出被转换为 DER ECDSA-Sig-Valuestring 原始签名字节HsmOperationException(密钥未找到、令牌故障);InvalidArgumentException(未映射的算法);当接入了 enforcer 时在签名前触发 FIPS 门控异常封闭的算法集;参见行为契约
Pkcs11Signer::signPqs()string $data, Pkcs11PqsAlgorithm $algorithm, string $context = '', bool $randomized = true除非设置了 $enablePostQuantum 否则拒绝;派发临时性的 PKCS#11 PQ 机制string 原始签名字节HsmOperationException(已禁用、令牌故障、签名长度不匹配);InvalidArgumentException(context 超过 255 字节)预览;无符合性声明;机制标识符为临时性
Pkcs11Signer::isPostQuantumEnabled()报告构造器的选择加入标志bool
Pkcs11Signer::getCertificateDer()返回从令牌读取的签名者证书string(DER)在构造时加载一次
Pkcs11Signer::getCertificateChainDer()返回构造器提供的中间证书array<string>(DER)不含签名者证书
OpenSslCliSigner::__construct()string $keyUri, string $certPath, string $pin, array $extraCertPaths = [], OpenSslCliBackend $backend = OpenSslCliBackend::Auto, string $opensslBinary = 'openssl', int $timeoutSeconds = 30, ?string $modulePath = null, ?string $configPath = null, bool $legacyPinDelivery = false, ?FipsSignatureEnforcer $fipsEnforcer = null校验 proc_open、探测二进制文件与版本、解析后端并加载证书HsmOperationExceptionproc_open 被禁用、缺失模块/配置/证书文件、二进制文件故障、无后端);InvalidArgumentException$keyUri 内含 pin-valueOpenSslCliBackend::Auto 优先 OpenSSL 3.x provider,其次 engine
OpenSslCliSigner::sign()string $data, string $algorithm = 'sha256WithRSAEncryption'在子进程中运行 openssl dgst;默认情况下 PIN 通过一个临时的 0600 pin-source 文件传递string 原始签名字节HsmOperationException(超时、PIN 被拒、密钥未找到、模块加载失败、空输出、pin 文件失败);InvalidArgumentException(未映射的算法);签名前触发 FIPS 门控异常子进程在 $timeoutSeconds 后被终止;stderr 在进入消息前被脱敏
OpenSslCliSigner 访问器面只读的构造结果string / array<string> / OpenSslCliBackendgetCertificateDergetCertificateChainDergetPublicKeyAlgorithmgetCertificatePemgetResolvedBackendgetOpensslVersion
HsmSignerProviderAdapter::__construct()HsmSignerInterface $hsm, string $providerId, SignatureAlgorithm $algorithm = SignatureAlgorithm::Pkcs1v15将一个 HSM 具体实现包装为 SignerProviderInterfaceProvider id 约定:pkcs11-{module-id}openssl-cli
HsmSignerProviderAdapter::providerId()返回构造器提供的 idnon-empty-string
HsmSignerProviderAdapter::supportsAlgorithm()SignatureAlgorithm $algo将枚举映射为 OpenSSL 风格的名称,再与后端允许集取交集bool拒绝仅摘要的算法;openssl-engine id 不声明任何能力
HsmSignerProviderAdapter::sign()string $data, ?string $keyVersion = null以所配置的算法派发到被包装的签名器non-empty-stringKeyManagementException(非空的 $keyVersion);SignatureFailedException(不可映射的算法、驱动故障、空签名)故障即关闭的 SPI 契约;每个驱动错误都以类型化方式浮现
public function __construct(private readonly string $libraryPath, private readonly int $slotId, #[SensitiveParameter] private readonly string $pin, #[SensitiveParameter] private readonly string $certLabel, #[SensitiveParameter] private readonly ?string $keyLabel = null, array $chainDer = [], private readonly bool $enablePostQuantum = false, ?FipsSignatureEnforcer $fipsEnforcer = null)
public function sign(string $data, string $algorithm = 'sha256WithRSAEncryption'): string
public function signPqs(string $data, Pkcs11PqsAlgorithm $algorithm, string $context = '', bool $randomized = true): string
public function isPostQuantumEnabled(): bool
public function getCertificateDer(): string
public function getCertificateChainDer(): array
public function __construct(private string $keyUri, string $certPath, #[SensitiveParameter] private string $pin, array $extraCertPaths = [], private OpenSslCliBackend $backend = OpenSslCliBackend::Auto, private string $opensslBinary = 'openssl', private int $timeoutSeconds = 30, private ?string $modulePath = null, private ?string $configPath = null, private bool $legacyPinDelivery = false, private ?FipsSignatureEnforcer $fipsEnforcer = null)
public function sign(string $data, string $algorithm = 'sha256WithRSAEncryption'): string
public function getCertificateDer(): string
public function getCertificateChainDer(): array
public function getPublicKeyAlgorithm(): string
public function getCertificatePem(): string
public function getResolvedBackend(): OpenSslCliBackend
public function getOpensslVersion(): string
public function __construct(private HsmSignerInterface $hsm, private string $providerId, private SignatureAlgorithm $algorithm = SignatureAlgorithm::Pkcs1v15)
public function providerId(): string
public function supportsAlgorithm(SignatureAlgorithm $algo): bool
public function sign(string $data, ?string $keyVersion = null): string
  • 密钥托管。 私钥永远不离开令牌边界。Pkcs11Signer 将操作委派给令牌;OpenSslCliSigner 将一个密钥引用——一个 PKCS#11 URI——传递给 openssl 子进程。两个签名器都无法导出密钥。
  • 会话与登录。 Pkcs11Signer 每个进程按库路径缓存一个 PKCS#11 模块句柄,因为令牌接口每个进程必须恰好初始化一次。每次操作都会打开一个会话并用 PIN 登录;登录在任何私钥使用之前对用户进行身份验证(PKCS#11 v3.1 §5.6.8)。当槽位报告已存在登录时,签名器会登出并再次登录,因此要求每次操作提供新 PIN 的令牌会收到一个新 PIN。
  • 算法集(封闭)。 两个签名器都精确接受:sha256WithRSAEncryptionsha384WithRSAEncryptionsha512WithRSAEncryptionRSASSA-PSSRSASSA-PSS-SHA256RSASSA-PSS-SHA384RSASSA-PSS-SHA512ecdsa-with-SHA256ecdsa-with-SHA384ecdsa-with-SHA512Pkcs11Signer 还额外接受 ecdsa-raw。任何其他标识符都会引发 InvalidArgumentException——绝不会签署替代算法。
  • PSS 盐绑定。 对于每个 PSS 变体,盐长度等于摘要长度——32、48 或 64 字节——且 hash 与 MGF 参数匹配所选摘要。这遵循 PSS 机制参数结构,其中盐长度通常为消息哈希长度(PKCS#11 v3.1 §6.1.9)。两个签名器应用相同的配对,因此在一个后端上有效的配置在另一个后端上同样有效。
  • ECDSA 转换。 令牌以 rs 的原始、零填充拼接形式返回 ECDSA 签名(PKCS#11 v3.1 §6.3.1)。Pkcs11Signer::sign() 将该输出转换为 PDF 校验器与 OpenSSL 所期望的 DER 编码 ECDSA-Sig-Value 形式。调用方永远不会处理原始形式。
  • PIN 传递(CLI 路径)。 在安全默认下,PIN 被写入一个仅以属主专属权限创建的临时文件,通过 PKCS#11 URI 的 pin-source 属性引用,并在子进程退出后解除链接。在此模式下 PIN 不放入命令行,也不导出到子进程环境。当 $legacyPinDelivery = true 时,PIN 作为 pin-value 嵌入 URI,可在进程命令行中被观察到;该模式仅为选择加入。
  • 子进程纪律。 OpenSslCliSigner 以参数数组方式启动二进制文件——无 shell 插值——强制执行 $timeoutSeconds,在到期时终止子进程,并将 stderr 分类为类型化错误。在 stderr 被引入异常消息之前,秘密会被脱敏。
  • 适配器语义。 HSM 令牌没有受管的密钥版本概念;令牌上的密钥即版本。因此 HsmSignerProviderAdapter::sign() 会以 KeyManagementException 拒绝任何非空的 $keyVersion,而不是忽略它。supportsAlgorithm() 将枚举映射与被包装后端的接受集取交集,因此适配器绝不会声明后端在签名时会拒绝的机制。来自驱动的空签名会引发 SignatureFailedException
  • 后量子预览。 signPqs()$enablePostQuantum 构造器标志门控,否则拒绝运行。context 字符串限于 255 字节,与 ML-DSA 的 context 上界一致(FIPS 204)。返回的签名必须匹配所选 Pkcs11PqsAlgorithm 参数集的精确字节长度,否则调用失败。机制标识符遵循一个临时性的 PKCS#11 PQ 扩展,并非最终版本。PAdES 配置文件不识别后量子套件,多数 PDF 校验器拒绝此类签名,且 NextPDF 不为它们提供验证路径。不作任何符合性声明。
  • 在没有 ext-pkcs11 的情况下构造 Pkcs11Signer 会立即引发 HsmOperationException;该扩展不随标准 PHP 发行版捆绑。
  • 令牌上没有对象与之匹配的证书或私钥标签会引发 HsmOperationException,并指明缺失的对象类。在某些令牌上,密钥标签合理地可能与证书标签不同。
  • 反复登录失败会在令牌处锁定 PIN;执行该策略的是令牌,而非 NextPDF。密钥要求每次使用都进行身份验证的令牌,会通过登出并重试的路径收到一次新登录(PKCS#11 v3.1,always-authenticate 语义)。
  • OpenSslCliSigner 在构造时以故障即关闭的方式拒绝已包含 pin-value$keyUri,因为那种传递会绕过安全 PIN 路径。
  • 在 Windows 上,安全 pin 文件模式以 HsmOperationException 故障即关闭:那里文件权限位无法限制 ACL 读授权,因此签名器拒绝在临时目录 ACL 处留下明文 PIN。旧式 PIN 传递是受信任 Windows 主机上有文档记载的、选择加入的替代方案。
  • 后端自动检测的 provider 路径需要 OpenSSL 3.x;LibreSSL 永远不会解析到 provider。当 provider 与 engine 探测均未成功时,构造以 HsmOperationException 失败,而不是把失败推迟到签名时。
  • 超过 $timeoutSeconds 的子进程会被终止并报告为超时;干净退出但输出为空的子进程会被报告为空签名失败。两种情况都无法产生部分签名的文档。
  • 字节长度与所选参数集不匹配的后量子签名会在到达 CMS 编码之前被拒绝。
  • 使用已退役的 openssl-engine provider id 的 HsmSignerProviderAdapter 不声明任何算法,因此过期的配置会在 provider 选择时失败,而不是在签名时失败。

两个签名器都接受一个可选的 FipsSignatureEnforcer。当接入一个时,该签名器的 FIPS 模式即处于激活:sign() 会在任何令牌或子进程签名发生之前拒绝不被允许的签名算法或低于下限的密钥。这些下限遵循签名生成表——RSA 模数低于 2048 位以及 ECDSA 阶低于 224 位是不被允许的(NIST SP 800-131A Rev.2 §3 Table 2)。没有 enforcer 时,行为保持不变。该门控仅覆盖经典的 sign() 路径;signPqs() 由其自身的预览标志治理。这些是关于 NextPDF 代码的能力声明:FIPS 140-3 验证是通过 CMVP 附着到一个密码模块的,在此部署中该模块是运营方的 HSM 或 provider——NextPDF 不是经验证的模块,不持有任何认证,也不授予任何认证。

声明标准条款
登录在私钥操作之前对用户向令牌进行身份验证;错误的 PIN 拒绝访问。PKCS#11 v3.1§5.6.8
Always-authenticate 密钥每次使用都需要一次新登录;反复失败的重新身份验证会锁定 PIN。PKCS#11 v3.1CKA_ALWAYS_AUTHENTICATE re-authentication
令牌的 ECDSA 签名是原始的 r‖s 拼接;签名器将其转换为 DER 以实现 PDF 互操作。PKCS#11 v3.1§6.3.1
PSS 参数绑定 hash、MGF 与盐长度;签名器将盐设为等于摘要长度。PKCS#11 v3.1§6.1.9
FIPS 门控拒绝使用低于 2048 位的 RSA 或阶低于 224 位的 ECDSA 生成签名。NIST SP 800-131A Rev.2§3 Table 2
后量子 context 字符串限于 255 字节。FIPS 204HashML-DSA context handling
FIPS 140-3 验证通过 CMVP 附着到密码模块。FIPS 140-3CMVP program scope

所有条款均为释义;不复制任何规范性文本。NextPDF 不作任何认证声明。 签名器将其行为与所引条款对齐,作为一项能力。所产生的签名是否验证通过,是校验方针对其信任锚的决定;密钥安全取决于令牌、HSM 与运营方——而不仅取决于 NextPDF。

  • PIN 传递机制遵循 PKCS#11 URI 的 pin-source 约定(RFC 7512);该 RFC 不在所引语料之内,因此上述行为以产品源码为依据,而非规范引用。

  • 在构造 Pkcs11Signer 之前,确认运行时加载了 ext-pkcs11;当扩展缺失时构造会快速失败。CLI 签名器需要启用 proc_open,以及一个安装了 PKCS#11 provider 或 engine 的 openssl 二进制文件。

  • PIN、证书标签与密钥标签是 #[SensitiveParameter],因此它们被排除在堆栈跟踪之外。从秘密管理器提供 PIN;切勿将其写入源码、提交到版本控制的配置或日志中。

  • 构造是两个签名器上代价高昂的步骤:PKCS#11 路径会登录并读取证书,CLI 路径会探测二进制文件与后端。构造一次并复用该实例;按库的模块缓存使得针对同一库的重复构造是安全的。

  • 当调用方通过 SignerProviderInterface 工作时,用 HsmSignerProviderAdapter 包装一个签名器。为被包装的类传入规范的 provider id——pkcs11-{module-id}openssl-cli——使能力检查使用正确的后端允许集。

  • 在启用后量子预览之前,将令牌固件的机制标识符与 NextPDF 注册的临时值进行核对;不匹配会在签名时失败。不要为生产 PAdES 输出启用预览。

  • getResolvedBackend()getOpensslVersion() 用于证据记录;当你的合规程序要求可复现性时,将它们与签名证据一并持久化。

本页仅记录外部可观察的行为与受支持的公有 API 面。内部命名空间路径、辅助类、机制表、runbook 文件名与工单前缀均不在范围内。