跳到內容

KMS 提供者契約

HsmSignerInterface 是供第三方實作的公開契約,用以為 NextPDF 提供金鑰託管。私密金鑰由你的程式碼保管,引擎則負責建構 CMS 結構。

Terminal window
composer require nextpdf/core:^3

NextPDF 將簽章組裝與金鑰託管分離。引擎負責準備位元組範圍,並組裝 CMS SignedData 結構;它並不持有私密金鑰。金鑰託管則透過公開契約委派給簽章後端。

你會實作以下三種公開契約之一:

  • SignerInterface 基礎契約,會針對給定資料回傳 SignatureResult、套用時間戳記,並回報長期驗證(LTV)的支援情形。當簽章邏輯在行程內執行時,請使用此契約。
  • 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回報是否能嵌入長期驗證(LTV)資料。

NextPDF\Contracts\DeferredSignerInterface(實驗性,自 3.0.0 起)延伸 SignerInterface,加入 submitForSigning()retrieveSignature()isComplete(),供非同步後端使用。

引擎產生的簽章層級遵循 PAdES 設定檔。ETSI EN 319 142-2 定義了延伸的 PAdES 設定檔,建構於 EN 319 142-1 的基準建構區塊之上。引擎會將 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';
}
}

正式環境的提供者會驗證輸入;遇到權杖錯誤時,會以 fail-closed(失敗即拒)方式處理,且絕不會記錄金鑰或簽章材料。

<?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 演算法名稱。
  • 失敗即拒(fail-closed)。 遇到任何權杖或 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 強制金鑰不可匯出
簽章預言機(signing oracle)無上限的簽章請求實作會對呼叫端進行速率限制與身分驗證
憑證鏈替換實作會回傳可建構至受信任根憑證的鏈
簽章位元組透過日誌洩漏失敗即拒的錯誤路徑;遙測中不含任何簽章材料
已逾密碼週期的金鑰持續使用依 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 與硬體安全模組(HSM)的金鑰託管,因此其前置資料設定 export_control_class: legal-review-required。依文件審查政策(plan §17 gate 6),任何具有安全模式,或路徑或內容符合 PAdESFIPSHSMPKCS#11 的頁面,都必須先取得 @nextpdf-labs/crypto-reviewers GitHub 團隊核可,才能發布。該 CODEOWNERS 核可是一道硬性合併 gate:本頁會維持 publish: false,直到法務出口管制審查與 @nextpdf-labs/crypto-reviewers 審查兩者都完成為止。

NextPDF Enterprise 提供此契約的受支援實作,具備金鑰管理系統託管、憑證鏈組裝與稽核整合。你可在 Core 中自行實作 HsmSignerInterface,或透過同一份公開契約使用 Enterprise 的實作,且無需變更程式碼。

詞彙表定義了 key management systemcryptoperiodHSM;各自的標準定義請參閱已發布的詞彙表。