コンテンツにスキップ

署名を適切に検証する

Spec: RFC 5280, §6 Spec: RFC 6960 Spec: RFC 5652 Evidence: Test-backed

「署名は有効です」とは通常、確認されたのがただ一つ、つまり数学的な計算が合っていたことだけだという意味です。正しい検証は少なくとも 5 つの独立した事項をチェックし、そのうち一つでも失敗すれば、緑色のチェックマークは無意味なものになり得ます。このページではチェックの全体像を示し、部分的な答えがなぜ危険なのかを説明します。

この領域で最も危険な出力は、単一の真偽値です。それは読み手に「有効」を「信頼できる」と同一視させがちですが、「有効」が意味しているのは、3 年前に期限切れになり、先月失効し、あなたが認識するどの認証局にも連鎖しない証明書の鍵によって、バイト列が改変されていなかったということだけかもしれません。これらはいずれも個別のチェックです。単一の真偽値を報告するソフトウェアは、どのチェックが重要かを暗黙のうちに決定しており、しかもそれをあなたに代わって決定しているのです。規制対象または契約上の場面では、ツールが最も安価に検証できる性質しか確認していなかった場合、「ツールが有効だと言った」ことは弁明になりません。

完全な検証は、5 つの個別の問いに答えます。これらは独立しており、一つに合格しても、ほかの問いについては何も語りません。

  1. 完全性 — 署名されたバイト列は、今なお署名が対象とする値へとハッシュ化されるか。(バイト範囲のダイジェストを再計算し、比較する。)
  2. 真正性 — 暗号署名は、署名対象属性 を対象として、署名証明書内の公開鍵に対して検証できるか。
  3. 証明書パス — その証明書は、連鎖の各リンクが有効な状態で、あなた が選んだトラストアンカーへ連鎖するか。
  4. 時刻 — 証明書は 該当する時点 においてその有効期間内にあったか、そしてその時刻は自己主張ではなく信頼できるものか。
  5. 失効 — 証明書はその時点で、あなたが実際に取得できる証拠、あるいは埋め込まれた証拠(OCSP/CRL)によって、失効していなかった か。

5 つすべてを実行せずに返される「有効」は、完全な答えのように見える不完全な答えです。

NextPDF の立場は、各問いが別個のものであり、それぞれに明示的に答えなければならないというものです。問いが一つの楽観的なフラグにまとめられることは決してなく、チェックが面倒だからといって暗黙のうちにスキップされることも決してありません。これはテストによって強制されています。このページが standard-backed ではなく test-backed と記されているのはそのためです。つまり、その挙動は条項から説明されているだけでなく、テストスイートによって固定されているのです。

完全性と真正性はエンドツーエンドでテストされています。既知の証明書が実際の署名対象属性構造に署名し、テストスイートは複数の時刻ベクトルにわたって、対応する公開鍵でその署名を検証します。したがって、正しい構造を壊す変更はテストを失敗させます。証明書パス検証は、署名のバイトを意図的に改ざんし、結果が構造化された理由とともに有効で ない ことを表明するテストによって固定されています。これは捨てられる例外ではなく、明示的に記録された失敗です。タイムスタンプトークンの検証は、デコード、署名者情報、署名対象属性、メッセージダイジェスト、証明書バインディング、鍵用途、署名、生成時刻という個別のステップに分解され、各ステップが単独でテストされます。したがって「タイムスタンプが検証された」とは、すべてのステップが検証されたことを意味します。失効チェックのソフト失敗(到達不能なレスポンダ)は、コード上でもテスト上でも、確定的な「失効」とは区別されます。この 2 つが同じ答えへと混同されることは決してありません。

  1. Integrity Recompute the byte-range digest and compare it to the value the signature covers.
  2. Authenticity Verify the cryptographic signature against the certificate’s public key, over the signed attributes — not the raw content.
  3. Certificate path Build and validate the chain to a trust anchor you chose; every link’s signature, validity, and constraints must hold.
  4. Time Confirm the certificate was valid at the relevant instant, and that the instant is trusted time, not the signer’s clock.
  5. Revocation Confirm the certificate was not revoked at that time, using obtainable or embedded OCSP/CRL evidence.
正しい PDF 署名検証が順番に答える、5 つの独立した問い。それぞれは別個のものです。一つに合格しても他について何も語らず、いずれか一つをスキップすると全体の結果は不完全になります。

Evidence: Test-backed この挙動はテストによって固定されており、 それらのテストは標準が要求する内容を実装しています。

完全性は Spec: ISO 32000-2, §12.8.1 に基づきます。ダイジェストはバイト範囲にわたって再計算され、格納された値と比較されます。差異があれば、署名は無効です。署名対象属性 に対する真正性は、実際の署名対象属性セットに署名し、複数の時刻ベクトルにわたって対応する公開鍵でそれを検証する統合テストによってカバーされています。証明書パスの問いは Spec: RFC 5280, §6.1 に基づきます。有効なパスはトラストアンカーから始まり、 Spec: RFC 5280, §6.2 は、そのアルゴリズムがパスを有効と見なすための 最小限 の条件を定義していると述べています。パス検証器の単体テストでは、改ざんされた署名が、明示的な理由とともに valid = false を返し、決して暗黙のうちに受け入れないことを表明します。

