KMS 提供方契约
HsmSignerInterface 是第三方实现的公开契约,用于向 NextPDF 提供密钥托管。私钥由你的代码持有。引擎负责构建 CMS 结构。
composer require nextpdf/core:^3概念概览
标题为“概念概览”的章节NextPDF 将签名组装与密钥托管分离。引擎会准备字节范围,并组装 CMS SignedData 结构;它并不持有私钥。密钥托管则通过一个公开契约委托给签名后端。
你需要实现三个公开契约之一:
SignerInterface。 基础契约。它针对给定数据返回一个SignatureResult,负责应用时间戳,并报告长期验证支持情况。当签名逻辑在进程内运行时,使用这个契约。HsmSignerInterface。 密钥托管契约。实现必须在硬件边界内执行签名操作,私钥不得离开该边界。密钥管理系统提供方实现这个契约。DeferredSignerInterface。SignerInterface针对异步后端的扩展。调用方提交数据,收到一个作业标识符,并在稍后取回结果。
本页规定的是公开契约,并不描述任何内部的 NextPDF Pro 或 NextPDF Enterprise 实现。付费版本可以提供这个契约的受支持实现。你依赖的是契约,而非实现。
密钥托管要求
标题为“密钥托管要求”的章节契约要求私钥永不离开安全边界。这一要求有标准实践支撑。NIST SP 800-57 Part 1 Revision 5 将密钥管理定义为在密钥的整个生命周期中对其进行处理。该生命周期包括安全的生成、存储、分发、使用与销毁。PKCS#11 v3.1 使该边界可以被强制执行。当私钥对象将 CKA_SENSITIVE 设为 true 或将 CKA_EXTRACTABLE 设为 false 时,令牌不得在自身边界外以明文形式泄露密钥值。
你的实现负责满足这一要求。引擎无法代你强制执行。如果你的 sign() 方法能够将原始密钥字节读入进程内存,契约的安全属性就会失效。
API 接口面
标题为“API 接口面”的章节NextPDF\Contracts\HsmSignerInterface(稳定,自 1.0.0 起):
| 方法 | 返回值 | 用途 |
|---|---|---|
sign(string $data, string $algorithm) | string | 在硬件边界内签名数据。返回原始签名字节。失败时抛出 RuntimeException。 |
getCertificateDer() | string | 以 DER 形式返回签名者的 X.509 证书。 |
getCertificateChainDer() | array<string> | 返回中间证书,从颁发者到根,不含签名者证书。 |
getPublicKeyAlgorithm() | string | 返回公钥算法标识符。 |
NextPDF\Contracts\SignerInterface(稳定,自 1.0.0 起):
| 方法 | 返回值 | 用途 |
|---|---|---|
sign(string $data) | SignatureResult | 返回 DER 编码的 CMS SignedData。 |
timestamp(string $signatureValue) | string | 返回 DER 编码的 RFC 3161 时间戳令牌。 |
supportsLtv() | bool | 报告是否可以嵌入长期验证数据。 |
NextPDF\Contracts\DeferredSignerInterface(实验性,自 3.0.0 起)扩展 SignerInterface,并新增 submitForSigning()、retrieveSignature() 和 isComplete(),以支持异步后端。
引擎生成的签名级别遵循 PAdES 配置文件。ETSI EN 319 142-2 定义了在 EN 319 142-1 基线构建块之上构建的扩展 PAdES 配置文件。引擎将 B-B、B-T、B-LT 和 B-LTA 级别映射到这些构建块。你的签名器负责提供引擎所需的加密操作和证书材料。
代码示例 — 快速上手
标题为“代码示例 — 快速上手”的章节下面是一个最小化的 PKCS#11 风格提供方。签名调用会委托给令牌,密钥值绝不会被读入 PHP。
<?php
declare(strict_types=1);
use NextPDF\Contracts\HsmSignerInterface;
final class TokenSigner implements HsmSignerInterface{ public function __construct(private readonly TokenSession $session) {}
public function sign(string $data, string $algorithm = 'sha256WithRSAEncryption'): string { // The token computes the signature. The key stays inside the token. return $this->session->c_sign($data, $algorithm); }
public function getCertificateDer(): string { return $this->session->readCertificate(); }
/** @return array<string> */ public function getCertificateChainDer(): array { return $this->session->readChain(); }
public function getPublicKeyAlgorithm(): string { return 'rsaEncryption'; }}代码示例 — 生产环境
标题为“代码示例 — 生产环境”的章节生产环境中的提供方会校验输入,在令牌出错时以失败关闭方式处理,并且绝不记录密钥或签名材料。
<?php
declare(strict_types=1);
use NextPDF\Contracts\HsmSignerInterface;use Psr\Log\LoggerInterface;use RuntimeException;
final class KmsSigner implements HsmSignerInterface{ public function __construct( private readonly RemoteKmsClient $kms, private readonly string $keyId, private readonly LoggerInterface $logger, ) {}
public function sign(string $data, string $algorithm = 'sha256WithRSAEncryption'): string { if ($data === '') { throw new RuntimeException('Refusing to sign empty data'); }
try { // The KMS performs the operation. The key never reaches this process. return $this->kms->sign($this->keyId, $data, $algorithm); } catch (\Throwable $error) { // Fail closed. Do not log key material or signature bytes. $this->logger->error('kms.sign.failed', ['key' => $this->keyId]);
throw new RuntimeException('KMS signing failed', previous: $error); } }
public function getCertificateDer(): string { return $this->kms->certificate($this->keyId); }
/** @return array<string> */ public function getCertificateChainDer(): array { return $this->kms->chain($this->keyId); }
public function getPublicKeyAlgorithm(): string { return $this->kms->algorithm($this->keyId); }}边界情况与陷阱
标题为“边界情况与陷阱”的章节- 签名字节格式。 返回原始签名字节。对于 RSA,这些字节是 DER。对于 ECDSA,这些字节先包含原始
r值,然后是s值。 - 链顺序。
getCertificateChainDer()不含签名者证书。请将中间证书从颁发者到根排序。 - 算法标识符。
sign()使用 OpenSSL 风格的标识符。getPublicKeyAlgorithm()返回 X.509 算法名称。 - 失败即失败关闭。 发生任何令牌或 KMS 错误时都应抛出
RuntimeException。不要返回部分签名或空签名。 - 密钥销毁。 当密钥达到其加密周期结束时,按你的密钥管理流程将其销毁。NIST SP 800-57 Part 1 Revision 5 §8.2.1.2 指出,密钥一旦不再需要就应当销毁。销毁过程本身遵循 §8.3.4。
数据驻留与 PII 缓解
标题为“数据驻留与 PII 缓解”的章节契约仅传输待签名的数据和证书材料,不会向签名后端传输文档内容或个人数据。请保持字节范围最小化。不要在签名原因或位置字段中放入个人数据。这些字段在已签名文件中可被观察到。
安全的遥测与日志清洗
标题为“安全的遥测与日志清洗”的章节绝不记录传递给 sign() 的数据、返回的签名字节、可恢复形式的密钥标识符,或任何证书私有组件。只记录操作结果和一个不可逆引用。SignatureAppliedEvent 钩子是受支持的审计锚点。参见 动作触发器。
威胁模型
标题为“威胁模型”的章节| 资产 | 威胁 | 缓解措施 |
|---|---|---|
| 私钥 | 泄露到进程内存 | 契约要求在边界内签名;PKCS#11 CKA_SENSITIVE / CKA_EXTRACTABLE 强制不可提取性 |
| 签名预言机 | 无约束的签名请求 | 实现对调用方进行速率限制和身份验证 |
| 证书链 | 替换 | 实现返回一条可构建至受信任根的链 |
| 签名字节 | 通过日志泄露 | 失败关闭的错误路径;遥测中不含签名材料 |
| 超过加密周期的密钥 | 继续使用 | 按 NIST SP 800-57 Part 1 Revision 5 §8.2.1.2 销毁密钥 |
符合性
标题为“符合性”的章节签名级别符合 PAdES 配置文件。ETSI EN 319 142-2 §5.1 在 EN 319 142-1 基线构建块之上定义了扩展 PAdES 配置文件。引擎会使用你的签名器提供的材料组装这些构建块。密钥管理与 NIST SP 800-57 Part 1 Revision 5 生命周期保持一致,包括 §8.2.1.2 销毁要求。硬件密钥托管与 PKCS#11 v3.1 不可提取密钥属性保持一致。引文记录在本页前置元数据中。
出口管制与审查治理
标题为“出口管制与审查治理”的章节本页涉及 PKCS#11 和硬件安全模块密钥托管,因此其前置元数据设置 export_control_class: legal-review-required。根据文档审查策略(计划 §17 关卡 6),任何具有安全模式,或路径或内容匹配 PAdES、FIPS、HSM 或 PKCS#11 的页面,在发布前都需要 @nextpdf-labs/crypto-reviewers GitHub 团队的签署批准。该 CODEOWNERS 批准是一道硬性合并关卡:本页保持 publish: false,直到法律出口管制审查和 @nextpdf-labs/crypto-reviewers 审查均完成。
商业背景
标题为“商业背景”的章节NextPDF Enterprise 提供这个契约的受支持实现,具备密钥管理系统托管、证书链组装和审计集成。你可以在 Core 中自行实现 HsmSignerInterface,或通过同一公开契约使用 Enterprise 实现,而无需更改代码。
另请参阅
标题为“另请参阅”的章节相关契约与模块
标题为“相关契约与模块”的章节- 签名契约参考 —
SignerInterface、HsmSignerInterface、DeferredSignerInterface以及时间戳提供方。 - 安全策略契约参考 — 同类安全敏感 SPI 接口面。
- SPI 稳定性规则 —
stable签名契约背后的接口承诺。 - 动作触发器与事件监听器 —
SignatureAppliedEvent,受支持的审计锚点。 - 扩展开发概览 — 完整的公开 SPI 接口面。
术语表定义了 密钥管理系统、加密周期 和 HSM;各术语的规范定义请参见已发布的术语表。