コンテンツにスキップ
getnextpdf.com

妥協なきスケールでの署名

Spec: ISO 32000-2, §12.8Spec: ETSI EN 319 142-1Spec: RFC 5652, §5.1

文書を 1 件署名するのは暗号操作です。締め切りに追われて 10 万件署名するのは、繰り返される同じ操作です。ただし、危険な失敗はもはや「遅かった」ではなく、「そのうちの 1 件が未署名のまま出ていき、誰も気づかなかった」になります。このページは、後者を、前者を諦めずに行うことについてです。すなわち、すべての署名が依然として正しく、署名できなかったファイルの送出を実行が拒み、大規模なジョブが最初からやり直すのではなく再開する、一括かつ並行の署名です。

署名は文書ごとの事実です。そのダイジェストは、署名値そのものを除外した宣言済みのバイト範囲に対して計算されるため(Spec: ISO 32000-2, §12.8)、1,000 件の文書を一括で「バッチとして」一気に署名する誠実な方法はありません。各文書は、それぞれ自身のバイトに対する自身の CMS SignedData を携えます(Spec: RFC 5652, §5.1)。したがってスケールは、まさに一つだけが静かに狂う確率を掛け算します。一瞬だけ失敗した鍵ハンドル、タイムアウトしたタイムスタンプ機関、半分書きかけのファイルを抱えたまま死んだワーカー。

高くつく結末はクラッシュではありません。クラッシュは騒がしく、あなたは再試行します。高くつく結末は 静かな もの — 完成したように見える未署名の PDF がアーカイブに鎮座し、数か月後に監査人のバリデータによって発見されることです。大量にあると、「ほぼ署名済み」は「署名済み」と見分けがつきません。問題の 1 件が確認されるその瞬間までは。スケールでの署名の眼目は、その結末を統計的に稀にすることではなく、構造的に不可能にすることです。

  • すべての文書は、それぞれ自身のバイト範囲に対して個別に署名されます。 バッチはスケジューリングの言葉であって、暗号の言葉ではありません。共有される署名はありません。
  • レベルはヒントではなく契約です。 あなたが PAdES ベースラインレベルを指名すると、エンジンはすべての文書に対して正確にそのレベルを生成するか、その文書を声高に失敗させます(Spec: ETSI EN 319 142-1)。
  • パイプラインはフェイルクローズドです。 正しく署名できなかった文書は、素のバイトとして通り抜けることはありません。引き渡されるのではなく、保留されます。
  • 並行性は文書ごとであり、構造上安全です。 署名ユニットは可変状態を共有しないため、2 つのワーカーが互いの出力を破壊することはありません。
  • 大規模な実行は堅牢です。 コミット済みの出力は再開時に再送されません。クラッシュした実行は、すべてを署名し直すのではなく、最後のチェックポイントから続きます。

この設計は一つの分離に基づいています。署名の生成は、小さく、決定論的で、文書ごとのステップである。それを数千件、安全に走らせるのはオーケストレーションのステップである。 両者を分けて保つことが、それぞれを単純なまま保たせます。

署名ステップは、決して妥協してはならない部分です。あなたはレベルを求めます — エンジンが解釈しなければならない文字列ではなく、SignatureLevel 列挙のケースです — そしてそのレベルは その 文書に対する契約として扱われます。エンジンは要求されたレベルを生成するか、対処可能なエラーで停止します。より低いレベルでひそかに署名し、記録だけがより高いレベルを主張するようなことはしません。背後にもっと多くの文書があるからといって、正しさが緩むことはありません。10 万件目の署名は、1 件目とまったく同じだけ注意深く計算されます。

フェイルクローズドのルールが、それを大量においても信頼に足るものにします。NextPDF の署名パスは、あなたが求めたものの代わりに、もっともらしく見えるが未署名の成果物を送出することを拒みます。サポートされたアプリケーション経路は、高レベルの Document API です。Document::setSignature() で署名を構成し、Document::getPdfData()(または save() / output())でバイトを求めると、その単一の書き込みパスは、正しく署名された PDF を送出するか、バイトを返す にスローします — 呼び出し側が署名済みと信じる未署名のファイルを返すことは決してありません。これをバッチ全体に適用すると、「1 件が未署名のまますり抜けた」を、静かな潜在的欠陥から、単一の失敗した再試行可能なジョブへと変換するルールになります。

  1. Warm the signing material onceOn worker boot, open the key/certificate source and the timestamp client. This cost is paid once per worker, not once per document.
  2. Enqueue the documentsA queue holds the per-document jobs. The queue is the throughput dial — signing workers scale horizontally behind it.
  3. Render and sign one documentA disposable unit renders the document, then signs it over its own byte range at the requested PAdES level. Nothing is shared with the next document.
  4. Commit on success, hold on failureA correctly-signed file commits once. A document that could not be signed is failed and retried — never emitted as unsigned bytes.
  5. Checkpoint, and resume on crashA durable run records what has committed. After a crash it continues from the last checkpoint instead of re-signing the whole batch.
