跳转到内容
getnextpdf.com

Pro 版本

云 KMS 签名(AWS KMS、Azure Key Vault、GCP KMS)

NextPDF Pro 使用托管在云端密钥管理服务(KMS)中的密钥来签名 PDF。受支持的 provider 包括 Amazon Web Services(AWS)KMS、Microsoft Azure Key Vault,以及 Google Cloud Platform(GCP)Cloud KMS。每个 provider 都实现同一套签名契约,因此你的应用程序依赖的是这套契约,而不是某个 provider 类。只有签名属性摘要会被发送给 provider;文档本身在签名操作中绝不会离开你的主机。本页面属于行为层级:它说明每个 provider 发送和接收什么、密钥版本如何解析,以及密钥保管在哪里不再是 NextPDF 的职责。

该契约扩展了 Core 的硬件与云端签名器契约,因此云端 KMS 策略可以接入 Core 签名器所使用的同一条签名路径。

前置条件已在 front matter 中说明,并在 前置条件 一节中重复列出。

云端 KMS 签名策略随 nextpdf/pro 包发行,并受 pro 授权功能标记限制。NextPDF Core 发行一个软件 CMS 签名器;NextPDF Enterprise 通过 PKCS#11 增加硬件密钥保管。云端 KMS 签名是一项 Pro 能力,由于 Enterprise 依赖 Pro,因此在 Enterprise 中也可使用。没有有效 Pro 权益的部署不会加载这些策略类;Core 签名契约则继续保持不变地工作。比较各版本

每个云端 KMS 签名器都实现一套 provider 契约,该契约扩展自 Core 签名器契约。该契约增加了三样东西:一个供注册表查找用的稳定 provider 标识符、一个可感知密钥版本的签名方法,以及对 provider 所支持算法的自描述,从而让编排器能在签名之前挑选一个兼容的 provider。

签名流程会把文档保留在你的主机上:

  1. Pro 签名会话计算文档摘要并构建 CMS 签名属性。
  2. 该会话对签名属性进行哈希,并仅将该摘要发送给 provider。一个接受调用方提供的消息摘要并返回签名的外部签名服务,正是将文档保留在你边界内的既定模式,欧盟数字签名服务(DSS)参考框架对此有所描述。
  3. provider 使用它解析出的密钥版本对摘要进行签名,并返回原始签名。
  4. 该会话组装 CMS SignedData 并将其嵌入 PDF。

这些 provider 是在纯 PSR-18 超文本传输协议(HTTP)调用之上实现的 —— 不依赖任何云厂商软件开发工具包(SDK)。身份验证被委派给你的应用程序:你提供一个 bearer token(AWS、GCP)或一个 token 或服务主体凭据(Azure)。每个 provider 都会为 CMS 归一化其输出:AWS 与 GCP 返回 DER 形式、可直接用于 CMS 的 Rivest–Shamir–Adleman(RSA)签名;某个 provider 以原始整数对形式返回的椭圆曲线数字签名算法(ECDSA)签名(Azure)会被转换为 DER 编码形式,而 GCP 返回的 ECDSA 已是 DER 编码。ECDSA 曲线与摘要按惯例配对 —— P-256 配 SHA-256、P-384 配 SHA-384、P-521 配 SHA-512 —— 遵循 RFC 5480 中推荐的配对方式。

一个 PSR-11 注册表按标识符解析各 provider,并支持惰性工厂。Enterprise 自托管客户可以通过实现 provider 契约并将其绑定到注册表中,来注册专有的 HSM 或 KMS 驱动 —— 而无需 fork NextPDF Pro。

各 provider 暴露不同的“活动版本”原语,因此默认的密钥版本行为各不相同:

  • AWS KMS —— null 密钥版本使用密钥别名,AWS 会在 provider 端将其解析为当前密钥版本。
  • Azure Key Vault —— null 密钥版本使用无版本的密钥 URL,Azure 会将其解析为最新启用的版本。显式覆盖值必须是一个 32 字符的十六进制标识符;任何其他值都会被拒绝,以防止 URL 段注入。
  • GCP Cloud KMS —— 非对称签名端点仅在某个特定的 crypto-key 版本上操作;不存在服务端的“活动版本”。你必须在配置中固定一个版本,或显式传入一个。两者都未设置时,签名器会抛出一个密钥管理错误,而不会去猜测。

请记录你的部署使用哪种模式,以使行为具有确定性。

  1. 安装 NextPDF Core 与 Pro 包,并持有一份有效的 Pro 授权。
  2. 在你选定的 provider 中预置一个签名密钥,并记下其标识符(AWS 用密钥别名或 Amazon Resource Name;Azure 用 vault 与密钥名;GCP 用 project、location、key ring、crypto key 与 version)。
  3. 提供一个 PSR-18 HTTP 客户端以及 PSR-17 request 与 stream 工厂。
  4. 在你的应用程序中获取 provider 凭据:AWS 或 GCP 用 bearer token,Azure 用预先获取的 token 或服务主体凭据。token 的获取是你应用程序的职责;请从你的密钥管理器提供机密,绝不要从源代码中提供。

每个 provider 都有一个由你的标识符与凭据构建的不可变配置对象。常见的配置关切点:

  • provider 标识符 —— aws-kmsazure-keyvaultgcp-kms,用作注册表的查找键。
  • 算法 —— 按调用从你的签名会话所传入的算法名称中选择;provider 会拒绝它不支持的算法。
  • 密钥版本 —— 在配置中固定或按调用传入,采用上文所述的各 provider 语义。
  • 凭据 —— 你的应用程序从其密钥管理器提供的 bearer token 或服务主体凭据。
  1. 从你的标识符以及一个从密钥管理器读取的凭据构建 provider 配置。
  2. 用该配置、DER 形式的签名者证书、证书链、PSR-18 客户端,以及 PSR-17 工厂构造 provider 签名器。
  3. 可选地在 PSR-11 注册表中以其标识符注册该 provider,以便编排器按名称解析它。
  4. 运行 Pro 签名会话:它计算摘要、构建签名属性,并仅以摘要调用 provider。
  5. 捕获最具体的失败 —— 密钥管理、不支持的算法,或签名失败 —— 记录一条不含机密的结构化消息,然后重新抛出。