失効チェックの順序は Spec: RFC 6960, §3.2 に基づきます。クライアントが署名付きの失効応答を有効として受け入れる前に、応答そのものの署名が有効であり、署名者が現在認可されていることを確認しなければなりません(SHALL)。そして Spec: RFC 6960, §4.2.2.2 は、その認可を、当該 CA によって直接発行された id-kp-OCSPSigning の委任として定義します。したがって、それ自体が、認可済みで検証可能な署名者に対して検証されていない失効の答えには意味がありません。証明書バインディングのチェックは Spec: RFC 5035, §5.4.2 に基づきます。署名された signing-certificate-v2 属性内の証明書ハッシュが、署名の検証に使用された証明書と一致しない場合、その署名は無効と見なさなければなりません。これにより、署名が攻撃者の選んだ証明書に対して検証されてしまう、すり替えの隙が塞がれます。タイムスタンプトークン自体は、 Spec: RFC 5652 方式で CMS オブジェクトとして、ステップごとに検証され、各ステップが単独でテストされます。

重要なのは、API 呼び出しではありません。それは、結果に基づいて行動する前に 答えられなければならない問い です。これを、レビューで確認を求められるチェックリストとして扱ってください。

<?php
declare(strict_types=1);
// A correct validation produces a structured outcome, not one boolean.
// Before you trust a signature, you must be able to answer ALL of these:
//
// integrity : Does the byte-range digest still match? (tamper check)
// authenticity: Does the signature verify over the SIGNED ATTRIBUTES,
// not just the content?
// path : Does the certificate chain to a trust anchor YOU chose,
// with every link valid at the relevant time?
// time : Is the relevant time TRUSTED (a timestamp), or merely the
// signer's self-asserted clock?
// revocation : Was the certificate not revoked at that time, by evidence
// you obtained or that the document embedded?
//
// "valid: true" without an answer to every line above is an incomplete
// result. A path-validation outcome carries a `valid` flag AND a structured
// `reasons` list precisely so a failure says WHY — never a bare false.

いずれかの行が「わからない」であれば、正直な状態は「有効」ではありません。それは「まだ確定していない」のです。そして、この 2 つを同じものとして扱うことこそ、このページが防ごうとしている誤りです。

よくある罠は、「暗号的に有効」を「信頼できる」と同一視することです。完全性と真正性を合わせても、証明されるのは これらのバイト列がこの鍵の保持者によって署名された ことだけです。それらは、その鍵の証明書が信頼されていたか、有効期間内であったか、失効していなかったかについては何も語りません。自己生成した証明書で署名された文書は、「暗号的に有効」でありながら、まったく価値のないものになり得ます。逆の罠は、不確定 な失効チェック(レスポンダがオフライン)を 合格 として、あるいは 不合格 として扱うことです。それはどちらでもありません。不明であり、正しい検証器はどちらかに決め打ちするのではなく、不明として報告します。5 つのチェックのうちどれが実際に実行されたかを隠す緑色のチェックマークは、検証結果ではありません。それは、他の誰かがあなたに代わって下した決定です。

NextPDF は構造面および暗号面のチェックを実行し、テストします。NextPDF はあなたのトラストアンカーを選択することも、その上に成り立つポリシーを保証することもしません。どの 証明書を信頼するかは、エンジンが下せるものではなく、デプロイ上の決定です。信頼すべきでなかったアンカーまで検証が通る連鎖は、それでも依拠できる検証にはなりません。失効の証拠は、取得可能であるか埋め込まれている場合にのみチェックできます。オフラインのレスポンダは「不確定」をもたらし、それを判定に変えるのは、エンジンではなくポリシーの選択です。このページが説明するのはチェックの全体像であり、法的な十分性ではありません。検証済みの署名が特定の法的効力を持つかどうかは、証明書、署名者、管轄区域、そして義務に依存します。埋め込まれた証拠が、これらのチェックに長期にわたって答えられる状態を保つ方法は、長期検証 で扱っています。完全性チェックの背後にあるバイト範囲の仕組みは、署名が PDF 内でどこに置かれるか にあります。

ティア別の検証機能の提供状況:

Signature validation checks — edition availability
Edition Availability
Core

署名対象属性に対する完全性と真正性、加えて RFC 5280 §6 の指定されたトラストアンカーまでの証明書パス検証。

Pro

RFC 3161 タイムスタンプトークンの検証を追加します。すなわち信頼できる時刻の問いを、 独立してチェックされるステップに分解したものです。

Enterprise

失効評価(OCSP/CRL)と、埋め込まれた長期マテリアルに対する検証を追加し、不確定な結果を確定的な結果から区別します。

  • 完全性チェック — バイト範囲のダイジェストを再計算し、署名が対象とする値と比較すること。
  • 真正性チェック — 署名対象属性を対象として、署名証明書の公開鍵に対して暗号署名を検証すること。
  • 署名対象属性 — 署名が実際に対象として計算される、認証済みの CMS 属性(content-typemessage-digestsigning-timesigning-certificate-v2)。
  • 証明書パス検証 — 署名証明書から選択されたトラストアンカーまでの連鎖を構築し、チェックすること(RFC 5280 §6)。
  • トラストアンカー — あなたが信頼すると決めた認証局。許容できるパスのルート。
  • 失効チェック — OCSP または CRL を通じて、証明書が該当する時点で失効していたかどうかを判定すること。
  • 不確定 — 証拠を取得できなかったために「good」でも「revoked」でもない失効の結果。合格でも不合格でもありません。