A high-volume signing run end to end: shared signing material is warmed once; each document is rendered and signed individually on a disposable unit; a correctly-signed result commits exactly once, while any failure is held for retry, never passed on as unsigned bytes; a crashed run resumes from its checkpoint.

Core が提供するのは暗号的な正しさです。ソフトウェアによる CMS 署名と PAdES B-B(タイムスタンプクライアントを介して B-T まで)であり、各文書は個別かつフェイルクローズドで署名されます。大規模な実行を堅牢・並行・正確に一度きりにする オーケストレーション — 副作用のないレンダリングエンジンに加えて、コミッター、チェックポイント、冪等性、デッドレターの各ストア — は、上位エディションの Stream モジュールです。HSM やクラウド KMS を介したハードウェアバックの署名も同様に上位エディションの接合点です。Core は各署名が正しいことを証明し、上位エディションはその 100 万件を生き延びられるものにします。

以下に示す形は、バッチループの内側にある文書ごとの署名ユニットです。各反復は、指名されたレベルで 1 件の文書に署名し、正しく署名された結果を生み出すか、その 1 件のジョブを失敗させます — 結果を装った未署名のバイトを返すことは決してありません。

<?php
declare(strict_types=1);
use NextPDF\Contracts\DocumentFactoryInterface;
use NextPDF\Security\Signature\CertificateInfo;
use NextPDF\Security\Signature\SignatureLevel;
use NextPDF\Exception\SignatureException;
use Psr\Log\LoggerInterface;
/**
* One signing-batch iteration: render, sign at a named level, commit or fail.
*
* The factory and the certificate source ($certInfo, the warmed signing
* material) are process-lifetime singletons; the document is disposable. A
* document that cannot be signed at the requested level fails this job loudly —
* it is never committed unsigned.
*
* @param iterable<int, callable(\NextPDF\Core\Document): \NextPDF\Core\Document> $jobs
*/
function signBatch(
DocumentFactoryInterface $factory,
CertificateInfo $certInfo,
LoggerInterface $logger,
iterable $jobs,
): void {
// The level is an explicit, ordered contract — not a flag we hope is honoured.
$level = SignatureLevel::PAdES_B_T;
foreach ($jobs as $jobId => $build) {
// Fresh, disposable unit — shares the warmed signing material only.
$doc = $factory->create();
$doc = $build($doc);
try {
// Sign over this document's own byte range, at exactly $level,
// or throw. There is no "signed lower, reported higher" path.
$doc->setSignature(certInfo: $certInfo, level: $level);
$signed = $doc->getPdfData();
} catch (SignatureException $e) {
// Fail-closed: this document does NOT continue as unsigned bytes.
// The job is failed and left for retry / dead-letter handling.
$logger->error('pdf.sign.failed', ['job_id' => $jobId, 'reason' => $e->getMessage()]);
continue;
}
// Only a correctly-signed result reaches the commit step.
commitSignedOutput($jobId, $signed);
unset($doc, $signed); // release per-document state before the next iteration
$logger->info('pdf.sign.committed', ['job_id' => $jobId, 'level' => $level->value]);
}
}

catch が荷重を支える行です。署名できなかった文書を引き留める実行と、それでも出荷してしまう実行との違いがそこにあります。continue は失敗を取り繕うものではありません — ジョブは記録され、再試行のために残されます。そのためバッチは、何が署名され何がされなかったかについての既知で完全なリストを伴って終わり、静かな空白を残すことは決してありません。

第一の誤解は、「バッチ署名」が多数のファイルに適用される一つの署名を意味する、というものです。そうではありませんし、そう謳うシステムはどれも妥当な PAdES 署名を生成していません — 各文書のダイジェストは、それ自身のバイトに結び付けられているからです(Spec: ISO 32000-2, §12.8)。バッチはもっぱら どれだけ多くどれだけ速く に関するものであり、暗号ユニットを共有することについてのものでは決してありません。

第二は、並行性が速度のために正しさを緩めることを意味する — 速い署名者は、注意深い署名者がしない手抜きをしなければならない — というものです。そうではありません。署名ユニットは可変状態を共有しないため、それらを並列に走らせてもスケジュールが変わるだけで、バイトは変わりません。並列な各署名は単一のものと同じ厳密さで計算され、並列性はそれらを取り巻くオーケストレーションの中にあります。