examples/pro/kms-provider-registry.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Pro\Security\Signing\Kms\KeyManagementProviderRegistry;
use NextPDF\Pro\Security\Signing\Kms\KmsSignerInterface;
/**
* Register cloud-KMS providers behind one registry resolved by identifier.
*
* Each provider is supplied as a lazy factory so a provider is only
* constructed when first resolved. The caller depends on the registry and
* the provider contract, not on a concrete provider class.
*
* @param array<non-empty-string, callable(): KmsSignerInterface> $factories
* Provider factories keyed by provider identifier.
*
* @return KeyManagementProviderRegistry The populated registry.
*/
function buildKmsRegistry(array $factories): KeyManagementProviderRegistry
{
$registry = new KeyManagementProviderRegistry();
foreach ($factories as $providerId => $factory) {
$registry->registerFactory($providerId, $factory);
}
return $registry;
}
examples/pro/kms-sign-guarded.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Pro\Security\Signing\Kms\KmsSignerInterface;
use NextPDF\Pro\Security\Exception\KeyManagementException;
use NextPDF\Pro\Security\Exception\SignatureFailedException;
use NextPDF\Pro\Security\Exception\UnsupportedAlgorithmException;
use Psr\Log\LoggerInterface;
final readonly class KmsSigningService
{
public function __construct(
private KmsSignerInterface $provider,
private LoggerInterface $logger,
) {}
/**
* Sign a signed-attributes digest with a pinned key version.
*
* Only the digest is sent to the provider; the document stays on the
* host. Each failure mode is caught as its most specific type so the
* caller can distinguish a key-version problem from a transport failure.
*
* @param string $digest The signed-attributes digest to sign.
* @param string $algorithm The OpenSSL-style algorithm name.
* @param string|null $keyVersion The pinned key version, or null for the
* provider default (per-provider semantics).
*
* @throws KeyManagementException When the key version is unknown or required and absent.
* @throws UnsupportedAlgorithmException When the provider does not support the algorithm.
* @throws SignatureFailedException When the provider sign operation fails.
*
* @return string The raw signature bytes (DER for RSA and ECDSA per CMS rules).
*/
public function sign(string $digest, string $algorithm, ?string $keyVersion): string
{
try {
return $this->provider->signWithVersion($digest, $algorithm, $keyVersion);
} catch (KeyManagementException | UnsupportedAlgorithmException | SignatureFailedException $e) {
$this->logger->error('KMS signing failed', [
'provider' => $this->provider->providerId(),
'reason' => $e->getMessage(),
]);
throw $e;
}
}
}
  1. 在签名之前确认 provider 自描述了你打算使用的算法,这样不支持的算法会在选择阶段被捕获,而不是在 provider 调用时才暴露。
  2. 确认只有摘要被传输:文档字节绝不能出现在 provider 请求体中。请求携带的是 base64 编码的摘要,而非文件本身。
  3. 对于 ECDSA,确认嵌入的签名是 DER 编码的 —— 签名器会替你转换原始整数对形式的签名。
  4. 在一个配置了你的信任锚的校验器中打开已签名的 PDF,并确认该签名被报告为密码学上完整。一个已生成的签名并不等于一个已验证的签名;信任判定属于校验方。
  5. 确认没有任何 token、凭据或密钥材料出现在你的应用程序日志中。
  • 密钥留在 provider 中。 云端 KMS 策略是一个集成点,而非一个密钥库。对于 KMS 策略,NextPDF Pro 并不持有私钥。
  • 只有摘要跨越边界。 该会话将签名属性摘要发送给 provider,而非文档 —— 这就是欧盟 DSS 参考框架所描述的消息摘要输入模式。
  • 字节范围由引擎计算。 它绝不会从调用方接受。
  • 失败即关闭。 provider、网络、密钥版本或不支持算法的失败都会抛出一个带类型的异常。该会话不会静默地产出一个未签名的文档,也绝不会替换为更弱的算法。
  • 凭据即机密。 token 与服务主体凭据来自你的密钥管理器,并被排除在日志之外。

本页面涉及密码学签名。每一处规范性来源都经过转述;不复现任何规范性原文。 ### 密钥保管边界

密钥保护取决于密钥的处理方式、所配置的 KMS,以及部署环境。NextPDF Pro 提供的是 KMS 集成,而非密钥库。NextPDF Pro 仅在配置为对接一个通过 FIPS 验证的 KMS 或 HSM 时才是 FIPS 兼容的;它本身并不是一个通过 FIPS 验证的密码学模块,也不作任何 FIPS 认证主张。

  • 未知或已禁用的密钥版本。 provider 会将“未找到”或“版本已禁用”的响应映射为一个指明 provider 与密钥的密钥管理异常。
  • GCP 未固定版本。 当配置与调用都未提供版本时,GCP 签名器会抛出一个密钥管理错误,因为非对称签名端点仅在某个特定版本上操作。
  • 不支持的算法。 请求一个 provider 不支持的算法,会在任何网络调用之前抛出一个不支持算法的异常。
  • 传输失败。 PSR-18 客户端错误会被映射为一个签名失败异常;该会话不会产出部分结果。
  • 缺失凭据。 一个既无 token 也无服务主体凭据的签名器会抛出一个带类型的错误,而不是以未经身份验证的方式调用 provider。