跳转到内容

KMS 提供方契约

HsmSignerInterface 是第三方实现的公开契约,用于向 NextPDF 提供密钥托管。私钥由你的代码持有。引擎负责构建 CMS 结构。

Terminal window
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() 方法能够将原始密钥字节读入进程内存,契约的安全属性就会失效。

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。

契约仅传输待签名的数据和证书材料,不会向签名后端传输文档内容或个人数据。请保持字节范围最小化。不要在签名原因或位置字段中放入个人数据。这些字段在已签名文件中可被观察到。

绝不记录传递给 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),任何具有安全模式,或路径或内容匹配 PAdESFIPSHSMPKCS#11 的页面,在发布前都需要 @nextpdf-labs/crypto-reviewers GitHub 团队的签署批准。该 CODEOWNERS 批准是一道硬性合并关卡:本页保持 publish: false,直到法律出口管制审查和 @nextpdf-labs/crypto-reviewers 审查均完成。

NextPDF Enterprise 提供这个契约的受支持实现,具备密钥管理系统托管、证书链组装和审计集成。你可以在 Core 中自行实现 HsmSignerInterface,或通过同一公开契约使用 Enterprise 实现,而无需更改代码。

术语表定义了 密钥管理系统加密周期HSM;各术语的规范定义请参见已发布的术语表。