第三は、堅牢性は最初の夜間実行が失敗してから後付けするものだ、というものです。そのときにはすでにその実行を失っています。再開可能なパイプラインは、文書ごとに、クラッシュ に何がコミットされ何がされなかったかを知っていなければなりません — それこそが、チェックポイントと冪等性のストアが記録するために存在する当のものです。

  • 各署名は文書ごとで標準に結び付けられており、バッチ用の近道はありません。 量はスケジューリングを変えるのであって、暗号ユニットを変えるのではありません。NextPDF はすべての文書を、それ自身のバイト範囲に対して署名します。
  • Core はソフトウェアによる CMS 署名と PAdES B-B(タイムスタンプクライアント経由で B-T)を行います。 堅牢・並行・正確に一度きりのレンダリングと署名のエンジンは、上位エディションの Stream モジュールです。HSM/KMS バックの鍵管理は上位エディションの接合点です。このページは、そのオーケストレーションを Core として主張しません。
  • フェイルクローズドはエンジンの振る舞いであって、あなたの配線に関する保証ではありません。 NextPDF は、未署名なのに署名済みと信じられるファイルの送出を拒み、サポートされた署名経路を提示します。その結果のエラーを捕捉してそれでもコミットするパイプラインは、保証を打ち消すことを選んだのです — それこそ、例の catch/continue が防ぐために存在する捉え方です。
  • PAdES レベルは文書ごとに強制されるのであって、実行に対して認証されるのではありません。 エンジンは要求されたベースラインレベルを生成するか失敗します。それは構造的な強制であって、生成されたファイルに対する第三者の準拠判定ではありません。レベルの進行そのものはPAdES ベースラインプロファイルで扱います。
  • キュー、鍵管理、タイムスタンプ機関、そしてオブジェクトストアはあなたのものです。 NextPDF は文書ごとの署名の正しさを、そして上位エディションでは堅牢なオーケストレーションのプリミティブを供給します。あなたのインフラを運用したり、あなたの TSA を保証したりはしません。
High-volume and concurrent signing — edition availability
EditionAvailability
Core

文書ごとのソフトウェア CMS 署名、PAdES B-B(タイムスタンプクライアントで B-T)。各文書のそれ自身のバイト範囲に対して個別に署名され、ひそかに未署名となる出力に対してフェイルクローズドです。素の文書ごとの署名に商用ティアは必要ありません。

Pro

Stream モジュールを追加します。副作用のないレンダリングエンジンに加え、堅牢なコミッター、チェックポイント、冪等性、デッドレターの各ストア — 並行・クラッシュ安全・正確に一度きりで、最初からやり直すのではなく再開するバッチ実行です。

Enterprise

ハードウェアバックの鍵管理(PKCS#11 経由の HSM、またはクラウド KMS)を追加し、秘密鍵がデバイスを離れることをなくします。さらに、大量のアーカイブを数十年にわたって検証可能に保つ長期 PAdES レベル(B-LT、B-LTA)を追加します。

  • High-volume document generation — このページがその上で署名する、メモリ上限つきでキュー化されたバッチモデル。スループットと計測の規律のために、まずこちらを読んでください。
  • PAdES baseline profiles — 各レベル(B-B から B-LTA まで)が何を加えるか。義務が必要とするレベルで署名できるように。
  • How signatures sit in a PDF — 署名を文書ごとにするバイト範囲とディクショナリの基盤。
  • HSM-backed signing — 署名マテリアルがハードウェアに存在するとき、秘密鍵の境界がどこに位置するか。
  • Stream (Pro) — 単一の署名ユニットを再開可能な実行へと変える、堅牢・並行・正確に一度きりのレンダリングエンジン。
  • バッチ署名(Batch signing) — 多数の文書をスケジュールに従って署名すること。スケジューリングの概念であり、各文書は依然としてそれ自身のバイトに対して個別に署名されます。
  • フェイルクローズド(Fail-closed) — そのまま進めば未署名または誤った出力を生むであろう失敗が起きたとき、パイプラインは文書を引き留めて報告し、素のバイトとして引き渡しません。
  • 正確に一度きりのコミット(Exactly-once commit) — 正しく署名された出力が一度だけ公開され、クラッシュした実行が再開しても再送されない、堅牢パイプラインの性質。
  • チェックポイント(Checkpoint) — 何がコミットされたかについての文書ごとの堅牢な記録。すべてを署名し直すのではなく、止まったところから実行を続けられるようにします。
  • CMS SignedData — コンテンツに対する署名のための暗号コンテナ(複数の署名者を携えられます)。このパイプラインは文書ごとに 1 人の署名者の PDF 署名を生成します。それがバッチが生み出す文書ごとのユニットです。
  • PAdES — PDF Advanced Electronic Signatures。PDF 署名のための ETSI EN 319 142 プロファイルファミリー。そのベースラインレベルは B-B から B-LTA まで続きます。