GPKI/eIDAS 対応 高度電子署名(AdES)検証器
本章では、pverify とは何か、それが何を検証し、また何を意図的に拒否するのかという正確な境界、対象とする読者層、リリース v1.3.0 時点で実際にサポートする入力フォーマットおよび高度電子署名(AdES)レベル、そしてその後のあらゆる設計判断を規定するアーキテクチャ上の立場——pure Rust でビルドし、WASM ビルド可能な検証カーネル——を確立する。章の末尾では、残りの各章への読者向けガイドを示す。
本章における主張は、リポジトリルートのソースツリーに根拠を置く。具体的な証拠は crates/<crate>/src/<file>.rs の形で引用する(論点を明確にする場合は関数または行番号を付す)。ツールの挙動が微妙な箇所——とりわけ、合格や失敗を捏造するのではなく INDETERMINATE へと縮退させる断定回避の原則——については、理想化された要約ではなく、コードが実際に行っていることを記述する。
pverify は、高度電子署名(AdES)を検証する Rust ツールキットであり、観測した事実の構造化記録を出力する——「有効/無効/適格」といった業務上の判定を下すことは決してない。これは本プロジェクトの第一かつ譲歩不能の原則であり、憲法(Constitution)において原則 I、*事実報告(Fact-Reporting)*として批准されている(.specify/memory/constitution.md、"Core Principles / I. Fact-Reporting (NON-NEGOTIABLE)"):
pverify は観測した事実の構造化レポートを出力するのであって、「適格電子署名」や「法的に有効」といった業務上の判定を出力することは決してない。
- 出力 JSON は、参照したすべての成果物を列挙しなければならない(MUST): 証明書チェーンの構成員、取得した CRL/OCSP レスポンス/TSA トークン、それらの出所、および各々に対して実施した評価。
- ETSI EN 319 102-1 のインディケーション(
TOTAL_PASSED/INDETERMINATE/TOTAL_FAILED)は機械可読な指標として出力してよい(MAY)が、pverify はそれを領域上の判定へ翻訳してはならない(MUST NOT)。- pverify は、法的有効性、規制上の適格性、または証拠能力を主張するいかなるテキストまたはフィールドも出力してはならない(MUST NOT)。
その理由づけは憲法に明示されている。署名の適格性は、保存期間、署名時刻と検証時刻との関係、アーカイブタイムスタンプの要件、および検証者(信頼当事者)ごとに異なる相手方との合意に依存する。これらの懸念事項を先回りして判断するツールは、誤用を強いることになる。したがって pverify は観測したこと——どの証明書をどのチェーンに組み込んだか、どの CRL および OCSP レスポンスをどの出所から参照したか、どのタイムスタンプをどのインプリントに対して再計算したか——を報告し、法的・業務的な判定は利用者に委ねる。
検証実行ごとの出力は、クローズドスキーマの JSON 文書である。署名ごとの最上位の機械可読判定は、ETSI EN 319 102-1 インディケーションであり、TOTAL_PASSED、INDETERMINATE、TOTAL_FAILED のいずれかである。これは crates/pverify-core/src/report/etsi.rs のクローズド列挙型 Indication(24 行目で宣言)で表現される。これは、単一のクローズド列挙型 SubIndication(同ファイル 39 行目)から引かれる精密な機械可読のサブインディケーションによって限定され、ルートの report-schema.json に 1:1 でミラーされる。自由形式の判定文字列は構造上禁止されている: サブインディケーション値の追加は追加的な MINOR スキーマ変更であり、いずれかの削除または転用は MAJOR 変更である。
JNSA デジタル署名検証ガイドライン(第 1.1 版)の判定語彙と pverify の ETSI インディケーションとの関係は docs/jnsa-conformance-statement.md に記載されている。高レベルのマッピングは VALID → TOTAL_PASSED(pverify が実装したチェックセットの範囲内)、INVALID → TOTAL_FAILED、INDETERMINATE → INDETERMINATE である。当該適合性ステートメントが正規のクロスウォークであり、本文書はそれを再掲しない。
pverify を特徴づける挙動の一つであり、いかなるレポートを読む前にも専門の監査人が体得すべきものは、ツールが立証できない判定を決して捏造しないことである。pverify が事実を確立できない場合、それは TOTAL_FAILED(改ざんや失効を誤って主張することになる)や TOTAL_PASSED(信頼を誤って確証することになる)をでっち上げるのではなく、精密なサブインディケーションを伴って INDETERMINATE へと縮退する。この原則の具体的な現れは次のとおりである:
INDETERMINATE / signature_algorithm_unsupported を生じる——これにより、利用者は「未対応」を暗号学的危殆化と混同することなく grep で抽出できる。INDETERMINATE における revoked_no_poe サブインディケーションを生じる(ブランチ 033 で導入)。これは、失効した TSA 証明書がそれ自体で保護対象の署名が偽造であることを証明するものではない——失効以前に確立された存在証明(PoE)が存在しうる——ことを反映している。INDETERMINATE / cms_multi_signer_unsupported で拒否される(crates/pverify-core/src/verify.rs、verify_cades、約 497 行目が 1890 行目の cades_multi_signer_entry を呼び出す)——暗黙の単一署名者レポートは誤解を招く確証となるためである。IndeterminateRevocationOffline / revocation_not_checked_offline を生じる(オフラインの誠実性の原則、憲法 §VII / §I)。INDETERMINATE における crypto_constraints_failure_no_poe を生じ、ETSI の CRYPTO_CONSTRAINTS_FAILURE_NO_POE を反映する。これが TOTAL_FAILED へエスカレートすることは決してない。この原則こそが、pverify のレポートが監査可能である理由である: あらゆる INDETERMINATE は、どの事実が確立できなかったかを正確に指名するクローズド列挙型の理由を伴う。サブインディケーション語彙の全体については第14章(Result Model: インディケーション、診断データおよびスキーマ)を、優先順位規則については第5章 §5.7 と第14章(ETSI インディケーション集約)を併せて参照のこと。
pverify は、ビジョンステートメント(docs/vision-statement.md §3)に定めるとおり、同心円状の三つの読者層に供する:
第一義的——統合する開発者。 pverify-core を組み込み、GPKI 署名検証を自社製品に追加する、電子申請 SaaS プロバイダ、自治体ベンダ、文書管理ベンダのバックエンドエンジニア。ライブラリファーストの設計(憲法 §V)は彼らのために存在する: pverify-core は I/O を行わない no_std + alloc クレートであり、検証ロジックを変更することなく、ネイティブバイナリ、WebAssembly モジュール、または Cloudflare Worker から駆動できる。
第二義的——検証オペレータおよび監査人。 CLI を直接呼び出して単発の検証や監査対応を実行する、自治体、監査法人、公文書の管理者。再現性(憲法 §II)および監査可能性(§III)の保証は彼らのために存在する: --from-bundle の呼び出しは、コンテンツアドレス指定されたバンドルから数週間後に検証を再実行し、バイト単位で同一のレポートを生成する。
第三義的——研究者および標準化参加者。 pverify を ETSI EN 319 102-1 および RFC 5280 §6 のリファレンス実装としてベンチマークやテストベクタの検証に用いる者。
包括的な顧客文脈は、日本の政府認証基盤(GPKI)である: pverify は、GPKI 署名された CAdES/PAdES/XAdES 文書——省庁間または政府対民間の信頼のために GPKI のブリッジCA構造を経由する署名を含む——を受領する検証者(信頼当事者)が、共通の、堅牢かつ再現可能な検証基盤を持てるように構築されている。同時に、欧州の eIDAS / ETSI 体系(EU トラステッドリスト、CAdES/PAdES/XAdES/JAdES/ASiC のベースラインレベル)も認識しており、同一のエンジンが GPKI/JNSA プロファイルと eIDAS プロファイルの双方を検証する。
pverify は五つの AdES 署名ファミリを検証し、そのうち三つは複数のベースラインレベルにわたる。サポートする範囲は意図的に誠実である: フォーマットのバリアントや AdES レベルが現行実装を超える場合、ツールは推測するのではなく、INDETERMINATE におけるクローズド列挙型のサブインディケーションで拒否する。
検証カーネルは、入力を正しいフォーマット別検証器へ振り分けるために、先頭バイトに対するコンテンツスニフを行う(crates/pverify-core/src/verify.rs、detect_format、359 行目):
| 先頭バイト | 検出されるフォーマット | 標準 | 検証器 |
|---|---|---|---|
%PDF- |
PAdES | EN 319 142 / RFC 5652(埋め込み CMS) | pades::verify_pdf |
BOM/<?xml/裸の <name |
XAdES | EN 319 132 | xades::verify_xades |
先頭の {(任意の BOM の後) |
JAdES | TS 119 182 / RFC 7515(JWS) | jades::verify_jades |
PK\x03\x04(ZIP マジック) |
ASiC | EN 319 162 | asic::verify_asic_signature |
| それ以外 | CAdES | EN 319 122 / RFC 5652 | verify_cades |
ASiC の ZIP マジックの場合は、バイトスニフの前に処理される: ホストが ASiC コンテナ署名を抽出していれば、verify_with は無条件に ASiC オーケストレータへ振り分ける。これは、コンテナバイトが ZIP マジックで始まり、そのままでは CAdES へフォールスルーしてしまうためである(crates/pverify-core/src/verify.rs、verify_with、約 195 行目: if !request.asic_signatures.is_empty())。認識可能な ASiC コンテンツを持たない裸の ZIP は INDETERMINATE / asic_unsupported_container を生じる(約 225 行目)。XML 対 JSON のスニフは構造上衝突しない: XAdES 入力は < で始まり、JAdES 入力は { で始まる(356 行目、"JSON and XML are mutually exclusive on the first non-whitespace byte")。
ETSI のベースラインレベル語彙(B-B/BES、B-T、B-LT、B-LTA)を正確に用い、ツールはフォーマットごとに実際にどのレベルを検証するかを明示する。
| フォーマット | B-B (BES) | B-T | B-LT | B-LTA | 備考 |
|---|---|---|---|---|---|
| CAdES(EN 319 122) | ✓ | ✓ | ✓ | ✓ | 完全な梯子。B-T = signature-time-stamp; B-LT = certificate-values + revocation-values; B-LTA = §6.3.4 インプリント再計算を伴う archive-time-stamp-v3。 |
| PAdES(EN 319 142) | ✓ | ✓ | ✓ | ✓ | 埋め込み CMS に加え /DocTimeStamp 上。ブランチ 034 時点で、オフラインで確定的な LT/LTA のために文書レベルの /DSS(/Certs,/CRLs,/OCSPs)、/VRI、および Adobe revocationInfoArchival を消費する。 |
| XAdES(EN 319 132) | ✓(enveloped) | ✓ | ✓ | ✓ | enveloped + Exclusive C14N のみ。B-LTA = xades:ArchiveTimeStamp インプリント検証(ETSI TS 101 903 v1.4.2 Annex A.1.5)。exc-C14N による ds:Signature 全体から後続 ArchiveTS の node-set を subtract(ブランチ 036)。detached/enveloping のパッケージングおよび non-exclusive C14N は INDETERMINATE へ縮退する。 |
| JAdES(TS 119 182) | ✓(JWS-JSON) | ✓ | ✓ | ✓ | JWS JSON Serialization。B-T(sigTst)、B-LT(xVals/rVals)、B-LTA(arcTst)を unprotected header から解析し既存の VRT/失効パイプラインへ接続(ブランチ 037)。sigD および Compact シリアライゼーションは明示的に拒否される。 |
| ASiC(EN 319 162) | 委譲 | 委譲 | 委譲 | 委譲 | ASiC-S / ASiC-E; 各内部署名は CAdES または XAdES パイプラインへ委譲される。 |
CAdES のフォーマット昇格梯子は crates/pverify-core/src/verify.rs の 1034〜1042 行目に実装されている: archive-time-stamp が存在すればワイヤフォーマットを CAdESLta へ昇格させる; さもなくば任意の LT/LTA ペイロードが CAdESLt へ昇格させる; さもなくば signature-time-stamp が CAdEST へ昇格させる; さもなくばフォーマットは CAdESBes である。
B-LTA / アーカイブ機構はフォーマットによって異なり、正確に名付けられている: CAdES は archive-time-stamp-v3(ETSI EN 319 122-1 §6.3.4、インプリント再計算を伴う; 不一致は archive_timestamp_imprint_mismatch、TOTAL_FAILED)を用い、PAdES は /DocTimeStamp(リビジョンの ByteRange 上の RFC 3161 トークンを担う PDF /Type /DocTimeStamp 辞書であり、pades_doc_timestamp という VRT 種別のタイムスタンプとして表出される)を用い、XAdES は xades:ArchiveTimeStamp(ETSI TS 101 903 v1.4.2 Annex A.1.5; インプリント = ds:Signature 要素全体の exc-C14N から同一署名内の後続 ArchiveTimeStamp が覆うノードセットを subtract したもの——ブランチ 036 で XadesUnverifiedTimestamp.imprint_input: Vec<u8> および bergshamra-c14n の node-set subtract API を用いて実装、SignatureFormat::XadesBLta を新設)を用いる。
誠実な拒否はレポートの第一級の結果であり、各々がクローズド列挙型の理由を伴う:
XadesProfile::Unsupported(理由 DetachedOrEnveloping または NonExclusiveC14n、crates/pverify-core/src/xades.rs、151 行目の UnsupportedReason)にマップされ、INDETERMINATE / xades_unsupported_profile(または正規化に固有のサブインディケーション)として表出する。一つの例外: ASiC で解決された detached XAdES は検証される。これは ASiC コンテナが解決済みのデータオブジェクトバイトを供給するためである(約 133 行目)。サポートされる形態は enveloped + Exclusive C14N(W3C exc-c14n)であり、監査済みの bergshamra-c14n クレートが提供する。JadesUnsupportedReason を介して行われる: DetachedSigD(JWS sigD detached プロファイル)、CompactSerialization(JWS Compact)、HigherLevel(rfsTst/tstVd のみ——ブランチ 037 で B-T/B-LT/B-LTA を実装済み)、NotJadesBaseline。cms_multi_signer_unsupported で拒否される(§1.1.1 を参照)。asic_unsupported_container を生じる。証明書、CRL、OCSP、TSA、および署名者の署名は、すべて RustCrypto のみのプリミティブを通じて検証され、crates/pverify-core/src/crypto.rs(verify_with_alg)において OID と SPKI 形状によってディスパッチされる。サポートする署名ファミリは次のとおりである:
verify_rsa_pss、151 行目を介して検証される; 約 284 行目の CMS OID テーブルは意図的に PSS を拒否するため、PSS は専用の PSS パスのみを流れる)。1.3.101.112)。2.16.840.1.101.3.4.3.17/18/19 の ML-DSA-44/65/87、216 行目の MlDsaParams)——先取り的なポスト量子ディスパッチ。このセット外の署名アルゴリズムはすべて、INDETERMINATE における signature_algorithm_unsupported として表出する。本番の依存セットには署名鍵生成の面は存在しない——暗号は検証専用である。
憲法 §IV(Specification-Compliance、NON-NEGOTIABLE)に従い、実装は以下を主要な規範的参照として扱い、いかなる逸脱も該当するフィーチャ仕様に書面の理由づけとともに列挙しなければならない:
pverify-eutl により取り込まれる)。pverify は意図的に境界づけられている。以下は v1.x 系列のスコープ外であり、ビジョンステートメント §6 から引き、事実報告の原則によって補強される:
サポートするファミリ内のフォーマットレベルの除外も同様に明示的であり、クローズド列挙型の拒否として表出される(暗黙の合格ではない): より高位の JAdES ベースラインレベル、detached/enveloping XAdES、non-exclusive C14N、複数署名者 CMS、およびサポートセット外のいかなる署名アルゴリズム(§1.3.3、§1.3.4)。ブランチ 034 の PAdES /DSS の作業も、DSS の生成、DSS /Certs のトラストアンカーへの昇格、VRI /TU//TS の内部整合性の検証、および otherRevInfo フィールドを明示的に除外する——これらは設計上スコープ外である。
pverify における最も重大な単一の設計判断は、I/O を行わない、no_std + alloc の、WASM ビルド可能な検証カーネル(pverify-core)と、すべてのコンテナ/文書のパース、ネットワーク取得、ファイルシステムアクセス、およびクロック読み取りを行うホスト側クレート群との境界である。これは美的な好みではない; 憲法と経験的な CI ゲートによって強制される。それが供する目標は次のとおりである:
pverify-core は、すべての暗号、RFC 5280 §6 パス検証、失効評価(CRL/OCSP)、タイムスタンプと VRT の導出、アルゴリズムポリシー評価、および ETSI インディケーション集約を行う。当該クレートは crates/pverify-core/src/lib.rs の冒頭(11〜12 行目)で #![no_std] と #![forbid(unsafe_code)] を宣言し、alloc クレートのみを取り込む(15 行目)。カーネルが自力で行えない三つのホスト関心事——検証クロック、失効情報、およびトラストアンカーセット——はトレイト経由で媒介される: crates/pverify-core/src/traits.rs 内の Clock(76 行目)、RevocationFetcher(98 行目)、TrustAnchorStore(139 行目)。カーネルはネットワーク、ファイルシステム、または環境クロックへのアクセスを一切行わない; その derive_vrt および derive_validation_objects 関数は純粋計算である。
PDF、XML、ZIP、JSON は、重量級でしばしば unsafe を含み WASM クリーンでない依存を信頼コンピューティングベースへ引き込むことなしには no_std カーネル内でパースできないため、すべてのコンテナ/文書のパースはホストクレートで行われ、追加的な VerificationRequest フィールドを介してモデルに依存しないバイト構造をカーネルへ供給する(crates/pverify-core/src/verify.rs、69 行目以降: pdf_signature_dicts、pdf_validation_data、xades_components、jades_components、asic_signatures)。カーネルは、スニフされたフォーマットに一致するフィールドのみを参照する; 非 PDF 入力は空の pdf_validation_data を担う、等々。この境界は scripts/cargo-tree-gate.sh によって経験的に強制される。同スクリプトは cargo tree -e normal --no-default-features -p pverify-core を実行し、lopdf、zip、flate2、bergshamra-c14n、pverify-eutl、pverify-aatl、またはいずれかのホスト専用クレートがコアグラフに現れた場合にビルドを失敗させる。
pverify-core は wasm32-unknown-unknown 上でコンパイルおよび実行できなければならない。暗号は RustCrypto のみである: ring、aws-lc-rs、OpenSSL FFI、および rustls のデフォルトプロバイダは、コアまたは WASM グラフのいかなる箇所においても禁止される。これは cargo build --target wasm32-unknown-unknown -p pverify-core と cargo-tree ゲートによって経験的にゲートされる。pure Rust 暗号の立場は、先行プロジェクト civ(ビジョンステートメント §8.5)から継承されたものであり、同一のカーネルを CLI から Cloudflare Worker、ブラウザ内 WASM モジュールまで実行可能にしているものである。
ネイティブ CLI(crates/pverify-cli)とブラウザ WASM ホスト(web/pverify-wasm)は、バイト単位で同一のホスト抽出器と同一の verify_with カーネルを実行し、同一の入力に対してバイト単位で同一のレポートを生成する。PAdES については、ネイティブ PDF パース(pverify-cli/src/pdf.rs、lopdf)とブラウザ内パース(web/pverify-wasm/src/lib.rs、lopdf)が同一の PdfSignatureDescriptor および同一の /DSS+/VRI 検証情報を抽出する; このパリティは、抽出器に対するパリティテストで担保される強制された不変条件である。
同一の入力(署名文書、トラストアンカー、検証時刻、および失効情報)に対して、pverify はいつどこで実行されてもバイト単位で同一の出力を生成する。検証時刻は --at <RFC3339> を介して注入可能であり、さもなくば CLI 起動時に一度だけ捕捉される; VRT エンジンはこの request_at 値のみを時刻源として読み取り、Clock::now を決して読まない。追加的なフィーチャを統べるさらなる不変条件: 追加的なフィーチャは、その入力が不在であるか無効化されている場合、schema_version 文字列を除いてレポート本体をバイト単位で同一に保たねばならない。例えば、/DSS を持たない PDF は、schema_version を除いて 034 以前の出力と同一のレポートを生成する(FR-008); アルゴリズムポリシーは、オフ(デフォルト)のとき signatures[].algorithm_validity を不在にする; また埋め込み失効のフォールスルーは、ライブ/CDP チャネルが不確定な場合にのみ発火するため、オンラインの成功はバイト単位で同一のままである。
レポート形状は SemVer 文字列 SCHEMA_VERSION を担い、現在は "1.10.0" である(crates/pverify-core/src/report/schema.rs)。スキーマバージョニングの規律は厳格かつ追加的である: SubIndication、RevocationOutcome、および ValidationObjectOrigin 列挙型とレポート構造体はクローズドであり、ルートの report-schema.json に 1:1 でミラーされる。クローズド列挙型の値またはオプショナルフィールドの追加は MINOR バンプであり、いずれかの削除または転用は MAJOR である。非推奨となったバリアント(例: 歴史的な bridge_ca_required_unsupported)は、保存済みレポートのデシリアライズのために保持されるが、もはや出力されない。例えばブランチ 034 は、既存のバリアントの後に二つの ValidationObjectOrigin バリアント——PdfDss と PdfVri(crates/pverify-core/src/report/validation_objects.rs)——を、宣言順=ソート順として追加し、スキーマを 1.5.0 → 1.7.0 へとバンプした。続く追加的スライスも同じ規律を継続し、ブランチ 035 が OcspAttempt.request_der_hex 追加(1.7.0 → 1.8.0)と CdpEntry 構造化(1.8.0 → 1.9.0)を行い、ブランチ 037 が JAdES-B-T/JAdES-B-LT/JAdES-B-LTA など 3 つの新 SignatureFormat 値を追加(1.9.0 → 1.10.0)した。
README の開示ブロックに関する注記。リポジトリの
README.mdは v0.7 開発系列に固定された歴史的成果物である; その本文はschema_versionが"1.1.0"のままであり、能力フェーズが"v0.6-bridge-acceptance"であると述べている。正規の現行値はreport/schema.rs内のSCHEMA_VERSION定数("1.10.0")であり、そのドキュメントコメントが1.0.0から1.10.0までの各バンプを叙述している。読者は、現行リリースについては README ではなくソース定数を規範として扱うべきである。
以下の不変条件は設計文書全体を通じて参照される; 後続の各章が名前で引用できるよう、ここで一度列挙する。各々はコードと憲法に根拠を置く。
| 不変条件 | 一行ステートメント | 典拠 |
|---|---|---|
| 事実報告/断定回避 | 事実を報告し、業務上の判定を決して下さない; 事実を確立できない場合はクローズド列挙型のサブインディケーションを伴って INDETERMINATE へ縮退する。 |
憲法 §I(NON-NEGOTIABLE) |
| WASM クリーンなコア | pverify-core は no_std + alloc、unsafe_code を禁止、RustCrypto のみ、wasm32-unknown-unknown 向けにビルドする。 |
憲法 §VI; cargo-tree-gate.sh |
| コアでパースしない | すべての PDF/XML/ZIP/JSON のパースはホスト側; カーネルはモデルに依存しないバイト構造を受け取る。 | 憲法 §V; ゲートで担保 |
| トレイト経由の I/O | クロック/失効/アンカーが唯一のホスト結合であり、Clock / RevocationFetcher / TrustAnchorStore を介する。 |
crates/pverify-core/src/traits.rs |
| 追加的なバイト同一性 | 追加的/オフのフィーチャは、schema_version を除いてレポート本体をバイト単位で同一に保つ。 |
憲法 §II(例: FR-008) |
| オフラインの誠実性 | --offline はソケットを一切開かず、レスポンダ接触を決して捏造しない。 |
憲法 §VII / §I |
| CLI と WASM のパリティ | ネイティブとブラウザのホストは同一の抽出器と同一のカーネルを実行する。 | パリティテスト |
| インディケーションの優先順位 | 重大度順のベース(TOTAL_FAILED > INDETERMINATE > TOTAL_PASSED、最初に見つかったものが勝つ); レイヤは縮退させるのみで、証明された TOTAL_FAILED を決して覆い隠さない。 |
aggregate_etsi, verify.rs |
| オブジェクトごとの VRT 分離 | 各オブジェクトは、純粋な再帰的な外側カバーエンジンによって、自身の導出された検証基準時刻で判定される。 | crates/pverify-core/src/vrt/ |
| クローズドかつ追加的なスキーマ | クローズド列挙型は report-schema.json に 1:1 でミラーされる; 追加 = MINOR、削除/転用 = MAJOR。 |
report/schema.rs, report-schema.json |
本文書は、監査人が上から下へ通読することも、レビュー対象のサブシステムへ直接ジャンプすることもできるよう構成されている。以下の章番号は統合設計仕様を反映する; 後続の各章での相互参照はこれらの番号を用いる。
RevocationOutcome 語彙。/DSS / /VRI / revocationInfoArchival 情報を含む)、XAdES、JAdES、ASiC。各々が、サポートする形態、拒否ケース、およびホスト抽出器を記述する。aggregate_etsi)、validation_objects インベントリとその ValidationObjectOrigin 出所タグ、およびスキーマバージョニング規則。cargo audit/cargo vet、perf-gate、cargo-tree ゲート)、および再現性/バイト単位の同一性の保証。本章が挙動(断定回避の原則、フォーマットの梯子、トレイト境界)を要約した箇所では、対応するサブシステムの章がフィールドレベルの詳細と障害モードの列挙を提供する。特定の判定を監査する読者は、まずレポートスキーマの章から関連するサブインディケーションを特定し、次いでその相互参照をたどってそれを生成するサブシステムへ進むべきである。
本章は本仕様書の残りの部分に対する規範的なアンカ(基準点)である。後続の各章 が特定のクレートやコードパスが何を行うかを記述するのに対し、本章はそれが なぜ許容されるのか——そして同程度に重要な点として、何を行うことが禁止されて いるか——を確立する。統制する文書は .specify/memory/constitution.md にあるプロ ジェクト憲法(バージョン 1.2.0、2026-06-10 批准、最終改正 2026-06-22)であ る。憲法はその場限りの慣習、README、レビュアの選好に優先する。「両者が矛盾する 場合は憲法が優先する」(Governance 節)。以下に引用する各原則は、当該ファイル から逐語的に再録し、そこで付されている条番号を併記する。
憲法は 7 つの Core Principles(中核原則 I–VII)、Technology Stack Constraints(技術スタック制約)節、Phased Path Validation Roadmap(段階的パス検 証ロードマップ)、明示的な Out-of-Scope(適用範囲外)一覧、および Governance/ バージョニングポリシーを定義する。本章の 2.1–2.10 節は、監査人が最も必要とする 順序でそれらの条項を順に辿り、各々をそれを強制するコードに結び付け、その後 2.11 節が(2.12 節が Out-of-Scope の境界を記録するとともに)アーキテクチャ概観(第3 章)と実装各章(第5章〜第13章)が繰り返し依拠する横断的不変条件を要約する。
用語に関する注記: 本書を通じて「カーネル」または「コア」は pverify-core クレ ートを指し、「ホスト」はその代理として I/O を行う任意のクレート(pverify-cli、 web/pverify-wasm、pverify-eutl、pverify-aatl、抽出系クレート群)を指す。 このホスト/カーネル分離を実現するクレートのトポロジは第3章で詳述する。本章は その分離の憲法上の根拠を説明する。
憲法はビジョンステートメントではない。それは独自のセマンティックバージョニング 体制(Governance → Versioning policy)の下にある、バージョン管理された契約である。
- MAJOR: 原則が削除される、非互換に再定義される、もしくは NON-NEGOTIABLE マーカが外される場合。または Out-of-Scope 項目が取り込まれる場合。
- MINOR: 新たな原則もしくは節が追加される場合、または既存の指針が実質的 に拡張される場合。
- PATCH: 明確化、文言、組版、または意味を変えない編集。
各改正は、バージョンの差分、影響を受けた原則、および SemVer 分類の根拠を記録す る「Sync Impact Report」コメントをファイル冒頭に伴う。直近の改正(1.1.0 → 1.2.0) はアルゴリズムポリシーのデフォルトテーブルを批准したものであり、当該レポートは NON-NEGOTIABLE マーカが外されておらず、Out-of-Scope 項目も取り込まれていないこ とを明示している。これがまさに、当該バンプが MAJOR ではなく MINOR である理由で ある。
7 つの原則のうち 3 つは NON-NEGOTIABLE とマークされている。すなわち I(事実 報告)、IV(仕様適合)、および VI(WASM 互換性)である。このマーカは load-bearing (負荷を担うもの)である。すなわち、それを外すことは定義上 MAJOR 改正となる。 憲法はまた、それ自体が憲法上の表面となるいくつかの値を固定している。アルゴリ ズムポリシーのデフォルトテーブル(§2.8)が顕著な例であり、そこではコード定数 pverify_core::algorithm_policy::AlgorithmPolicy::default_policy() が 「specs/032-algorithm-validity/contracts/default-policy.md(test C-3)と バイト単位で一致しなければならない(MUST)」とされている。これがプロジェクト全 体が従うモデルである。すなわち、原則は、それが侵されたときにテストが失敗する場 合に限って実在する。本章の残りの部分は、各原則について、その線を保持するコード とテストを特定する。
原則 I はツール全体の哲学的背骨である。逐語的に引用する。
I. Fact-Reporting(NON-NEGOTIABLE)
pverify は観測した事実の構造化レポートを出力するものであり、「適格電子署 名(qualified electronic signature)」や「法的に有効(legally valid)」とい った業務上の判定を出力することは決してない。
- 出力 JSON は、参照したすべての成果物を列挙しなければならない(MUST)。すな わち、証明書チェーンの構成要素、取得した CRL/OCSP レスポンス/TSA トークン、 それらの出所、および各々に対して実施した評価である。
- ETSI EN 319 102-1 インディケーション(
TOTAL_PASSED/INDETERMINATE/TOTAL_FAILED)は機械可読の指標として出力してよい(MAY)が、pverify はそれ をドメインの判定へ翻訳してはならない(MUST NOT)。- pverify は、法的有効性、規制上の適格性、または許容性を主張するいかなるテキ ストやフィールドも出力してはならない(MUST NOT)。
根拠(Rationale): 適格性は、保存期間、署名時と検証時の別、アーカイブ TS 要件、およびユーザごとに異なる相手方との合意に依存する。これらを先回りして 判定するツールは誤用を強いる。
この原則は、コードベース全体に浸透する 2 つの運用上の帰結を持つ。
レポートはインベントリであって、可否の二択ではない。カーネルは、参照したすべ ての証明書、CRL、OCSP レスポンス、タイムスタンプトークン——各々にその出所(プロ ビナンス)をタグ付けしたもの——から成る統合された資料インベントリを、 derive_validation_objects(crates/pverify-core/src/report/validation_objects.rs) において導出する。当該関数は、完成した &Report ツリーに対する純粋な読み取り (再計算なし)であり、すべてのオブジェクトについて、それがどこから来たかを記録す る。出所の語彙はクローズドな列挙 ValidationObjectOrigin (report/validation_objects.rs、98–118 行)である。すなわち signature_embedded、bundle、fetched_online、trust_anchor、 supplied_input、そして——PAdES /DSS+/VRI の作業のために追加された—— pdf_dss と pdf_vri である。これらのタグの要点は監査可能性(§2.4)にある。 読者は、ある CRL が使用されたという事実だけでなく、それがどこから取得された かをも知ることができる。これはまさに原則 I の「それらの出所」要件である。
第二の、より微妙な帰結は 断定回避の原則である。pverify がある事実を確立でき ない場合、失敗も合格も捏造してはならない(MUST NOT)。それは ETSI EN 319 102-1 の INDETERMINATE へと、精密でクローズドな列挙のサブインディケーションを伴っ て降格する。これは知らないことと答えが否定であると知っていることとの違いで あり、ツールは両者を一緒くたにすることを拒否する。トップレベルのインディケーシ ョンはクローズドな列挙 Indication (crates/pverify-core/src/report/etsi.rs)であり、その限定理由は同ファイル内の クローズドな列挙 SubIndication である。この原則の典型的な実例を以下に示す。
| 状況 | インディケーション | サブインディケーション(serde rename) | 根拠 |
|---|---|---|---|
--offline でキャッシュ済み失効情報がない |
INDETERMINATE | revocation_not_checked_offline |
revocation/mod.rs(IndeterminateRevocationOffline、~L667) |
| マルチサイナの CMS SignedData | INDETERMINATE | cms_multi_signer_unsupported |
verify.rs:497 → cades_multi_signer_entry(verify.rs:1890) |
| 失効した TSA 証明書(033) | INDETERMINATE | revoked_no_poe |
report/etsi.rs:424 |
| オプトインのアルゴリズム制約の失敗(032) | INDETERMINATE | crypto_constraints_failure_no_poe |
report/etsi.rs:399 |
| ブリッジCAパスが必要だが未対応(歴史的事情) | INDETERMINATE | bridge_ca_required_unsupported |
report/etsi.rs:63(デシリアライズのために保持) |
マルチサイナのケースは、この原則の能動的な強制の最も明快な例示である。素朴な 検証器は最初の SignerInfo のみを黙って報告するであろうが、pverify は意図的に これを拒否する。verify.rs:487 のコードコメントがその理由付けを記録しており、 cades_multi_signer_entry は部分的な合格ではなく INDETERMINATE / cms_multi_signer_unsupported のエントリを返す。これは断定回避を実行可能にした ものである。すなわち pverify は、完全に評価していない署名を断言しない。
同様に、失効した TSA 証明書は TOTAL_FAILED の偽造主張にはならない。033 の 作業は revoked_no_poe を導入した。失効したタイムスタンプ局は、そのタイムスタン プの存在証明(PoE)がもはや信頼できないことを意味するため、時刻のアンカが利用 不能となる——これは不確定性であって、署名の偽造が証明されたわけではない。これを TOTAL_FAILED ではなく INDETERMINATE として符号化することは、§I の直接的な適用で ある。「証明済みの失敗を決して覆い隠さない」を強制する集約処理については §2.11 で述べる。
第三の項目——法的有効性、適格性、または許容性の主張をしないこと——は、欠落によ って遵守される。すなわち、レポートスキーマ(report-schema.json、 crates/pverify-core/src/report/ からミラーされたもの)は qualified、 legally_valid、admissible といったフィールドを持たず、インディケーションの 列挙は 3 つの ETSI 値で止まる。根拠条項は憲法としては異例なほど明示的である。 すなわち適格性は「保存期間、署名時と検証時の別、アーカイブ TS 要件、およびユー ザごとに異なる相手方との合意に依存する」。pverify はそれらの判断が拠って立つ事 実を報告する。その判断そのものは行わない。
II. Reproducibility(再現性)
同一の入力(署名文書、トラストアンカー、検証時刻、および失効情報)に対して、 pverify はいつどこで実行されるかにかかわらず、バイト単位で同一の出力を生成し なければならない(MUST)。
- 検証時刻は注入可能でなければならない(MUST)(
--at <RFC3339>)。これは署 名時評価および歴史的評価を支援するためである。- すべてのネットワーク I/O はトレイトによって媒介されなければならない(MUST)。 これにより
--from-bundle呼び出しが、外部呼び出しなしに以前の実行を再現で きるようにする。- 非決定的なデータソース(システムクロック、環境依存の TLS ルート、ランダム化 された順序付け)はレポートに影響を与えてはならない(MUST NOT)。
再現性は、検証レポートを証拠たらしめる性質である。憲法は再現性のタプルを精密 に定義する。すなわち (署名文書、トラストアンカー、検証時刻、失効情報) であ る。このタプルが与えられれば、出力は実時間、ホスト、またはネットワーク到達性に かかわらずバイト単位で同一である。
CLI は起動時に検証時刻をちょうど一度だけ捕捉する(あるいは --at から取得し、 これは常に優先される)。そしてそれを request_at としてスレッドを通す。カーネル は検証ロジックのために実時間クロックを決して読まない—— crates/pverify-core/src/vrt/ 内のオブジェクトごとの VRT エンジンは、基準時刻を request_at および構造内に存在するタイムスタンプトークンのみから導出し、 Clock::now() からは決して導出しない。Clock トレイト (crates/pverify-core/src/traits.rs:76)はホスト側の配管のために存在するので あって、検証の決定のためではない。この分離こそが --at <RFC3339> による歴史的評 価を決定的にするものである。
--from-bundle の再現性カーネルが持つあらゆる外部依存——クロック、失効情報、トラストアンカー——はトレイ トである。すなわち Clock、RevocationFetcher、TrustAnchorStore (crates/pverify-core/src/traits.rs、76、98、139 行)である。カーネルはそれ自 体ではネットワーク、ファイルシステム、クロックへのアクセスを行わない。これが --from-bundle をして、外部呼び出しゼロで以前の実行を再生させることを可能にす る。すなわちバンドルは、元の実行が取得したのと同じ失効情報を、同じ RevocationFetcher トレイト面を通して再供給する。バンドルに対して再実行する独 立した監査人は同一のレポートを得る——これはまさに §III(§2.4)の監査可能性の保証 である。
プロジェクトが積極的に強制する系として、追加的機能の下でのバイト同一性があ る。ある機能が無効化されている場合、またはその入力が存在しない場合、レポート本 体は schema_version 文字列を除いて、直前のリリースとバイト単位で同一でなけれ ばならない(MUST)。具体例を以下に示す。
/DSS を持たない PDF は、034 以前のベースラインと schema_version のみが異な るレポートを生成する(034 の FR-008 要件)。PAdES の検証データフィールドは VerificationRequest 上の追加的な Vec/Default であり、非 PDF および no-DSS 入力に対しては空である。schema_version を除いて 032 以前のレポートとバイト単位で同一の本体を生成する(032 の SC-001 要件、§2.8)。この規律こそ、各追加的スライスが schema_version を MINOR インクリメントで バンプし、共通パスにおいてはそれ以外に何も変わらない理由である。現在のレポート 形状のバージョンは SCHEMA_VERSION = "1.10.0" (crates/pverify-core/src/report/schema.rs:line)である。
憲法は、システムクロック、環境依存の TLS ルート、およびランダム化された順序付け がレポートに影響を与えることを明示的に禁止する。トラストアンカーは明示的に供給 される(--trust-anchors <DIR>、もしくは pverify-eutl/pverify-aatl が通常の アンカーへと取り込んだもの)のであって、OS のトラストストアから読まれることは決 してない。カーネルは no_std(§2.6)であり、したがって環境依存の OS 状態へのア クセスを一切持たない。
III. Auditability(監査可能性)
検証の実行は、使用したすべての証明書、CRL、OCSP レスポンス、および TSA トーク ンを埋め込んだ自己完結型の「検証バンドル」を出力できなければならない(MUST)。 これにより、独立した第三者がバンドルに対して pverify を再実行し、同じレポート を得られるようにする。
監査可能性は、再現性(§II)と事実報告(§I)を組み合わせた運用上の成果である。 カーネルは I/O を行わず、トレイト経由であるため、CLI はオンライン実行中に参照し た成果物の完全な集合を捕捉し、それらをバンドルディレクトリ(<bundle>/crls、 <bundle>/ocsp、<bundle>/trust-anchors)として実体化できる。そして後続の --from-bundle 実行は、同じ RevocationFetcher/TrustAnchorStore トレイトを通 してそれらを再生する。統合された validation_objects インベントリ(§2.2.1)は、 何が、どの出所で使用されたかを正確に示すレポート内のマニフェストである。したが って第三者はバンドルの内容をレポートと突き合わせて検証できる。 ValidationObjectOrigin の bundle 値はバンドルから来た資料を示す。from_bundle モードにおいては、あるオブジェクトは判明している場合にはその元の出所を保持する (report/validation_objects.rs の L303 周辺のコメント)。したがって監査の連鎖は 閉じている。すなわち レポート → マニフェスト → バンドル → 再実行 → 同一のレポー ト である。
これを実現する CLI モードはクローズドな Mode 列挙 {Online, Offline, FromBundle}(pverify-cli/src)であり、カーネルの失効 VerificationMode へとマップされる。モードの配管については第3章を参照のこと。
IV. Specification-Compliance(仕様適合、NON-NEGOTIABLE)
実装は以下を主要な規範的リファレンスとして扱う。
- RFC 5280(X.509、CRL、§6 パス検証)
- RFC 5652(CMS)
- RFC 3161 / 5816(TSP)
- RFC 6960(OCSP)
- ETSI EN 319 102-1(署名検証手続)
- ETSI EN 319 122 / 319 142(CAdES / PAdES)。319 132(XAdES、v0.4+)
- デジタル庁 デジタル社会推進標準ガイドライン(GPKI 規定)
- JIS X 5092 / 5093(CAdES / XAdES の日本語訳)
振る舞いの正しさは、公開されたテストベクタを通じて実証されなければならない (MUST)。すなわち ETSI 適合性スイート、デジタル庁が公開したサンプル、および プロジェクトが保守するゴールデンケースである。いずれかのリファレンス文書から の逸脱は、該当する機能仕様において文書化された根拠とともに列挙されなければなら ない(MUST)。
この原則は規範的コーパスを固定し——そして決定的に重要なことに——正しさが公開され たテストベクタによって実証されることを要求する。主張するだけでは足りない。標準 → 実装モジュールの対応付けは第5章〜第13章の主題である。本節はその契約と、§IV が 拘束力を持たせる AdES レベルの語彙を記録する。
| 標準 | 関心事 | 主要モジュール |
|---|---|---|
| RFC 5280 §6 | X.509 パス検証、CRL | crates/pverify-core/src/path/(mod.rs、name_constraints.rs、policy.rs、bridge.rs)、revocation/crl.rs |
| RFC 5652 | CMS SignedData | crates/pverify-core/src/cms/signed_data.rs |
| RFC 3161 / 5816 | タイムスタンプトークン(TST) | crates/pverify-core/src/timestamp.rs |
| RFC 6960 | OCSP | crates/pverify-core/src/revocation/ocsp.rs |
| RFC 5035 | ESS署名証明書バインディング | CAdES の署名者バインディングパス(025) |
| ETSI EN 319 102-1 | インディケーション/サブインディケーション | crates/pverify-core/src/report/etsi.rs |
| ETSI EN 319 122 | CAdES | verify_cades(verify.rs:475) |
| ETSI EN 319 142 | PAdES | crates/pverify-core/src/pades/mod.rs |
| ETSI EN 319 132 | XAdES | crates/pverify-core/src/xades.rs + pverify-xades |
| ETSI TS 119 182 | JAdES | crates/pverify-core/src/jades.rs + pverify-jades |
| ETSI EN 319 162 | ASiC | crates/pverify-core/src/asic.rs + pverify-asic |
| CRYPTREC / NIST SP 800-57, 800-131A | アルゴリズムポリシー | crates/pverify-core/src/algorithm_policy/(032) |
| JNSA 署名検証ガイドライン | VRT、gap-review プロファイル | crates/pverify-core/src/vrt/ |
§IV の「逸脱は……文書化された根拠とともに列挙されなければならない」条項こそ、 pverify が一律のサポートを主張するのではなく、各フォーマットの実際にどのレベル を検証するかについて精密である理由である。サポート行列を、 verify.rs:1034-1042 のフォーマット昇格ラダーおよびフォーマットごとの検証器に 根ざして以下に示す。
| フォーマット | 検証するレベル | 明示的に降格/拒否するもの |
|---|---|---|
| CAdES(EN 319 122) | B-B/BES、B-T(signature-time-stamp)、B-LT、B-LTA(§6.3.4 のインプリント再計算を伴う archive-time-stamp-v3) | マルチサイナ CMS → INDETERMINATE cms_multi_signer_unsupported |
| PAdES(EN 319 142) | 埋め込み CMS + /DocTimeStamp 上の B-B/B-T/B-LT/B-LTA。文書レベルの /DSS+/VRI+Adobe revocationInfoArchival を、オフラインで definitive な LT/LTA のために消費(034) |
— |
| XAdES(EN 319 132) | B-T/B-LT と B-LTA ArchiveTimeStamp インプリント検証(ETSI TS 101 903 Annex A.1.5; ブランチ 036、SignatureFormat::XadesBLta)を伴う enveloped B-B(監査済み bergshamra-c14n 経由の Exclusive C14N) |
detached/enveloping および非排他的 C14N → INDETERMINATE xades_unsupported_canonicalization |
| JAdES(TS 119 182) | JWS-JSON-serialization の B-B | B-T+ および sigD/Compact serialization → 拒否し INDETERMINATE |
| ASiC(EN 319 162) | ASiC-S / ASiC-E。各内部署名を CAdES または XAdES に委譲 | 認識される ASiC コンテンツを持たない素の ZIP → INDETERMINATE asic_unsupported_container |
これらの拒否はバグではない。それらは §I/§IV の協働である——ツールは実装していな いレベルを断言することを辞退し、その逸脱は対応する機能仕様において列挙される。 詳細は第5章(CMS/CAdES)、第6章(PAdES)、第7章(XAdES/ASiC)、第8章(JAdES)を 参照のこと。
これら 2 つの原則はアーキテクチャの要石であり、併せて読むのが最良である。
V. Library-First(ライブラリファースト)
pverify-coreは I/O を行わないno_std + allocライブラリとして実装されなけ ればならない(MUST)。これにより CLI バイナリと Cloudflare Workers のリファレ ンスビルドが単一のコアの上に座るようにする。ネットワークアクセス、時刻、およ びトラストアンカーの保管は、コアに焼き付けられるのではなく、ターゲットごとに実 装されるトレイト——RevocationFetcher、Clock、TrustAnchorStore——として 表現されなければならない(MUST)。
VI. WASM Compatibility(WASM 互換性、NON-NEGOTIABLE)
オプションのネイティブ専用アクセラレーションパスを除き、
pverify-coreはwasm32-unknown-unknown上でコンパイルおよび実行できなければならない(MUST)。
ring、aws-lc-rs、および OpenSSL FFI 依存は、コアにおいて、ならびにコアも しくは Workers ビルドが推移的に引き込む任意のクレートにおいて禁止される (PROHIBITED)。純 Rust の RustCrypto エコシステムのみが許容される。- Workers ビルドは、バンドルサイズを削減するためにハッシュ計算と公開鍵検証を Web Crypto の
SubtleCryptoAPI に委譲してよい(MAY)。ただし、その結果出力 が原則 II の下でネイティブパスとバイト単位で同一に保たれることを条件とする。- 禁止された依存を再有効化するフィーチャフラグの追加(例: デフォルトフィーチャ 付きの
rustls、civ のvp-federation)は禁止される(PROHIBITED)。
no_std、unsafe-free、I/O-free であるcrates/pverify-core/src/lib.rs の最初の 3 行が、両原則の強制である。
#![no_std]
#![forbid(unsafe_code)]
#![deny(missing_debug_implementations)]
extern crate alloc;#![no_std] は、カーネルが OS に触れること(std::fs なし、std::net なし、 SystemTime なし)を構造的に不可能にする。#![forbid(unsafe_code)] は、いかな る unsafe ブロックに対しても強制的なコンパイルエラーとなる——allow による上書 きは存在しない。カーネルは alloc のみを使用する。それが真に必要とするホスト的 関心事は、traits.rs 内の 3 つのトレイト(§2.3.2)であり、ネイティブターゲット 向けに一度、WASM 向けに一度実装される。
no_std の直接的な帰結は、カーネルが PDF、XML、ZIP、または JSON を解析できないこ とである——それらは重量級の std リンクされたライブラリ(lopdf、quick-xml、 zip/flate2)を必要とする。したがって、すべてのコンテナ/文書の解析はホスト 側で行われ、追加的な VerificationRequest フィールドを介してモデルに依存しな いバイト構造をカーネルへ供給する。PAdES 署名辞書および /DSS+/VRI 資料は pverify-cli/src/pdf.rs(ネイティブ、lopdf)とバイト単位で同一の web/pverify-wasm/src/lib.rs(ブラウザ、lopdf)によって抽出され、XAdES は pverify-xades によって、ASiC は pverify-asic によって、JAdES は pverify-jades によって抽出される。カーネルは、自身が検出したフォーマットに一致 するフィールドのみを参照する。
原則 VI は「コアが……推移的に引き込む任意のクレートにおいて」と述べる——これは 依存関係グラフについての言明であり、プロジェクトはこれを信仰によってではなく 経験的に強制する。
cargo build --target wasm32-unknown-unknown -p pverify-core が成功しなければならない。いずれかの推移的依存が wasm32 でビル ド可能でない場合(ring/getrandom/ネイティブ FFI を引き込むため)、これは失 敗する。scripts/cargo-tree-gate.sh。 このスクリプトは cargo tree -e normal --no-default-features -p pverify-core を実行し、いずれ かのホスト専用クレートがコアの本番グラフに現れた場合にビルドを失敗させる (exit 1、117 行)。すなわち pverify-eutl(L50)、pverify-aatl/lopdf (L76)、pverify-asic/zip/flate2/miniz_oxide、pverify-jades、 pverify-anchor-inventory、pverify-test-helpers、xtask である。PASS メッ セージ(L120)は、不在を確認した対象を正確に列挙する。暗号の制約は依存選択によって実現される。カーネルの暗号依存は RustCrypto のみ (der、x509-cert、rsa、p256、p384、ed25519-dalek、ml-dsa、 sha1/sha2、spki、pkcs1、x509-ocsp)であり、すべて検証専用である——本番 グラフには署名や鍵生成の面は存在しない。web/pverify-wasm クレートはルートワーク スペースから意図的に切り離されている(その Cargo.toml 内の空の [workspace] テーブル。根拠は 3–9 行に記録)。これにより wasm-bindgen が生成する unsafe をルートの unsafe_code = "forbid" lint 面の外に保ち、また wasm グラフを リリースされる cargo test --workspace / cargo audit の面の外に保つ。完全なト ポロジと WASM ホストについては第3章を参照のこと。
「単一のコア」(§V)の成果は CLIとWASMのパリティである。すなわちネイティブ CLI とブラウザホストは同一の verify_with カーネル (crates/pverify-core/src/verify.rs:178)とバイト単位で同一の抽出器を実行し、同 一の入力に対してバイト単位で同一のレポートを生成する。§VI の第二の項目で許容され る Web Crypto への委譲は、同じバイト同一性のルールによって制約される。すなわち、 いかなる SubtleCrypto アクセラレーションも「原則 II の下でネイティブパスとバイト 単位で同一に保たれなければならない」。パリティは抽出器の出力形状に対するテストに よって固定されており、これは強制された不変条件として扱われる(第3章を参照)。
VII. Offline-First(オフラインファースト)
CLI は
--offlineモードをサポートしなければならない(MUST)。このモードでは、 署名に埋め込まれた資料(DSS / VRI 辞書、アーカイブタイムスタンプ)と、設定され たトラストアンカーのみを用いて検証が進行する。ネットワークアクセスは明示的な オプトインでなければならない(MUST)。verify --from-bundleのデフォルトの姿勢 は完全にオフラインである。
オフラインファーストには、プロジェクトが名付けられた不変条件にまで高める微妙な倫 理的な角がある。すなわちオフラインの誠実性である。--offline モードにおいて、 ツールは決して行わなかったレスポンダ接触を捏造してはならない。以前のある実装は、 オフラインであって OCSP レスポンダに到達できないとき、ocsp_responder_unreachable を出力していた——これは接触が試みられて失敗したかのように誤って示唆する。それは §I/§VII の侵犯であった。すなわち誠実な事実は「オフラインであるため失効検証が行わ れなかった」であって、「レスポンダがダウンしている」ではない。その是正は IndeterminateRevocationOffline の結果 → revocation_not_checked_offline サブイ ンディケーション(crates/pverify-core/src/revocation/mod.rs、~L657–667。 ocsp_attempt: None フィールドが接触の不在を明示する)である。オフラインモード はソケットを一切開かない。
これは §2.3.3 の埋め込み資料のフォールスルーとかみ合う。埋め込まれた DSS/VRI/revocationInfoArchival チャネル(ブランチ 034 の時点で PAdES 向けに消費 される)は、ライブ/CDP チャネルが不確定の場合にのみ発火する。--offline モー ドにおいてそれは常にそうであるため——証拠が十全に揃ったオフラインの PAdES-LT/LTA は、自身の埋め込み資料から definitive な結果に到達でき、一方その資料を欠く PDF は誠実に revocation_not_checked_offline へと降格する。--from-bundle のデフォル トの姿勢は、構成上、完全にオフラインである(バンドルが唯一の資料源である)。
憲法の Phased Path Validation Roadmap 節は、異例なほど指示的な条項—— アルゴリズムポリシー強制の適用範囲——を含む。これはポリシーテーブルが憲法上 の表面であると宣言されているためである。すなわちそれを変更することは改正であっ て、コード編集ではない。一般条項は次のように述べる。
あるリリースがアルゴリズムポリシー強制(弱いアルゴリズムの存在が ETSI インディ ケーションを降格させるモード)を導入する場合、それはオプトインでなければなら ない(MUST)。デフォルトの振る舞いは v0.1 の事実報告の姿勢(原則 I)に留まらな ければならない(MUST)。正確なポリシーテーブル……は憲法上の表面である——追加 または再分類は憲法改正を要する。
その後、1.2.0 改正が具体的なテーブルを批准した。3 つの制約が実装を拘束する。
--algorithm-policy [FILE] によって のみ有効化される。デフォルト無効のレポートは、schema_version を除いて 032 以 前の本体とバイト単位で同一である(§2.3.3)。これは §I をデフォルトの姿勢として 保つ。value 時 点で有効なポリシーに対して判定される。「D 以降は許容不可」とは、当該オブジェ クトはその VRT 日付が厳密に D より前である場合に限り合格することを意味する。crypto_constraints_failure_no_poe(ETSI EN 319 102-1 の CRYPTO_CONSTRAINTS_FAILURE_NO_POE を反映)を伴う INDETERMINATE を生む——教 科書的な断定回避(§2.2.2)である。弱いアルゴリズムは、その証明を断言できないこ とを意味するのであって、偽造を証明するものではない。批准されたデフォルトテーブル(憲法 §「Algorithm-policy enforcement scope」)は、 CRYPTREC 電子政府推奨暗号リストを一次権威とし、NIST SP 800-57 Part 1 / SP 800-131A を二次権威とし、バージョンは 2026-06-default であり、認識されない OID または判定不能な鍵長を INDETERMINATE として扱う(決して暗黙の合格にはしない)。
| アルゴリズム | ルール | 有効日 | 権威 |
|---|---|---|---|
| MD5(ダイジェスト) | 決して許容不可 | 1970-01-01 | CRYPTREC 除外 |
| SHA-1(ダイジェスト) | 以降は許容不可 | 2014-01-01 | NIST SP 800-131A;CRYPTREC 危殆化 |
| SHA-256 / SHA-384 / SHA-512 | 許容 | — | CRYPTREC 推奨 |
| RSA < 2048-bit | 以降は許容不可 | 2014-01-01 | NIST SP 800-57;CRYPTREC 推奨 |
| ECDSA(P-256 / P-384、≥256-bit フィールド) | 許容 | — | CRYPTREC 推奨 |
| Ed25519 | 許容 | — | 強力と認識 |
| ML-DSA-44/65/87 | 許容 | — | PQC、サンセットなし |
コード定数 AlgorithmPolicy::default_policy()(crates/pverify-core/src/algorithm_policy/ 内)は「specs/032-algorithm-validity/contracts/default-policy.md(test C-3)と バイト単位で一致しなければならない(MUST)」。これはプロジェクトの「原則は、それを 強制するテストがある場合に限って実在する」という立場(§2.1)の最も強い形である。 すなわち憲法とコードがバイト比較テストによって結び付けられている。評価エンジンに ついては第13章(アルゴリズム妥当性ポリシー)を参照のこと。
§IV のロードマップ節は RFC 5280 §6 を段階的に提供し、交渉不可能な開示ルールを伴う。
すべてのリリースは、機械可読の形で、実装する段階(phase)を出力しなければなら ない(MUST)。これにより消費者が、より早い段階を完全な検証と取り違えることがで きないようにする。
当初の v0.1 MVP は基本的なパス構築を出荷し、ブリッジCAチェーンが未対応であること を示す構造的フラグの設定が要求されていた。
Phase 1 の必須開示。 v0.1 MVP の出力は、ブリッジCAチェーンが未対応である ことを示す構造的フラグを設定しなければならない(MUST)。このフラグの抑制は、提 示上の選択ではなく憲法違反である。……当該フラグが
trueに反転してよいのは、 v0.3 が名前制約とポリシー処理の下でのブリッジCA走査を出荷したときに限る。
これは §I(事実報告)をツール自身の能力に適用したものである。すなわち pverify は、消費者が部分的な検証を完全な検証と取り違えることを許してはならない。現在のリ リースは v0.3/v0.4+ の帯域に到達している——名前制約 (path/name_constraints.rs、RFC 5280 §4.2.1.10)、ポリシー処理 (path/policy.rs、§6.1 valid_policy_tree)、ブリッジCA走査 (path/bridge.rs)、archive-time-stamp-v3 のインプリント再計算(EN 319 122-1 §6.3.4)、XAdES、OCSP、およびオブジェクトごとの VRT エンジンがすべて実装されてい る。歴史的な bridge_ca_required_unsupported サブインディケーションは、保存された レポートのデシリアライズのためだけに SubIndication 列挙(report/etsi.rs:63) 内に保持されている——非推奨だが削除はしないバリアントであり、追加的なスキーマバー ジョニングのルール(§2.10)と整合する。
レポート形状は公開された契約である(リポジトリルートの report-schema.json。 crates/pverify-core/src/report/ から 1:1 でミラーされる)。クローズドな列挙—— SubIndication、RevocationOutcome、ValidationObjectOrigin——とレポート構造体は すべてその一部である。憲法のガバナンス SemVer から導出され、レポートスキーマに 具体的に適用されるバージョニングルールは以下のとおりである。
ValidationObjectOrigin に pdf_dss/pdf_vri を追加 (report/validation_objects.rs:113,118)し、SCHEMA_VERSION を 1.5.0→1.7.0 にバンプすること。監査人にとって決定的に重要な細部: 列挙の宣言順序がソート順序である。新しい pdf_dss/pdf_vri バリアントは(割り込ませるのではなく)末尾に追加された。 これにより derive_validation_objects におけるインベントリの導出が、出所 → 位置を、 既存バリアントの相対順序を乱すことなくマップする——新しいタグを使用しないレポート について、インベントリのバイト同一性を保つ。非推奨のバリアント(例: bridge_ca_required_unsupported、§2.9)は保存されたレポートのデシリアライズのため に保持されるが、現在のコードパスからは出力されない。SCHEMA_VERSION は crates/pverify-core/src/report/schema.rs:line にあり、現在 "1.10.0" である。その 系譜 1.2.0 → 1.3.0(031)→ 1.4.0(032)→ 1.5.0(033)→ 1.7.0(034)→ 1.8.0(035 OcspAttempt.request_der_hex)→ 1.9.0(035 CdpEntry 構造化 + aia_ocsp_uris) は、追加的スライスごとに 1 つの MINOR バンプである。
以下の不変条件は第5章〜第13章を通じて繰り返し現れる。それらは上記の各原則から導出 され、それらに遡及可能である。本節はその正典たるリファレンス一覧である。各々は、 それが奉仕する原則と、それを強制するコードを引用する。
| # | 不変条件 | 原則 | 強制する根拠 |
|---|---|---|---|
| 1 | 断言できないことは断言しない — 確立できない事実 → INDETERMINATE + クローズドなサブインディケーション。決して捏造された PASS/FAIL にしない | §I | report/etsi.rs(SubIndication);verify.rs:497(マルチサイナ);revocation/mod.rs:667(オフライン) |
| 2 | すべての成果物を出所付きで列挙 | §I, §III | report/validation_objects.rs(derive_validation_objects、ValidationObjectOrigin) |
| 3 | 業務上/法的な判定を出力しない | §I | スキーマに qualified/legally_valid フィールドが一切存在しないこと |
| 4 | 判定のバイト同一性(追加的/無効化された機能では schema_version のみが異なる) |
§II | 034 FR-008(no-DSS PDF);032 SC-001(ポリシー無効);schema.rs:line |
| 5 | 時刻は一度捕捉、--at が優先;VRT は request_at のみを使用 |
§II | traits.rs:76(Clock);vrt/ エンジン(Clock::now なし) |
| 6 | すべての I/O はトレイト経由。カーネルはネットワーク/fs/クロックをゼロ実行 | §II, §V | traits.rs(Clock/RevocationFetcher/TrustAnchorStore) |
| 7 | 再現可能なバンドルの再実行が同一のレポートを生む | §III | Mode::FromBundle;validation_objects.rs のバンドル出所 |
| 8 | 標準に固定された振る舞いが公開ベクタによって実証される | §IV | モジュール対応表 §2.5.1;ETSI/DSS 相互運用フィクスチャ |
| 9 | コア内でコンテナを解析しない。ホストがモデルに依存しないバイトを供給 | §V, §VI | pverify-cli/src/pdf.rs、pverify-xades/asic/jades;コアグラフから不在であることがゲートで保証される |
| 10 | WASMクリーンなコア: no_std+alloc、forbid(unsafe_code)、RustCrypto のみ |
§VI | lib.rs:10-14;scripts/cargo-tree-gate.sh(exit 1、L117);WASM 切り離しワークスペース |
| 11 | CLIとWASMのパリティ — 同一のカーネル + 抽出器 → 同一のレポート | §V, §VI | verify.rs:178(verify_with);抽出器に対するパリティテスト |
| 12 | オフラインの誠実性 — 捏造されたレスポンダ接触がない | §VII, §I | revocation/mod.rs:667(IndeterminateRevocationOffline、ocsp_attempt: None) |
| 13 | 埋め込み資料のフォールスルーは、ライブ/CDP が不確定の場合にのみ発火する | §II, §VII | 失効の埋め込み CRL/OCSP チャネル(019 の先例);PAdES DSS/VRI(034) |
| 14 | 追加的スキーマバージョニング;宣言順序 = ソート順序;非推奨バリアントを保持 | §IV(ガバナンス) | report/validation_objects.rs:113,118;schema.rs:line |
| 15 | オブジェクトごとの VRT 分離 — 各オブジェクトは自身の導出された VRT で判定される;下流レイヤによって再導出されることは決してない | §I, §II(031) | vrt/;verify.rs:775 および各トークンのループ |
| 16 | インディケーションの優先順位/決して覆い隠さない — 重大度順序付けされたベース。レイヤは降格のみ行い、証明済みの TOTAL_FAILED を決して覆い隠さない | §I | aggregate_etsi(verify.rs:2343) |
| 17 | アルゴリズムポリシーはオプトイン、VRT スコープ、INDETERMINATE のみ;デフォルトテーブルは憲法とバイト一致する | §I, §IV(032) | algorithm_policy/;default-policy テスト C-3 |
| 18 | ホストの取り込みは通常のアンカーを生成する;DSS /Certs は決してアンカーに昇格されない |
§III, §V | pverify-eutl/pverify-aatl;PAdES DSS 処理(034) |
不変条件 16 は、それが「断言できないことは断言しない」と「失敗を過少報告しない」が 出会う場所であるため、締めくくりの注記に値する。aggregate_etsi(verify.rs:2343) は重大度順序付けされたベースインディケーション(TOTAL_FAILED > INDETERMINATE > TOTAL_PASSED、最初に見つかったものが優先)を確立する。後続のレイヤ——署名者バイン ディング、コンテンツタイプ、オプトインのアルゴリズム妥当性レイヤ、および TSA 失効レイヤ——は、TOTAL_FAILED でないベースを降格することのみができ、 証明済みの TOTAL_FAILED を決して覆い隠さない。これは、証明済みのメッセージ ダイジェスト不一致や ESS 証明書置換防御が、後続レイヤの不確定性によって黙って INDETERMINATE に格下げされ得ないことの形式的保証である。これにより §I の 2 つの半 分が同時に遵守される。すなわち pverify は決して失敗を捏造せず(断定回避の側、不変 条件 1)、また実在する失敗を決して隠さない(決して覆い隠さない側、不変条件 16)。
憲法は明示的な Out-of-Scope 一覧で締めくくられる。いずれかの項目を取り込むことは、 バージョニングポリシーにより MAJOR 改正である。
- 署名生成。 pverify は検証専用であり……
- JPKI(マイナンバーカード)署名検証。 v2.0+ で追跡される……
- 法的有効性の判定。 原則 I により除外される。
- エンドユーザ GUI。 CLI、ライブラリ、および HTTP API のみ……
- 適格トラストサービスプロバイダ(QTSP)認定。 ETSI への整合は主張される。 認定は追求されない。
これらのうち 2 つは、単なるポリシーではなく構造的なものである。すなわち、検証専用 であることは、本番依存関係グラフに署名/鍵生成の面が存在しないことを意味し (§2.6.3)、法的有効性の判定が「原則 I により」除外されることは、§2.2.3 で述べた のと同じ境界である。
ガバナンスは開発プロセスを憲法に縛り付ける。すべての /speckit-plan は「上記の 原則に対する Constitution Check を実行しなければならない(MUST)」。文書化された違 反は、プランの Complexity Tracking 節において明示的な正当化とともに列挙されなけれ ばならない。そして「pverify-core、Workers ビルド、またはトラストアンカー処理に 触れるプルリクエストは、どの原則が検証されたかを引用しなければならない(MUST)」。 憲法は境界を定める。機能ごとの運用上の細部は各 specs/<feature>/plan.md に存在し、 (規範的でない)歴史的根拠は docs/vision-statement.md に存在する。現在の憲法バー ジョンは 1.2.0、2026-06-10 批准、最終改正 2026-06-22 である。
残りの各章は厳密にこれらの境界内で動作する。第3章は §II/§III/§V/§VI/§VII を実現す るクレートのトポロジ、データフロー、CLI モード、および WASM ホストを詳述する。そし て第5章〜第9章は §IV が固定する RFC 5280 / RFC 5652 / CMS / AdES 検証とパス検証を 詳述する。
本章では、pverify の構造的構成について述べる。すなわち、クレートのトポロジーとその 依存関係の規律、I/O を行わない検証カーネルとそのホスト群との間にある荷重を担う境界、 2 つの具体的な実行モデル(ネイティブ CLI とブラウザ内 WebAssembly)、検証カーネル pverify-core の内部モジュール構成、そして VerificationRequest が生の入力バイト列 からクローズドスキーマの JSON 事実報告へと至る端から端までの経路である。以下のあらゆる 主張はソースツリーに根拠を置いており、レビューアが主張を直接監査できるよう、ファイル参照 および関数参照を付している。
本章が判定(verdict)やサブインディケーションの意味に触れる箇所では、その主題を所掌する 各章へ委ねる。すなわち、ETSI インディケーションの集約(第5章 §5.7 および Result Model の章)、形式ごとの検証セマンティクス(CAdES, PAdES, XAdES, JAdES, ASiC の各章)、 RFC 5280 パス検証(パス検証の章)、失効検証(失効検証の章)、そして報告スキーマと バージョニング(報告スキーマの章)である。相互参照では「… の章を参照」という語を用いる。 本ファイルを超える章番号付けは別途編纂されるためである。
pverify は、日本の GPKI/JNSA プロファイルおよび欧州の eIDAS/ETSI 体系に照らして 高度電子署名(AdES)を検証し、業務上の「有効/無効」判定ではなく事実報告を出力する。 システム全体を秩序づける、唯一の妥協を許さないアーキテクチャ上の境界は、以下の分離である。
pverify-core — これは no_std + alloc であり、unsafe を 禁止し、I/O を一切行わず、wasm32-unknown-unknown 上でコンパイルおよび実行できなければ ならない。pverify-core/src/lib.rs は冒頭の数行でこれを具体化している。
#![no_std]
#![forbid(unsafe_code)]
#![deny(missing_debug_implementations)]
extern crate alloc;(crates/pverify-core/src/lib.rs:11-15)。
カーネルは、すべての暗号処理、RFC 5280 §6 パス検証、失効評価、RFC 3161/5816 タイムスタンプ検証、オブジェクトごとの検証基準時刻(VRT)の導出、オプトインの アルゴリズムポリシー評価、そして ETSI EN 319 102-1 インディケーション集約を実行する。 しかしカーネルは、外界へのアクセスを要するために自前では実行できない 3 つのことがある。 すなわち、検証クロックの読み取り、失効情報の取得または照会、そしてトラストアンカー集合の 列挙である。これら 3 つの関心事 — そしてこれらのみが、 crates/pverify-core/src/traits.rs においてケイパビリティトレイトの背後に抽象化される。 すなわち Clock、RevocationFetcher、TrustAnchorStore である。カーネルは &dyn TrustAnchorStore および &dyn RevocationFetcher の参照を保持し、それらを通じて 呼び出す。カーネルがソケットを開いたり、ファイルを読んだり、SystemTime::now() を呼んだり することは決してない。
この境界は努力目標ではない。scripts/cargo-tree-gate.sh(§3.2.2 で述べる)と、 ゲートバッテリー中の cargo build --target wasm32-unknown-unknown -p pverify-core ステップによって、経験的に強制される。その背後にある憲章上の原則は、§V(ライブラリ ファースト:コアでの文書パース禁止)、§VI(WASM クリーンなコア、妥協不可)、および §I (事実報告)である。憲章は .specify/memory/constitution.md を参照のこと。
3 つの性質がこの境界から導かれ、そのいずれも政府の顧客が独立して検証できる。
決定性/再現性(§II)。 カーネルはクロックもネットワークも読まないため、 (入力バイト列、トラストアンカー、検証時刻、失効情報)という所与のタプルは、いつ どこで実行されようともバイト単位で同一の報告を生み出す。検証時刻はホストによって一度だけ 捕捉される(CLI は --at が与えられない限り起動時に一度だけ SystemTime::now() を 読む)、そして VerificationRequest を通じて引き渡される。
監査可能な暗号サーフェス(§VI)。 すべての暗号処理は RustCrypto である。 カーネルや WASM グラフのいずれにも、ring、aws-lc-rs、OpenSSL FFI、デフォルトの rustls は存在しない。検証に用いられる暗号プリミティブは、RSA PKCS#1 v1.5、 RSA-PSS、ECDSA P-256/P-384、Ed25519、および ML-DSA であり、 crate::crypto::verify_with_alg(crates/pverify-core/src/crypto.rs)において OID と SubjectPublicKeyInfo によりディスパッチされる。本番の暗号サーフェスは 検証専用である — 本番依存関係には署名処理や鍵生成の経路は存在しない。
ネイティブとブラウザで同一の挙動。 カーネルが検証の真実の唯一の源泉であり、かつ WASM ビルド可能であるため、ネイティブ CLI とブラウザ内デモは同じ抽出済み構造に対して 同じ verify_with 関数を実行し、同一の入力に対してバイト単位で同一の報告を生み出す (「CLI と WASM のパリティ」不変条件、§3.5)。
システム全体は、憲章 §I に由来する「断言できないことは断言しない」という規則に 統べられている。pverify がある事実を確立できない場合、TOTAL_FAILED や TOTAL_PASSED を捏造するのではなく、精密なクローズドな列挙(閉じた語彙)の サブインディケーションを伴って INDETERMINATE へと格下げする。アーキテクチャが推測 ではなく誠実に表出する具体例を以下に挙げる。
IndeterminateRevocationOffline / revocation_not_checked_offline(レスポンダ接触を捏造することは決してない — 憲章 §VII オフラインの誠実性)。INDETERMINATE / cms_multi_signer_unsupported (crates/pverify-core/src/verify.rs:496)。暗黙に切り詰められた署名者集合に対する 判定を下すことは決してない。revoked_no_poe(INDETERMINATE)。偽造の主張ではない。サブインディケーションの値空間は単一のクローズドな Rust 列挙 (crates/pverify-core/src/report/etsi.rs、SubIndication)であり、ルートの report-schema.json に 1 対 1 で写し取られている。報告形状のバージョンは SCHEMA_VERSION = "1.10.0"(crates/pverify-core/src/report/schema.rs:line)である。
ルートワークスペース(Cargo.toml)は 11 個のメンバーを宣言する。
| クレート | 役割 | グラフ分類 |
|---|---|---|
pverify-core |
検証カーネル(no_std + alloc、I/O を行わない、RustCrypto のみ)。 |
WASM クリーン |
pverify-xades |
ホスト XAdES(XML-DSIG)抽出:パース + Exclusive C14N + バイト抽出。暗号処理なし。 | WASM クリーン(抽出のみ) |
pverify-asic |
ホスト ASiC(EN 319 162)コンテナ抽出:ZIP の走査。暗号処理なし。 | WASM クリーン(抽出のみ) |
pverify-jades |
ホスト JAdES(JWS JSON Serialization)抽出。暗号処理なし。 | WASM クリーン(抽出のみ) |
pverify-eutl |
EU トラステッドリスト(ETSI TS 119 612)の取り込み → トラストアンカー。 | ホスト専用(ネイティブ std) |
pverify-aatl |
Adobe Approved Trust List の取り込み(lopdf + flate2 を引き込む)。 |
ホスト専用 |
pverify-anchor-inventory |
あらゆるアンカー配布チャネルの読み取り専用アグリゲータ。 | ホスト専用 |
pverify-cli |
ネイティブコマンドライン検証器 — ネイティブ I/O が許される唯一のクレート。 | ホスト専用 |
fixture-gen |
署名/フィクスチャ生成ツール(publish = false)。 |
ツール |
pverify-test-helpers |
共有テストヘルパー(publish = false)。 |
ツール |
xtask |
ビルド/CI 自動化タスク。 | ツール |
12 番目のクレート web/pverify-wasm は、ルートワークスペースから意図的に切り離されて いる。その Cargo.toml は空の [workspace] テーブルを持ち、これにより wasm-bindgen が生成する unsafe コードと wasm 依存関係グラフが、ルートの unsafe_code = "forbid" リントのサーフェスから、またリリース対象の cargo test --workspace / cargo audit の サーフェスから外れるようにしている。これがブラウザホストである。
クレート群は 2 つのグラフ分類に分かれ、ホスト → WASM クリーン → コアという、 ゲートによって表明される厳格な依存方向を持つ。
WASM クリーングラフ(wasm32-unknown-unknown へコンパイル可能でなければならず、 RustCrypto のみ、ring/getrandom/openssl なし):
pverify-core — カーネル。その依存関係は RustCrypto のフォーマットおよび プリミティブクレート(der、x509-cert、rsa、p256、p384、 ed25519-dalek、ml-dsa、sha1、sha2、spki、pkcs1、x509-ocsp)に加え、 time、serde、serde_json、hex である。ワークスペース内部には何も依存しない。pverify-xades、pverify-asic、pverify-jades — pverify-core に依存する ホスト抽出クレート(コアが所有する境界型、例えば XadesComponents、AsicSignature、 JadesComponents のため)であるが、それ自体は wasm32 ビルド可能であり続けることが 求められる。これらは暗号処理を含まない — パース、正規化、バイト抽出のみである。 ツリーゲートによって pverify-core のランタイムグラフから禁止されている。ホスト専用グラフ(ネイティブ std。コア/WASM ランタイムグラフに現れては ならない):
pverify-eutl、pverify-aatl、pverify-anchor-inventory — アンカー取り込み。 各々が通常の TrustAnchor 値を出力し、既存の --trust-anchors 経路を通じて 再投入可能である。取り込み処理はカーネルに何も付け加えない。pverify-cli — ネイティブ検証器。ネイティブ I/O が許される唯一のクレートである。 すなわち cfg(not(wasm32)) 下でのオンライン取得のための ureq、PDF パースのための lopdf、そして std::fs である。web/pverify-wasm — ブラウザホスト。コア + xades + asic + jades + lopdf (ブラウザ内 PDF パース)+ wasm-bindgen に依存する。破線の矢印 CLI -. dev-dep .-> TESTH は dev-dependency のみである。本番の cargo tree -e normal グラフには存在しないことが表明されている。
scripts/cargo-tree-gate.sh は、境界に対する荷重を担う経験的なガードである。これは cargo tree -e normal --no-default-features -p pverify-core を実行し、いずれかの ホスト専用クレートまたはその特徴的な推移的依存関係がコアの通常依存グラフに現れた場合に ビルドを失敗させる — 具体的には pverify-eutl、 pverify-asic/zip/flate2/miniz_oxide、pverify-aatl/lopdf、pverify-jades、 pverify-test-helpers、または xtask である。これにより「カーネルは PDF/XML/ZIP パーサを決してリンクしない」というアーキテクチャ上の規則を、コードレビューの慣習ではなく 機械的に検査される不変条件へと変換する。補完的な cargo build --target wasm32-unknown-unknown -p pverify-core ステップは、カーネルが 実際にブラウザターゲット向けにビルドされることを証明する。
カーネルは PDF、XML、ZIP、JSON のコンテナをパースできない — これらのパーサは std と、no_std WASM カーネルと両立しない巨大な依存関係グラフを引き込む。代わりに、ホストが 各コンテナ形式を事前にパースし、抽出されたモデルに依存しないバイト構造を VerificationRequest(crates/pverify-core/src/verify.rs:69)の追加的フィールドへ 引き渡す。カーネルは判別された形式に合致するフィールドのみを参照する。関連するリクエスト フィールドとその生産者は以下のとおりである。
VerificationRequest フィールド |
型 | ホスト生産者 | 消費される形式 |
|---|---|---|---|
signature_bytes |
Vec<u8> |
生の入力ファイル | 常時(判別済み) |
detached_content_bytes |
Option<Vec<u8>> |
--content ファイル |
CAdES / XAdES(デタッチ) |
extra_certificates |
Vec<Vec<u8>> |
バンドル certificates/ |
パス構築 |
pdf_signature_dicts |
Vec<PdfSignatureDescriptor> |
pverify-cli/src/pdf.rs / WASM lib.rs |
PAdES |
pdf_validation_data |
PdfValidationData |
pverify-cli/src/pdf.rs / WASM lib.rs |
PAdES |
xades_components |
Vec<XadesComponents> |
pverify-xades |
XAdES |
jades_components |
Vec<JadesComponents> |
pverify-jades |
JAdES |
asic_signatures |
Vec<AsicSignature> |
pverify-asic |
ASiC |
required_policies |
Vec<PolicyOid> |
CLI --required-policy |
全形式 |
algorithm_policy |
Option<AlgorithmPolicy> |
CLI --algorithm-policy |
全形式(オプトイン) |
この追加性は意図的な再現性の契約(憲章 §II)である。すなわち、PDF でない入力は空の pdf_validation_data(その Default)を持ち、スコープ外の JSON は空の jades_components を持つ、といった具合である。従前のあらゆる呼び出し側は、より新しい フィールドを Default::default() / Vec::new() で埋めるため、チャネルを追加しても 従前の報告は schema_version 文字列を除いてバイト単位で同一のまま保たれる(FR-008 バイト単位の同一性)。例えば、/DSS を持たない PDF は、034 以前の報告と schema_version だけが異なる。
これは憲章 §V「ライブラリファースト」の構造的表現である。コンテナのパースは完全に ホストクレート(pverify-cli/src/pdf.rs、pverify-xades、pverify-asic、 pverify-jades、pverify-aatl)内に存在し、カーネルは lopdf、zip、flate2、 あるいは bergshamra-c14n 正規化器を決してリンクしない。
crates/pverify-core/src/traits.rs は 3 つのホストケイパビリティと、最小限の値型一対を 定義する。
Clock — fn now(&self) -> OffsetDateTime。カーネルは実際にはホットパスで これを呼ばない(検証時刻はホストによって VerificationRequest.verification_time へ捕捉される)。このトレイトは、ホストが時刻を 決定論的に供給できるように存在する。テストでは固定時刻のフェイクを用いる。RevocationFetcher — fn fetch_crl(&self, url: &str) および fn fetch_ocsp(&self, uri: &str, request_bytes: &[u8])。実装は呼び出し側のモードを 尊重する。すなわちオンライン(ureq GET/POST)、オフライン(常に Err(Error::NotAvailableOffline))、または from-bundle(<bundle>/crls/ または <bundle>/ocsp/ から読む)である。fetch_ocsp は NotAvailableOffline を返す デフォルト実装を持ち、OCSP 以前の実装者が誠実に格下げできるようにしている。 決定的に重要なのは、カーネルが DER の OCSPRequest を自前で構築し (crate::revocation::ocsp::build_ocsp_request_bytes)、トレイトが生の BasicOCSPResponse バイト列を返す点である — パースはカーネル内に留まり、トレイトは x509-ocsp のバージョンをまたいで安定である。TrustAnchorStore — fn anchors(&self) -> &[TrustAnchor]。アンカーをホストが 供給した順序で返す。ストアは「意図的に不活性」であり、重複排除、ソート、フィルタを 行わない。ホストはロード時に各 TrustAnchor の SHA-256 fingerprint、生の subject_dn バイト列、および validity_window_covers_request_time フラグを計算する (FR-025)。このクレートはまた、スケルトン実装 OfflineRevocationFetcher(規範的な「常に拒否する」 フェッチャ)と InMemoryTrustAnchorStore を同梱しており、CLI および WASM ホストが これらをラップまたは投入する。
ネイティブ検証器は pverify-cli である。そのコマンドサーフェス (crates/pverify-cli/src/main.rs、enum Command)は 4 つのサブコマンドを網羅する。
verify — CAdES / PAdES / XAdES / JAdES / ASiC 署名を検証する。ingest-trust-list — EU トラステッドリスト(ETSI TS 119 612)を再投入可能な PEM アンカーへ取り込む(検証専用であり、「適格(qualified)」を決して判定しない)。ingest-aatl — Adobe Approved Trust List を、ピン留めされた Adobe Root CA G2 に 照らして取り込む。trust-anchors — あらゆるアンカー配布チャネルの読み取り専用検査。verify の経路(Command::Verify、main.rs:346)は以下のように動作する。
Clock(src/clock.rs)から、または --at から捕捉する。--at は常に優先される(§II 再現性)。--trust-anchors <DIR>、--offline、 --from-bundle <DIR>、または LDAP-CRL のいずれか — src/modes/{online,offline,from_bundle,ldap}.rs に実装されている。CLI の Mode 列挙 {Online, Offline, FromBundle} はカーネルの失効 VerificationMode へ写像される。 --offline はソケットを開かない(オフラインの誠実性)。src/trust.rs が各アンカーの SHA-256 フィンガープリント、生のサブジェクト DN バイト列、および validity_window_covers_request_time フラグ — ホストの FR-025 責務 — を計算し、 InMemoryTrustAnchorStore を構築する。src/pdf.rs::extract_signatures が lopdf を実行して PdfSignatureDescriptor 群(/ByteRange、/Contents、/SubFilter、%%EOF 走査による revision_end、 および /Sig 対 /DocTimeStamp のタグ)を出力し、extract_validation_data が 文書レベルの /DSS プールおよび /VRI をパースする(§3.8)。XAdES/ASiC/JAdES の入力は それぞれの抽出クレートによってパースされる。VerificationRequest を埋め、 verify_with(&request, &store, &fetcher) を呼ぶ。src/render.rs が返された Report を JSON へシリアライズする。CLI は規範的な検証器である。その境界付き I/O ガード(bounded_io.rs、net_guard.rs) とホスト側の責務(アンカーのロード、PDF パース)は、まさにカーネルが意図的に除外している ホストの関心事である。
ブラウザホストは web/pverify-wasm(web/pverify-wasm/src/lib.rs)である。これは 2 つの #[wasm_bindgen] エントリポイントを公開する。
classify(bytes) — 本物の pverify-core パーサを優先順位順に実行する(PDF → ASiC ZIP → JAdES JSON → X.509 証明書 → CRL → CMS SignedData → それ以外は 「コンテンツ」)。これによりドラッグアンドドロップの UI は、ドロップされたファイルを 拡張子ではなくそれが何であるかによって自動的に振り分けできる (classify_bytes、lib.rs:158)。verify(req) — 完全な pverify_core::verify::verify_with パイプラインを完全に クライアント側で実行し、整形済みの JSON 報告を返す(lib.rs:288)。verify は VerifyInput(署名バイト列、任意のデタッチコンテンツ、トラストアンカーの DER、追加の証明書、source_uri をキーとする crls/ocsp、verification_time_unix、 および任意の algorithm_policy セレクタ)をデシリアライズする。次に、ネイティブ CLI と バイト単位で同一のホスト作業を行う。
extract_pdf_signatures(pverify-cli/src/pdf.rs::extract_signatures の移植)、extract_pdf_validation_data(pverify-cli/src/pdf.rs::extract_validation_data の 移植 — 同一の /DSS+/VRI の和集合、重複排除、 MAX_DSS_ENTRIES_PER_POOL/MAX_DSS_STREAM_BYTES の上限、および大文字 16 進 SHA-1 の VRI キー付け、lib.rs:519)、pverify_xades::extract、pverify_asic::extract、pverify_jades::extract、これらにより同じ VerificationRequest を構築し、同じ verify_with を呼ぶ。 アンカーは anchor_from_der によって構築され、これは「pverify-cli/src/trust.rs と 同じやり方でフィンガープリントと有効期間ウィンドウを計算する」(lib.rs:115)。 ブラウザ側の DemoFetcher は CLI のバンドルフェッチャを鏡映したインメモリの source_uri → bytes ルックアップである。ミスは NotAvailableOffline を返し、これを パイプラインが IndeterminateNoCrl / IndeterminateOcspUnreachable へ写像する。
したがってCLI と WASM のパリティは偶然ではなく強制された不変条件である。両ホストは 同一の構造を抽出し同一のカーネルを実行するため、同一の入力に対してバイト単位で同一の報告を 生み出す。パリティは抽出器に対するテスト(例えば crates/pverify-cli/tests/extractor_parity.rs、および WASM 側のパリティテスト web/pverify-wasm/src/lib.rs:729 以降)によってピン留めされている。
ページのレイアウト(web/site/index.html)は検証結果セクションを先頭に、ヘッダ直下に 配置し、ドロップ/検証ツールはその下に置く。これにより、検証完了時の判定が、画面外に はみ出して見落とされかねない縦長の形式/アルゴリズム解説セクションの下ではなく、ビューポートの 最上部に表示される。renderReport(app.js)は #result の非表示を解除し scrollIntoView を呼ぶため、読者が下方へスクロールした後に再検証を発火させた場合(検証時刻の 変更など)でも、新しい判定を視界へ引き戻す。検証はドロップ時に自動実行され、「再検証する」 ボタンとその直下のステータス行が手動再実行の経路である。
第 3 の、診断用のハーネス web/verify-cli.mjs は、ビルド済みの同じ site/pkg/ WASM モジュールを Node から駆動する。これによりフィクスチャをブラウザなしで WASM パイプラインを 通して検証できる。これは明示的に製品ではない(CRL プロキシを持たないため、CDP のみの チェーンは INDETERMINATE を報告する。一方ブラウザはプロキシを通じて CRL を取得するため TOTAL_PASSED に到達する)。これは純粋にパリティ診断のために存在し、そのヘッダがその旨を 述べている(web/verify-cli.mjs:9-13)。そのプロセスの終了コードは signatures[0] の ETSI インディケーションを鏡映する(0=TOTAL_PASSED、1=TOTAL_FAILED、 2=INDETERMINATE/署名なし)。
pverify-core のモジュール構成crates/pverify-core/src/lib.rs がカーネルの公開モジュールを宣言する。その責務は 以下のとおりである。
| モジュール | パス | 責務 |
|---|---|---|
verify |
verify.rs |
オーケストレータ:verify_with、形式の判別/ディスパッチ、形式ごとの検証関数、ETSI 集約、ヘッダ導出。 |
cms |
cms/ |
RFC 5652 CMS SignedData(寛容な手書きウォーカー signed_data.rs)、署名付き/署名なし属性の射影、ESS 署名証明書バインディング(ess.rs)、正規化(canonical.rs)、署名検証(verify.rs)。 |
pades |
pades/mod.rs |
埋め込み CMS + /DocTimeStamp に対する PAdES オーケストレーション。/DSS+/VRI 情報の解決。Adobe revocationInfoArchival の射影。 |
xades |
xades.rs |
ホスト抽出された XadesComponents に対する XAdES 検証。 |
jades |
jades.rs |
JadesComponents に対する JAdES(JWS JSON Serialization)検証。 |
asic |
asic.rs |
ASiC コンテナのオーケストレーション:各内部署名を CAdES または XAdES パイプラインへ委譲する。 |
path |
path/ |
RFC 5280 §6 パス検証:バックトラックと DER フィンガープリントによるサイクル検出を伴う DFS のパス構築(mod.rs)、名前制約のステートマシン(name_constraints.rs)、ポリシー処理(policy.rs)、ブリッジ CA 相互認証証明書の経由(bridge.rs)。 |
revocation |
revocation/ |
CRL(RFC 5280、crl.rs)、間接 CRL 検出(indirect.rs)、OCSP(RFC 6960、ocsp.rs)、および失効コンテキスト/ディスパッチャ(mod.rs)。 |
timestamp |
timestamp.rs |
RFC 3161/5816 タイムスタンプトークン検証(TST CMS、インプリント、TSA チェーン、ESS 証明書バインディング)。 |
vrt |
vrt/ |
オブジェクトごとの検証基準時刻エンジン:再帰的な外側カバーグラフ(coverage.rs)、基準時刻の繰り上げ規則(promotion.rs)、derive_vrt(mod.rs)。 |
x509 |
x509/ |
証明書のパース(mod.rs)、Name の扱い(name.rs)、拡張のパース(extensions.rs)。 |
crypto |
crypto.rs |
RustCrypto プリミティブのディスパッチ(verify_with_alg)、ダイジェスト計算(compute_digest、HashAlg)。 |
algorithms |
algorithms.rs |
弱アルゴリズムのフラグ付け(flag_if_weak)とヘッダ集約(aggregate_for_header)。 |
algorithm_policy |
algorithm_policy/ |
オプトインの CRYPTREC/NIST アルゴリズム妥当性ポリシー(policy.rs)、ポリシー抽出(extract.rs)、および mod.rs。 |
ber |
ber/ |
カーネル全体で共有される最小限の BER/DER ヘルパー。 |
report |
report/ |
クローズドスキーマの報告型(mod.rs)、ETSI インディケーション/サブインディケーション(etsi.rs)、スキーマ定数(schema.rs)、VRT のワイヤ型(vrt.rs)、アルゴリズム妥当性のワイヤ型(algorithm_validity.rs)、検証対象オブジェクトの一覧(validation_objects.rs)、時刻フォーマット(time_fmt.rs)。 |
traits |
traits.rs |
Clock / RevocationFetcher / TrustAnchorStore のケイパビリティトレイトと値型。 |
error |
error.rs |
カーネルのエラー型。 |
report 内の意図的な分割に注意されたい。schema.rs は SCHEMA_VERSION("1.10.0")と、 あらゆる報告ヘッダに表出される開示定数 — PATH_VALIDATION_PHASE = "v0.6-bridge-acceptance"、 BRIDGE_CA_SUPPORTED = true、DN_MATCH_METHOD = "rfc4518-minimal-v0.1" — をピン留めする (crates/pverify-core/src/report/schema.rs、 crates/pverify-core/src/report/mod.rs:95-107)。
VerificationRequest から Report へ:端から端までのフロー唯一のオーケストレータは verify_with(crates/pverify-core/src/verify.rs:178)である。 これは (&VerificationRequest, &dyn TrustAnchorStore, &dyn RevocationFetcher) を取り、 Report を返す。このフローは 6 つのステージを持つ。
§3.4/§3.5 で扱った。ホストがコンテナをパースし、時刻を捕捉し、アンカーをロードし、 VerificationRequest を埋める。あらゆる形式チャネルは追加的な Vec / Default である。
verify_with はまず Report::new_header を介してヘッダを初期化する。次に、以下の 優先順位でディスパッチする(verify.rs:189-327)。
asic_signatures が空でなければ、生バイトの判別にかかわらず 入力は ASiC コンテナである(コンテナのバイト列は ZIP マジック PK\x03\x04 で始まり、 そうでなければ CAdES へ流れ込んでしまう)。内部署名ごとに 1 つの SignatureEntry が crate::asic::verify_asic_signature を介して出力される。signature_bytes が PK\x03\x04 で始まるが ホストが ASiC 署名を生成しなかった場合、入力は認識可能な ASiC コンテナでない ZIP で ある。カーネルはこれを CAdES と誤分類させるのではなく、 asic_unsupported_container(INDETERMINATE)を出力する(verify.rs:227)。detect_format(verify.rs:359)が先頭バイトを 検査する。%PDF- → PAdES、UTF-8 BOM 許容の <?xml または裸の <+NameStartChar → XAdES、BOM 許容の先頭 { → JAdES、それ以外は CAdES。JSON と XML は最初の非空白バイト ({ 対 <)において相互排他的であるため、判別が衝突することはあり得ない。各アームは署名ごとに 1 つの SignatureEntry を出力する。PAdES は crate::pades::verify_pdf へルーティングされる。XAdES は各 xades_components エントリを crate::xades::verify_xades を通じて写像する(ホストが何も抽出しなかった場合は xades_unsupported)。JAdES は各 jades_components エントリを crate::jades::verify_jades を通じて写像する(または JAdES 拒否エントリ)。CAdES は verify_cades へルーティングされる。
verify_cades(verify.rs:475)は基準となるパイプラインである。他の形式は共有ヘルパーを 通じてそのフェーズを再利用する。順に以下のとおりである。
SignedDataParser::parse)で CMS SignedData をパースする。signer_info_count > 1 の場合、最初の SignerInfo のみを報告するのではなく、INDETERMINATE / cms_multi_signer_unsupported (verify.rs:496)を返す — これは事実報告の要件(§I)である。029 以前のパーサは署名者 集合を暗黙に切り詰めていたためである。extra_certificates を 追加する(パス構築のために消費されるが、署名者として選択されることは決してない)。SignerInfo.sid(issuer+serial または SKI)によって署名者を選択する。 これは select_signer_by_sid(verify.rs:1096)を介する。不正な/見つからない sid は 報告の足場としてのみヒューリスティックなインデックスへフォールバックする — 判定は いかなる署名/ダイジェストのゲートの前に、SignerIdentifierCheck の結果によって ゲートされる。path/mod.rs)、次に各チェーン ステップの証明書署名を wire_chain_step_signatures(verify.rs:1944)を介して RustCrypto で検証する。verify.rs:1034-1042)を計算する。すなわち、アーカイブタイムスタンプが存在する → CAdESLta、LT ペイロードが存在する → CAdESLt、signature-time-stamp が存在する → CAdEST、それ以外は CAdESBes である。apply_revocations)。crate::vrt::derive_vrt(verify.rs:757)はタイムスタンプトークンから再帰的な外側 カバーグラフを構築し、各オブジェクト — 署名者チェーン、署名者の署名、各 signature_timestamp[i]、各 archive_timestamp[i] — に固有の検証基準時刻を割り当てる Vrt ブロックを返す。署名者チェーンと各 TSA チェーンはその後それぞれ自身の VRT で 再検証され、TSA チェーンは apply_tsa_chain_revocation_for_token(verify.rs:1552)を 介して各オブジェクトの VRT で失効検証される。カバーグラフはその時刻源として request_at のみを読み(Clock::now を決して読まない)、下流の失効/アルゴリズム層によって再導出 されたり変異させられたりすることは決してない(FR-005a)。VRT の章を参照のこと。
aggregate_etsi(verify.rs:2343)は、構造的/署名/ダイジェスト/失効の所見を、 重大度順序 TOTAL_FAILED > INDETERMINATE > TOTAL_PASSED と、ある重大度内での最初の所見 優先の規則をもって、基底の EtsiIndication へと畳み込む。次に階層化されたパスが実行され、 各々は非 TOTAL_FAILED の基底を格下げできるのみであり、証明済みの TOTAL_FAILED を 覆い隠すことは決してできない。すなわち署名者バインディング、コンテンツタイプ、オプトインの アルゴリズム妥当性、そして TSA 失効である。優先順位の梯子はソース中で明示的である — 例えば archive_timestamp_imprint_mismatch は chain_constraints_failure の直後にランクされる (verify.rs:2357-2391)。ETSI 集約の章を参照のこと。
report.signatures を投入した後、verify_with はステップごとの弱アルゴリズムフラグを ヘッダへ集約し(aggregate_for_header)、次に統合された資料インベントリを導出する。 report.validation_objects = crate::report::derive_validation_objects(&report) (verify.rs:341)。derive_validation_objects (crates/pverify-core/src/report/validation_objects.rs:191)は純粋な &Report の 読み取りである — ネットワークなし、新たな暗号処理なし(あらゆるダイジェストは既にフィールド である)。実際に参照された証明書、CRL、OCSP レスポンス、コンテンツオブジェクトを列挙し、 各々を ValidationObjectOrigin の出所(プロビナンス)値でタグ付けする。次にホストが Report を JSON へシリアライズする(CLI は src/render.rs、WASM は serde_json::to_string_pretty)。両ホストは同一のカーネルを実行するため、同一の入力に 対して出力はバイト単位で同一である。
/DSS+/VRI の特殊化(ブランチ 034)PAdES は、文書レベルの検証情報により、そのステージ 3 が実質的に異なる唯一の形式である。 そのため、判定ロジックに触れることなく追加的機能がアーキテクチャをどのように貫くか、その 最も明快な実例として、ここで述べる価値がある。
ブランチ 034 以前、pades::verify_pdf は失効パイプラインに空の埋め込みスライスを供給して いた。そのため、証拠が完全に揃ったオフラインの PAdES-LT/LTA が INDETERMINATE / revocation_not_checked_offline で行き詰まっていた。ブランチ 034 は、カタログレベルの /DSS 辞書(/Certs、/CRLs、/OCSPs)の すべてのインクリメンタルアップデート リビジョンにわたる和集合を DER で重複排除したホスト側抽出、加えて各署名の /Contents の 大文字 16 進 SHA-1 をキーとする /VRI サブ辞書の抽出を追加した。これは型 PdfValidationData の新たな追加的 VerificationRequest.pdf_validation_data (crates/pverify-core/src/pades/mod.rs:80)としてカーネルへ届けられ、ネイティブ CLI (pverify-cli/src/pdf.rs::extract_validation_data)とブラウザホスト (web/pverify-wasm/src/lib.rs::extract_pdf_validation_data)によって同一に投入される。
pades::verify_pdf の内部でカーネルは、署名オブジェクトごとに、resolve_material (crates/pverify-core/src/pades/mod.rs:148)を介して優先される検証情報を解決する。 すなわち、オブジェクトの /Contents SHA-1 をキーとする /VRI エントリが優先される (出所 PdfVri)。それがなければ、文書レベルの /DSS プールがフォールバックである (出所 PdfDss)。解決された CRL/OCSP DER は、以前は &[],&[] を受け取っていた既存の apply_revocations(署名者チェーン)および apply_tsa_chain_revocation_for_token (各タイムスタンプ)の呼び出しへ、各オブジェクト自身の VRT で供給される。Adobe revocationInfoArchival(OID 1.2.840.113583.1.1.8)は CMS から射影され、 signature_embedded 情報として併合される。
いくつかのアーキテクチャ上の性質が設計によって保持される。
RevocationOutcome 語彙を生み出す — 失効した署名者は既存の TOTAL_FAILED、失効した TSA は 033 の既存の revoked_no_poe (INDETERMINATE)である。新たな結果も新たなサブインディケーションも追加されなかった。/Certs はアンカーになることは決してない。 DSS /Certs は中間/リーフのパス構築 候補にすぎず、トラストアンカーへ昇格されることは決してない(PdfValidationData.dss_certs のドキュメントコメント、pades/mod.rs:81-83)。/VRI 辞書のキーのみであり、 決してセキュリティプリミティブではない。情報は追加的な証拠(CRL は issuer+serial で 照合され、OCSP は certID で照合される)であるため、衝突は追加的な証拠を誤ルーティング し得るのみであり、「good」な結果を捏造することは決してできない(sha1_uppercase_hex のドキュメントコメント、pades/mod.rs:183-188)。ValidationObjectOrigin バリアント pdf_dss と pdf_vri であり、supplied_input の後に追加された (crates/pverify-core/src/report/validation_objects.rs:113-118)。016 の導出が CRL ごと/OCSP ごとの出所タグ(PdfRevocationSource::{PdfDss,PdfVri})をそれらへ 写像する(validation_objects.rs:308-336)。クローズドな列挙バリアントの追加は追加的な MINOR スキーマバンプであり、これがスキーマを 1.5.0 から 1.7.0 へと進めたものである。ディスパッチと形式ごとの検証器はスコープについて誠実である。以下の表は、ツールが何を 検証するか、対して何を未対応としてフラグ付けするかを述べる。詳細は形式ごとの章に 記載されている。
| ファミリ | 標準 | 検証されるレベル | 明示的に未対応(→ INDETERMINATE) |
|---|---|---|---|
| CAdES | ETSI EN 319 122 | B-B/BES、B-T(signature-time-stamp)、B-LT、B-LTA(§6.3.4 インプリント再計算を伴う archive-time-stamp-v3) | マルチ署名者 CMS(cms_multi_signer_unsupported) |
| PAdES | ETSI EN 319 142 | 埋め込み CMS + /DocTimeStamp に対する B-B/B-T/B-LT/B-LTA。/DSS、/VRI、Adobe revocationInfoArchival を消費(ブランチ 034) |
— |
| XAdES | ETSI EN 319 132 | B-T/B-LT タイムスタンプと B-LTA xades:ArchiveTimeStamp インプリント検証(ETSI TS 101 903 v1.4.2 Annex A.1.5; SignatureFormat::XadesBLta、ブランチ 036)を伴う enveloped B-B。bergshamra-c14n を介した Exclusive C14N |
detached/enveloping の XAdES、非 exclusive C14N(xades_unsupported_canonicalization) |
| JAdES | ETSI TS 119 182 | JWS-JSON-serialization の B-B | B-T 以降および sigD/Compact serialization(INDETERMINATE で拒否) |
| ASiC | ETSI EN 319 162 | ASiC-S / ASiC-E。各内部署名を CAdES または XAdES へ委譲 | 非 ASiC ZIP(asic_unsupported_container) |
CAdES の形式繰り上げの梯子は verify.rs:1034-1042 にある。JAdES および XAdES の拒否 エントリは、それぞれ crate::jades::jades_unsupported_entry および xades_unsupported_entry によって出力される。レベルやバリアントがスコープ外である場合、 アーキテクチャは精密なサブインディケーションを伴って INDETERMINATE へ格下げし(§3.1.2)、 捏造された失敗を決して出さない。
以下の不変条件は、いずれかひとつのモジュールの性質ではなく、アーキテクチャそれ自体の性質 であり、各々が機械的またはテストによって強制される。
pverify-core は no_std + alloc で あり、unsafe を禁止し、wasm32-unknown-unknown 向けにビルドされ、RustCrypto のみで ある — ツリーゲートと wasm ビルドステップによって強制される。lopdf/zip/flate2/bergshamra を決してリンクしない — ゲートによって表明される。traits.rs を通じてのみである。derive_vrt と derive_validation_objects は純粋な計算である。SubIndication、RevocationOutcome、 ValidationObjectOrigin は report-schema.json に 1 対 1 で写し取られたクローズドな 列挙である。値や任意フィールドの追加は MINOR、削除や再利用は MAJOR である。非推奨の バリアント(例えば bridge_ca_required_unsupported)は、保存された報告のデシリアライズの ために保持されるが、出力されることはない。schema_version を除いてバイト単位で同一のまま 残す(例えば DSS を持たない PDF、アルゴリズムポリシーがオフ、空の埋め込み失効チャネル)。--offline はソケットを開かず、レスポンダ接触を 決して捏造しない。誠実な IndeterminateRevocationOffline / revocation_not_checked_offline の結果が、従前の不誠実な ocsp_responder_unreachable の出力を置き換えた。verify_with を実行する。パリティテストが抽出器の出力形状をピン留めする。aggregate_etsi は重大度順序の 基底を設定する。後続の層は非 TOTAL_FAILED の基底を格下げするのみである。request_at のみを読む純粋な 再帰的外側カバーエンジンを介して、それぞれ自身の導出された VRT で判定される。カバー グラフは下流で再導出されたり変異させられたりすることは決してない。pverify-eutl/aatl/ anchor-inventory は、--trust-anchors を通じて再投入可能な通常の TrustAnchor 値を出力する。取り込みはパース時にネットワーク I/O を行わない。--at が与えられない限り検証時刻を起動時に 一度だけ捕捉し、--at は常に優先される。これらの不変条件は、まとめて、報告を監査可能な事実たらしめるものである。すなわち、同一の 入力は常に同一の報告を生み出し、暗号とパースのサーフェスは境界付けられ検査可能であり、 ツールは証明できる以上のことを決して主張しない。
本章では、pverify が不透明なバイト列をどのようにして経路付けされた検証へと変換するかを説明する。本章が扱うのは、二層からなる分類パイプライン(どの構造パーサを実行するかを決定するホスト側のスニフと、どの検証アームに入るかを決定するカーネル側のスニフ)、両者の優先順位ルール、detached(分離型)コンテンツと attached(添付型)コンテンツの取り扱い、ASiC-S/ASiC-E コンテナの検出、そして各フォーマット別検証器へのディスパッチである。以下に述べるすべての挙動上の主張はソースに根拠を持つ。中核を担う分類器は pverify_core::verify::detect_format(crates/pverify-core/src/verify.rs:359)であり、そのホスト側ミラーは crates/pverify-cli/src/main.rs と web/pverify-wasm/src/lib.rs に分割されている。
本章を通じての設計原則は、誠実で決定論的なディスパッチである。すべての入力はちょうど一つのフォーマットに割り当てられ、その割り当てはバイト列の小さなプレフィックスから行われ(よって安価かつ再現可能である)、ある系統に合致するもののツールがサポートするレベルでは検証できない入力は、別のアームへ静かに誤経路付けされたり pass/fail を捏造されたりするのではなく、正確なクローズドな列挙(閉じた語彙)のサブインディケーションを伴って INDETERMINATE へと degrade する(憲法 §I「断言できないことは断言しない」。憲法上の枠組みについては第2章を、本章が動作する範囲となるアーキテクチャ境界については第3章を参照)。
pverify は検証カーネルの内部で PDF・XML・ZIP・JSON をパースできない。カーネルは #![no_std] であり、コンテナ/文書パーサのリンクを禁じられている(憲法 §V/§VI。クレートトポロジと、それを強制する scripts/cargo-tree-gate.sh ガードについては第3章 §3.x を参照)。したがって分類は、設計上、二度行われ、二つの層は一致しなければならない。
ホスト側スニフ(構造パース選択)。 ネイティブ CLI とブラウザの WASM ホストは、入力の先頭バイトを検査してどの構造抽出器を実行するかを決定する — PDF には lopdf、XML には pverify-xades、ZIP には pverify-asic、JSON には pverify-jades — とともに、CLI のコンテンツソースルール(§4.4)を強制する。ホストは VerificationRequest のフォーマット固有チャネル(pdf_signature_dicts、pdf_validation_data、xades_components、jades_components、asic_signatures)を、抽出したモデルに依存しないバイト構造で充填する。
カーネル側スニフ(検証アームのディスパッチ)。 verify_with(crates/pverify-core/src/verify.rs:178)は同じ request.signature_bytes を再分類し、ちょうど一つの検証アームへ経路付けする。カーネルは常に自身のスニフ結果に合致するリクエストチャネルのみを参照するため、(常に空である)pdf_validation_data を伴う非 PDF 入力は、そのフィールドを一切持たなかった入力と区別がつかない。
両層とも先頭バイトのみを読む — 分類にファイル全体のスキャンは不要である — そして両者はバイト単位で同一の述語を用いる。CLI の looks_like_xml(crates/pverify-cli/src/main.rs:399)と looks_like_json(main.rs:420)は、カーネルの looks_like_xml/looks_like_json(verify.rs:392/375)と一行一行が等価であり、WASM ホストの複製(web/pverify-wasm/src/lib.rs:419/439)には明示的に「mirrors pverify_core::verify」というコメントが付されている。この意図的な三重化が、CLIとWASMのパリティ不変条件(第3章)である。ネイティブおよびブラウザのホストは同一の構造を抽出しなければならず、カーネルは同一のアームへ到達しなければならない。よって所与の入力は、いずれのホスト上でもバイト単位で同一のレポートを生成する。
ホストの順序(ZIP → PDF → XML → JSON → CAdES)とカーネルの順序(ASiC チャネル → ZIP → detect_format)との間の非対称性は意図的なものであり、§4.5 で説明する。カーネルは detect_format の前にASiC をディスパッチしなければならない。なぜなら ASiC コンテナのバイト列は ZIP マジックで始まるが、detect_format のスニフはこれを認識せず、さもなければそれを CAdES アームへ素通りさせてしまうからである。
detect_formatカーネルの分類器は、先頭バイトに対する四方向の判定である(crates/pverify-core/src/verify.rs:359)。
fn detect_format(bytes: &[u8]) -> DetectedFormat {
if bytes.starts_with(b"%PDF-") {
return DetectedFormat::Pdf;
}
if looks_like_xml(bytes) {
return DetectedFormat::Xades;
}
if looks_like_json(bytes) {
return DetectedFormat::Jades;
}
DetectedFormat::Cades
}DetectedFormat はクローズド列挙型 {Pdf, Xades, Jades, Cades}(verify.rs:347)である。その順序は重要であり、ソースに記録されている。PDF が最初にテストされるのは %PDF- が一義的なバイナリマーカーだからである。XML と JSON は最初の非空白バイト(< 対 {)において相互排他的であるため、両者の相対的な順序が衝突を引き起こすことはない。そして CAdES は残余のデフォルトである — PDF/XML/JSON として認識されないものはすべて DER エンコードされた CMS SignedData として扱われ、verify_cades へ手渡される。ZIP は detect_format の分岐ではないことに注意されたい。ZIP 入力は verify_with の中でより早期に捕捉され(§4.5)、この関数に到達することはない。スニフは構造の残りを検証しようとはしない — 不正な形式の CMS であっても CAdES へ経路付けされ、そこで誤分類ではなく cades_parse_failed エントリとなる。
| フォーマット | 述語 | 受理対象 | 根拠 |
|---|---|---|---|
bytes.starts_with(b"%PDF-") |
リテラルの PDF ヘッダマーカー | verify.rs:360 |
|
| XAdES | looks_like_xml |
任意の UTF-8 BOM、続いて <?xml、または裸の < に続く XML の NameStartChar(is_ascii_alphabetic() | _ | :) |
verify.rs:392 |
| JAdES | looks_like_json |
任意の UTF-8 BOM、続いて空白(SP/TAB/CR/LF)、続いて { |
verify.rs:375 |
| CAdES | 残余のデフォルト(述語なし) | DER CMS を含むその他すべて | verify.rs:369 |
監査人にとって記録に値する二つの精度上の論点がある。
looks_like_json は先頭の { に対してのみ true を返し、[ に対しては決して返さない。これは JAdES にとって正しい。JWS JSON Serialization(RFC 7515 §7.2)は常に JSON オブジェクトであり、裸の配列となることはない。したがって先頭の [ は非 JAdES の JSON 文書となり、意図的に JAdES アームではなく CAdES 残余へ経路付けされる。この挙動は WASM テストで表明されている(web/pverify-wasm/src/lib.rs:781、!looks_like_json(b"[]"))。looks_like_xml は裸の < に続く ASCII 文字、_、または : を受理する。ソースのコメントはこれを「ASCII subset is sufficient for all GPKI-shaped fixtures」(verify.rs:402)と注記している。ds:Signature や xades:… を根とする文書、あるいは <?xml プロローグを伴う任意の XML は正しく分類される。仮想的に、ルート要素名が非 ASCII の Unicode NameStartChar で始まり、かつプロローグと BOM を省略した XML は、XML としてスニフされないであろう。そのような入力が GPKI/eIDAS コーパスに生じることはなく、その失敗モードは無害である(それは CAdES へ素通りし、偽の pass ではなく cades_parse_failed という事実を生成する)。UTF-8 BOM(EF BB BF)は XML テストと JSON テストの両方の前に剥がされる(verify.rs:376、verify.rs:394)ため、BOM をプレフィックスとして持つ XAdES または JAdES 文書は、その真の最初のコンテンツバイトに基づいて分類される。
verify_with におけるフォーマット別ディスパッチASiC の捕捉(§4.5)の後、verify_with は detect_format の結果に対してマッチを行い、Vec<SignatureEntry> を構築する — 文書内の署名一つにつき一エントリ(crates/pverify-core/src/verify.rs:240)。すべてのアームが遵守する契約は、消費者は常に確定した事実を受け取る、というものである。ホストの抽出が空であっても、ちょうど一つの説明的エントリを生み、空の配列を生むことは決してない。
DetectedFormat::Pdf)PDF 入力は crate::pades::verify_pdf(verify.rs:241)へ経路付けされ、ホストが抽出した pdf_signature_dicts と pdf_validation_data、トラストストア、フェッチャ、検証時刻、実際の VerificationMode(よって --offline は OCSP 接触を捏造するのではなく尊重される — §I のギャップに対する 015 の修正、verify.rs:255)、正規の required_policies 集合(verify.rs:259、よって --required-policy が PAdES に対して強制される)、そしてオプトインのアルゴリズムポリシー(verify.rs:261)とともにスレッドされる。
verify_pdf(crates/pverify-core/src/pades/mod.rs:242)は記述子を反復し、各 /Sig 辞書につき一つの SignatureEntry を生成し、カバーする各 /DocTimeStamp トークンを直前の最新の署名に付加する(mod.rs:309)。これは埋め込み CMS と /DocTimeStamp(PAdES のアーカイブタイムスタンプ機構)にわたって PAdES B-B、B-T(signature-time-stamp)、B-LT、B-LTA をサポートし、ブランチ 034 以降は文書レベルの /DSS(/Certs//CRLs//OCSPs)、/VRI、および Adobe revocationInfoArchival の検証材料を消費する(解決の詳細については PAdES 検証の章を参照)。ホストが署名辞書をまったく抽出しなかった場合、verify_pdf は空のベクタではなく単一の説明的エントリを返す。no_signature_entry(mod.rs:475)は、サブインディケーション PdfByteRangeMalformed とコンテキスト文字列 "pdf-no-signature-dict"(mod.rs:496)を伴う INDETERMINATE を発する。
PAdES は構造上常に attached である。/ByteRange は PDF 本体内のバイトを参照するため、すべての PAdES SignatureEntry は不変に content_source = Embedded を担持する(mod.rs:483)。これが、CLI が PDF 入力と --detached-content の組み合わせを禁ずる理由である(§4.4)。
DetectedFormat::Xades)XML 入力はコンポーネントごとのループ(verify.rs:263)へ経路付けされる。request.xades_components が空である場合 — 入力が XML としてスニフされたがホストが ds:Signature を抽出しなかった、あるいはホストが分類しきれない抽出エラーに遭遇した場合 — アームは xades_unsupported_entry(verify.rs:272、verify.rs:413 に定義)へフォールバックする。これはサブインディケーション XadesUnsupported を伴う INDETERMINATE である。そうでなければ、抽出された各コンポーネントにつき一度 crate::xades::verify_xades を呼び出す。pverify は、監査済みの bergshamra-c14n クレートを介した Exclusive XML 正規化(C14N)を用いて、B-T/B-LT タイムスタンプおよび B-LTA ArchiveTimeStamp インプリントを伴う enveloped XAdES B-B(ブランチ 036、SignatureFormat::XadesBLta)を検証する。detached/enveloping XAdES および非 exclusive な C14N は INDETERMINATE へ degrade する(XAdES の章を参照)。この拒否へのフォールバック設計は、検証可能な XAdES 署名ではない XML 文書であっても、静かな空の結果ではなく、確定した機械可読の事実(FR-009)を生成することを意味する。
DetectedFormat::Jades)JSON 入力は類似のコンポーネントごとのループ(verify.rs:296)へ経路付けされる。空の request.jades_components は jades_unsupported_entry(verify.rs:304)へフォールバックする。そうでなければ各コンポーネントは crate::jades::verify_jades によって検証される。pverify は JWS JSON Serialization の B-B のみを検証する。JAdES 抽出器は、スコープ外の形態を誤検証するのではなく、明示的に分類し拒否する(crates/pverify-jades/src/parse.rs:212)。sigD detached ペイロード参照は JadesUnsupportedReason::DetachedSigD(parse.rs:232)へマップされ、より高いベースラインレベル(B-T+)は HigherLevel(parse.rs:250)へマップされ、JAdES ベースラインプロパティを担持しない素の JWS は NotJadesBaseline(parse.rs:261)へマップされる。Compact serialization も同様にスコープ外である。各拒否は誠実な INDETERMINATE の事実であり、§I「断言できないことは断言しない」の規律に一貫している。RFC 7797 の b64:false 非エンコードペイロードは抽出器によって処理されるが、それは b64 が crit にも現れる場合のみである(parse.rs:116)。要求される crit 宣言を伴わない b64:false は、静かに受理されるのではなく誠実に取り扱われる。
DetectedFormat::Cades、残余のデフォルト)PDF/XML/JSON としてスニフされず(かつ ZIP として捕捉されなかった)ものはすべて verify_cades(verify.rs:326、verify.rs:475 に定義)へ手渡される。これは単一エントリのアームである。ちょうど一つの SignatureEntry を生成する。verify_cades は SignedDataParser を介して入力を CMS SignedData(RFC 5652)としてパースする。パース失敗は cades_parse_failed_entry(verify.rs:483)となる — 誤分類された非 CMS 入力(例えば PDF/XML/JSON でない任意のバイナリ)は、偽りの出力を生成するのではなく、ここで清潔に失敗することに注意されたい。
このアームには重要な誠実性ルールが存在する。マルチ署名者の CMS は意図的に拒否される。 parsed.signer_info_count > 1 の場合、verify_cades は cades_multi_signer_entry(verify.rs:496-497)を返す。これはサブインディケーション cms_multi_signer_unsupported を伴う INDETERMINATE である。ソースはその根拠を記録している。029 以前のパーサは最初の SignerInfo のみを静かに保持し、N>1 の署名者を持つ CMS を一つしか存在しないかのように報告していた — これは、副署ワークフローや監査のために検証者(信頼当事者)が必要とする署名者集合の証拠を隠蔽してしまう事実報告のギャップであった(verify.rs:487-495)。signatures[] が SignerInfo 一つにつき一エントリのモデルへ拡張されるまで、誠実な答えは、切り詰められた署名者集合を検証することではなく拒否することである。CAdES は BES/B-B、B-T(signature-time-stamp)、B-LT、B-LTA(ETSI EN 319 122-1 §6.3.4 のインプリント再計算を伴う archive-time-stamp-v3)にわたってサポートされる。CAdES の章を参照。
CAdES は detached となりうる唯一の系統である — すなわち署名されたペイロード(eContent)が CMS の外部に存在し、署名とともに供給されなければならない場合である。PAdES、XAdES(enveloped)、JAdES、ASiC はいずれも構造的にペイロードを担持または参照するため、detached コンテンツの問題は CAdES に対してのみ生じ、それは完全にホスト層で解決される(それはファイル I/O と CLI フラグの内省を要し、カーネルはそれを実行できない)。このロジックは、crates/pverify-cli/src/main.rs:554-606 において四セルからなる FR-027/FR-028 コンテンツソースマトリクスを実装する。
CLI はまず安価なプレフィックスチェックで系統を判定する。is_pdf(main.rs:535)、is_asic(pverify_asic::is_zip、main.rs:540)、looks_xml(main.rs:541)、looks_json(main.rs:553)である。これら四つのいずれかであれば、コンテンツソースマトリクスは完全にバイパスされ(main.rs:554)、content_source_override = None となる — PAdES は構造的に attached であり、XAdES はコンテンツソース解決の前に拒否され、ASiC は eContent を持たず --detached-content を取らず、JAdES はそのペイロードを JWS 構造内に埋め込む。入力がこれら四つのいずれでもない場合 — すなわち CAdES と推定される場合 — のみマトリクスが動作する。
明白に誤った組み合わせを防ぐためのハードな事前チェックがある。--detached-content を PDF 署名とともに供給することは、いかなるマトリクスロジックよりも前の呼び出しエラー(終了コード 2)である(main.rs:507-512)。なぜなら PAdES は決して detached ではないからである。
CAdES 入力に対して、CLI は SignedDataParser で CMS 構造を覗き、存在すれば非空の eContent を抽出し(main.rs:565-568)、続いて (eContent が存在するか?, --detached-content が供給されたか?) に対してマトリクスを適用する。
eContent |
--detached-content |
挙動 | 根拠 |
|---|---|---|---|
| 存在する | 供給され、バイト等価 | 受理。content_source = Embedded、content_source_redundant = Some(true)(冗長性はレポートにフラグされる) |
main.rs:570-577 |
| 存在する | 供給され、相違 | 呼び出しエラー(終了コード 2): 「--detached-content does not match the signature's embedded eContent (FR-027 conflict)」 |
main.rs:578-583 |
| 存在する | なし | attached CAdES。content_source = Embedded(冗長性分岐が誤作動しないよう固定される) |
main.rs:585-591 |
| 不在 | 供給される | detached CAdES(v0.1 の経路)。content_source = Detached |
main.rs:592-596 |
| 不在 | なし | 呼び出しエラー(終了コード 2): 「detached CAdES signature requires --detached-content (FR-028)」 |
main.rs:597-604 |
最後のセルが本節の中心的な誠実性ルールである。pverify は空のペイロードを静かに検証することは決してない。コンテンツが供給されない detached CAdES 署名は、ゼロバイトに対して検証されるのではなく CLI で拒否される(main.rs:599)。衝突セル(存在するが相違する)も同様に、二つの矛盾するペイロードのうちどちらを呼び出し者が意図したかを推測するのではなく拒否する。
解決されたタグは content_source / content_source_redundant としてリクエストにスレッドされる(第3章 §リクエスト組み立て)。オーバーライドが None の場合(PDF/XML/JSON/ASiC のケース、あるいは CLI が覗けなかった任意の CAdES)、検証パイプラインは自身の構造的デフォルトを適用する。CAdES は detached_content_bytes より埋め込みの eContent を優先し、拒否/PAdES エントリは常に Embedded である(crates/pverify-core/src/verify.rs:121-131)。
ASiC(EN 319 162)は CAdES または XAdES 署名を ZIP コンテナ内にパッケージするため、ASiC 入力は ZIP のローカルファイルヘッダマジック PK\x03\x04 で始まる。そのマジックは detect_format の分岐ではないため、ZIP はさもなければ CAdES 残余のデフォルトへ素通りして誤分類されてしまう。pverify は、ホストとカーネルにわたって分割された二段階の優先順位ルールでこれを防ぐ。
CLI は任意の ZIP 入力を、PDF/XML/CAdES スニフの前にASiC 抽出器へ経路付けする(crates/pverify-cli/src/main.rs:723)。pverify_asic::is_zip がプレフィックステストである(crates/pverify-asic/src/lib.rs:35、bytes.starts_with(b"PK\x03\x04"))。成功時には、抽出された Vec<AsicSignature> が request.asic_signatures へスレッドされる。NotAsic/抽出失敗の場合、チャネルは空のままにされる(.unwrap_or_default())。よってカーネルは ASiC 署名を持たない ZIP マジックを見て、入力を CAdES として誤分類するのではなく、誠実な asic_unsupported_container の事実を発する(main.rs:716-727)。WASM ホストはバイト単位で同一の経路付けを行う(web/pverify-wasm/src/lib.rs:362)。
verify_with はカーネルでも同じ優先順位を強制する(crates/pverify-core/src/verify.rs:189-235)。これは detect_format が呼ばれるよりも前の、二つの順序付けられたチェックである。
asic_signatures が非空(verify.rs:195): ホストが ASiC 署名を見つけたので、それぞれが crate::asic::verify_asic_signature を介して独立に検証され — 内側の署名一つにつき一つの SignatureEntry — verify_with は直ちにリターンする。内側の署名は既存の detached CAdES / XAdES パイプラインへ委譲される(verify.rs:194 のコメントがこれを明示している)。verify.rs:227): request.signature_bytes.starts_with(b"PK\x03\x04") であるが ASiC チャネルが空の場合、入力は認識可能な ASiC コンテナではない ZIP である(あるいはホストが分類しきれないハードな抽出失敗である)。verify_with は単一の asic_unsupported_entry(INDETERMINATE、asic_unsupported_container)を発してリターンし、それが detect_format に到達して CAdES へ誤経路付けされることは決してない(verify.rs:222-235)。両チェックがともに失敗した後にのみ、verify_with は detect_format を呼び出す(verify.rs:239)。これが、カーネルの分類順序(ASiC チャネル → ZIP マジック → detect_format)がホストの順序(ZIP → PDF → XML → JSON → CAdES)と異なる構造的理由である。
pverify_asic::extract(crates/pverify-asic/src/lib.rs:50)が実行されると、それは ZIP をパースし、続いて detect::detect(crates/pverify-asic/src/detect.rs:76)でコンテナを分類する。これは、パッケージングの逸脱をすべて静かに許容するのではなく記録する、ベストエフォートの認識である(FR-001a)。検出には三つの出力がある。プロファイル(ASiC-S 対 ASiC-E)、主要な署名系統(CAdES 対 XAdES)、そして AsicDeviation のリストである。
プロファイル(ASiC-S 対 ASiC-E)。 適合する mimetype エントリは権威的である。application/vnd.etsi.asic-s+zip → ASiC-S、application/vnd.etsi.asic-e+zip → ASiC-E(detect.rs:14-15、detect.rs:85-87)。mimetype はさらに EN 319 162 に従い最初のエントリかつ STORED(無圧縮)でなければならない。違反は MimetypeNotFirstEntry / MimetypeCompressed の逸脱として記録されるが、それ自体ではコンテナを失格にはしない(detect.rs:97-112)。欠落または非適合の mimetype は MimetypeMissing / MimetypeNonConformantValue を記録し、レイアウト推論へフォールバックする(detect.rs:88-96、detect.rs:136)。
レイアウト推論 infer_profile(detect.rs:147)は、mimetype が不在または非適合のときに適用される。任意の ASiCManifest の存在は ASiC-E を含意する(detect.rs:149)。そうでなければ、平易に命名された単一の署名ファイル(META-INF/signature.p7s または META-INF/signatures.xml、数値サフィックスなし)は ASiC-S を含意し、一方で数値サフィックスまたは二つ以上の署名は ASiC-E を含意する(detect.rs:157-168)。
系統(CAdES 対 XAdES)。 検出は META-INF/ のエントリ名を大文字小文字を区別する述語でスキャンする。is_cades_signature は META-INF/signatureNNN.p7s にマッチし(detect.rs:45)、is_xades_signature は META-INF/signaturesNNN.xml にマッチし(detect.rs:54)、is_asic_manifest は META-INF/ASiCManifest*.xml にマッチするが、ASiCArchiveManifest(LTA、スコープ外)と OpenDocument の manifest.xml は意図的に除外する(detect.rs:65-70)。family フィールドは主要な系統を記録する(両方が存在する場合は CAdES が優先される、detect.rs:129)が、ディスパッチには使われない — それは診断用フィールドである(detect.rs:35-39)。代わりに extract は実際に存在するすべての系統を処理する。出力を CAdES 署名(lib.rs:65)と XAdES 署名(lib.rs:68)で独立に拡張するため、混在コンテナ(signature*.p7s と signatures*.xml の両方)はいかなる署名も取りこぼさない(FR-014/FR-020、lib.rs:56-70)。
非 ASiC の境界。 detect::detect は、アーカイブが CAdES 署名ファイルも XAdES 署名ファイルも持たないとき、None を返し — extract はそれを AsicError::NotAsic(lib.rs:52-54)へマップする(detect.rs:122-124)。ソースは、適合する mimetype だけでは不十分であることを明示している。検証すべき署名材料が存在しなければならない(detect.rs:119-121)。この NotAsic の帰結こそが、ホストに asic_signatures を空のままにさせる原因であり、カーネルはそれを asic_unsupported_container として表出する(§4.5.2)。ASiC-E CAdES コンテナでは、CAdES は ASiCManifest のバイトに署名し、各 DataObjectReference のダイジェストはカーネルで再計算される。ASiC-S CAdES コンテナでは、CAdES は単一の非 mimetype データオブジェクトに直接署名する(lib.rs:110-139)。
どのアームが実行されたかによらず、すべての経路は Vec<SignatureEntry> を生成し、verify_with 内の共通の末尾へ再収束する(crates/pverify-core/src/verify.rs:329-342、および verify.rs:212-218 の ASiC 早期リターンにおける等価物)。ステップごとの弱アルゴリズムフラグは aggregate_for_header を介してヘッダへ畳み込まれ、統合された資料インベントリは derive_validation_objects によって完成したレポートツリーから導出される — これはネットワークアクセスも新たな暗号も加えない純粋な &Report の読み取りである(verify.rs:341)。詳細な ETSI インディケーションの集約(aggregate_etsi、重大度の順序付けと決してマスクしない層化)は第5章 §5.7 および結果モデル(第14章)で扱う。分類の観点からの鍵となる不変条件は、出力の形状 — 一つ以上の SignatureEntry 値とヘッダ — が、どのフォーマットが検出されたかによらず同一である、ということである。よって下流の消費者は単一の一様なスキーマをパースする。
分類とディスパッチの層は、配置も検証もサポートレベルでできない入力に対して判定を捏造することは決してない。下表は、この層に由来する誠実な degradation の帰結を集めたものである。
| 状況 | 帰結 | インディケーション / サブインディケーション | 根拠 |
|---|---|---|---|
| 認識可能な ASiC コンテナではない ZIP | 一エントリ | INDETERMINATE / asic_unsupported_container |
verify.rs:227-235; detect.rs:122 |
| 署名辞書を持たない PDF | 一エントリ | INDETERMINATE / pdf_byte_range_malformed(ctx pdf-no-signature-dict) |
pades/mod.rs:475-499 |
抽出可能な ds:Signature を持たない XML |
一エントリ | INDETERMINATE / xades_unsupported |
verify.rs:271-273, 413 |
JAdES ベースラインを持たない JSON(または sigD/compact/B-T+) |
一エントリ | INDETERMINATE / jades_unsupported_profile(DetachedSigD/HigherLevel/NotJadesBaseline) |
verify.rs:303-305; parse.rs:212-261 |
マルチ署名者 CMS(N>1 の SignerInfo) |
一エントリ | INDETERMINATE / cms_multi_signer_unsupported |
verify.rs:496-497 |
| パース不能な CMS(誤分類されたバイナリを含む) | 一エントリ | パース失敗エントリ | verify.rs:483 |
PDF 入力 + --detached-content |
終了コード 2(呼び出しエラー) | (CLI 拒否) | main.rs:507-512 |
detached CAdES、--detached-content なし(FR-028) |
終了コード 2(呼び出しエラー) | (CLI 拒否) | main.rs:597-604 |
--detached-content ≠ 埋め込み eContent(FR-027) |
終了コード 2(呼び出しエラー) | (CLI 拒否) | main.rs:578-583 |
二つの失敗カテゴリの区別には原則がある。CLI 呼び出しエラー(終了コード 2) は、リクエストそのものが矛盾している、または仕様が不足している場合に予約されている — PDF は detached になりえず、detached CAdES はそのコンテンツを必要とし、二つの矛盾するペイロードがともに署名対象であることはありえない。INDETERMINATE レポートエントリ(終了コード 0) は、リクエストは整形式であるが pverify がそのサポートレベルで事実を確立できない場合に予約されている — 認識されないコンテナ、サポートされない JAdES serialization、現行スキーマが表現できないマルチ署名者集合。後者は機械可読の事実として報告されるため、下流の監査人はなぜ判定に至らなかったのかを正確に見ることができ、これは憲法 §I に一貫している。
detect_format の前に捕捉されるため ZIP マジックが素通りすることは決してない。SignatureEntry を返す。INDETERMINATE へ degrade し、捏造された TOTAL_FAILED/TOTAL_PASSED となることは決してない(憲法 §I)。本章では、pverify が RFC 5652 Cryptographic Message Syntax(CMS)の SignedData をどのように解析し、その上に構築された CAdES(ETSI EN 319 122)高度電子署名(AdES)をどのように検証するかを規定する。CMS SignedData はツール全体の根幹をなす構造である。CAdES の検証はこれを直接対象とし、PAdES(第6章)はこれを PDF の /Contents に埋め込み、RFC 3161 タイムスタンプトークン(第12章)はそれ自体が SignedData であり、ASiC(第7章)はその内部 CAdES 署名を本章で説明するのと同一のパイプラインに委譲する。したがって CAdES のパスは、他の形式の章がそれに照らして記述される際の参照実装である。他の形式が「CAdES パイプライン」に委ねると述べる箇所は、§5.7 に文書化されたオーケストレーションを指す。
本章で参照されるすべてのコードは、no_std + alloc の、I/O を行わない検証カーネル pverify-core 内に存在する(カーネルとホストの境界については第3章を参照)。構造的な CMS パーサは crates/pverify-core/src/cms/ にあり、署名ごとのオーケストレーションは crates/pverify-core/src/verify.rs の関数 verify_cades にある。JNSA が必須とする2つのバインディングチェック — 署名者識別 + ESS署名証明書バインディング(slice 025)と content-type バインディング(slice 026)— はそのオーケストレーションに統合されており、§5.5 および §5.6 に文書化されている。
pverify は4つの CAdES ベースラインレベルを認識し、verify_cades によってレポートの SignatureFormat 列挙へ投影する(crates/pverify-core/src/verify.rs:1034-1042):
レポート SignatureFormat |
ETSI ベースライン | verify_cades における認識条件 |
標準 |
|---|---|---|---|
CAdESBes |
B-B / B-ES | unsigned-attribute 拡張が存在しない | EN 319 122-1 §6.1 |
CAdEST |
B-T | 少なくとも1つの signature-time-stamp トークンがあり、LT/LTA ペイロードがない |
EN 319 122-1 §6.2 |
CAdESLt |
B-LT | certificate-values/revocation-values が存在する(またはアーカイブ TS の予約スロットが空でない)が、archive-time-stamp がない |
EN 319 122-1 §6.3 |
CAdESLta |
B-LTA | 少なくとも1つの archive-time-stamp-v3 トークンがある |
EN 319 122-1 §6.3.4 |
繰り上げの階梯は厳密に順序付けられている(archive_timestamps が空でなければ勝ち、次に has_lt_lta_payload、次に timestamps が空でない、それ以外は BES)。このレベルは、入力が主張するプロファイルではなく、存在する構造から認識される事実である。ワイヤ列挙には「B-ES と B-B」を区別する別個の項目は存在しない — CAdESBes はレガシーの CAdES-BES(RFC 5126)と EN 319 122-1 の B-B ベースラインの両方を包含する。これは、pverify が読み取る構造的表面(署名証明書 + 署名属性、信頼されたタイムスタンプなし)が同一であるためである。
単一署名者の CMS がサポートされるケースである。複数署名者の SignedData は、最初の署名者のみを報告するのではなく、意図的に拒否される(§5.4.4)。
SignedData 構造パーサパーサは crates/pverify-core/src/cms/signed_data.rs の型 SignedDataParser である。これは意図的に、厳密にフィールド順序付けされた ASN.1 デコーダではなく、BER/DER TLV ツリー(crate::ber::parse_ber_tlv で構築される)上を歩く寛容な構造ウォーカである。モジュールのヘッダはその出自を記録している。すなわち、CAdES/PAdES と同じ外形を持つ RFC 5652 SignedData である civ プロジェクトの ICAO 9303 SOD パーサから派生したもので、LDS パスポート固有の抽出は除去されている(憲章 §VII はパスポートドメインのコードを PKI 表面に持ち込むことを禁じている)。
ウォーカは、下流のチェックが必要とする最小限のフィールド集合を抽出する:
SignedDataParser フィールド |
出典(RFC 5652 §5.1) | 格納形式 |
|---|---|---|
digest_algorithm_oid |
最初の digestAlgorithms AlgorithmIdentifier の OID |
生の OID 値バイト |
e_content_type_oid |
encapContentInfo.eContentType |
生の OID 値バイト |
e_content |
encapContentInfo.eContent [0] EXPLICIT 内側の OCTET STRING |
値バイト(detached の場合は None) |
certificates |
certificates [0] IMPLICIT |
各 X.509 SEQUENCE の値バイト(ヘッダを除去) |
signed_attrs |
最初の SignerInfo.signedAttrs [0] IMPLICIT |
値バイト([0] ヘッダなし) |
signature |
最初の SignerInfo.signature OCTET STRING |
値バイト |
unsigned_attrs |
最初の SignerInfo.unsignedAttrs [1] IMPLICIT |
値バイト |
sid |
最初の SignerInfo.sid CHOICE |
完全な DER 要素(タグ含む全体)、再エンコード済み |
signer_info_count |
signerInfos 内の SignerInfo SEQUENCE の個数 |
usize |
ウォーカは、素の RFC 5652 ContentInfo SEQUENCE、または外側がアプリケーションタグ 0x77 でラップされたもの(後者は ICAO SOD のフレーミングであり、civ を経由してラウンドトリップするテストベクタがここでもラウンドトリップするよう保持されている — SignedDataParser::parse)のいずれかを受理する。SignedData の位置特定は、最初の子が OID である最初の SEQUENCE を見つけ(find_signed_data_content)、content [0] EXPLICIT ラッパを通って内側の SignedData SEQUENCE へ降りていくことによって行う。
SignedData SEQUENCE 内で、extract_signed_data_components は小さなステートマシンによって子要素を宣言順に歩く:
digestAlgorithms の後の最初の SEQUENCE は encapContentInfo として扱われ(saw_encap フラグ)、そこから eContentType OID と任意の [0] EXPLICIT eContent が抽出される。CAdES は id-data(1.2.840.113549.1.7.1)を用いる。埋め込まれた RFC 3161 トークンは id-ct-TSTInfo(1.2.840.113549.1.9.16.1.4)を担う。0x31)が digestAlgorithms であり、それより後のいずれかの SET が signerInfos である。これは、このレベルに SET が最大2つしか存在しないことに依拠している — 適合する単一署名者の CAdES/PAdES では真である。ウォーカは signerInfos SET から signer_info_count を記録し、複数署名者ゲート(§5.4.4)が発火できるようにする。certificates [0] IMPLICIT(0xA0)は各 Certificate SEQUENCE の値バイトを生成する。完全な DER ヘッダは下流で再付加される(§5.4.1)。sid の捕捉(extract_signer_info)は、パーサがタグについて正確でなければならない唯一の箇所である。SignerInfo.sid は version(INTEGER, 0x02)の直後の要素である。ウォーカは version の後の最初の要素を捕捉し、それを完全な DER へ再エンコードする(reencode_tlv)。この CHOICE は issuerAndSerialNumber(SEQUENCE, タグ 0x30)または subjectKeyIdentifier のいずれかであり、後者は [0] IMPLICIT OCTET STRING であって、そのワイヤタグは構築型の 0xA0 ではなく、プリミティブのコンテキストタグ 0x80 である。捕捉したバイトの解釈は、埋め込み証明書集合が利用可能となる選択箇所(§5.5.1)まで遅延される。
signedAttrs、signature、unsignedAttrs の各フィールドはそれぞれ一度だけ捕捉される(is_none() ガード)ため、不正な2番目の SignerInfo が最初の署名者のデータを上書きすることは決してない — ただし個数は依然としてインクリメントされ、ゲートが切り詰めを誠実に拒否できるようにする。
空の入力は Error::Parse("empty CMS SignedData input") を表面化させる。切り詰められた 0x77 ラッパは Error::Ber を表面化させる。認識可能な SignedData SEQUENCE を持たない入力は、(エラーではなく)すべてのフィールドが空の SignedDataParser を返す。検証パイプラインはその後、構造的な欠落を事実として報告する。これらの挙動は signed_data.rs 内のユニットテスト(例えば parse_empty_input_returns_err、parse_returns_empty_fields_when_no_signed_data_seq_found)によって固定されている。
signedAttrs が存在する場合、署名は生の [0] IMPLICIT 本体ではなく、構成要素を昇順のオクテット順にソートした SET OF Attribute(ユニバーサルタグ 0x31)としての DER 再エンコードに対して計算される。これは RFC 5652 §5.4 / X.690 §11.5 の規則であり、検証者がワイヤ上の順序や暗黙タグの置換とは独立に、署名されたオクテットを再構築できるようにするために存在する。
crates/pverify-core/src/cms/canonical.rs — canonicalise_signed_attrs — がこれを実装する。すなわち、格納された値バイトを構成要素の Attribute TLV へ分割し、sort_unstable()(辞書式バイト比較)でソートし、0x31 タグ付きの SET として再放出する。モジュールヘッダは、ここで辞書式バイト比較が X.690 §11.5 のゼロパディング規則と等価である理由を文書化している。すなわち、外側のタグを共有する相異なる DER TLV では、パディングの考慮が生じる前に長さオクテットが分岐するため、より単純な比較で十分である。
verify_content_digest(crates/pverify-core/src/cms/verify.rs):
digest_algorithm_oid からダイジェストアルゴリズムを読み取る(HashAlg::from_oid_bytes を介してマップ。SHA-1 および SHA-2 のみ — SHA-3 はスコープ外、cms/signed_attrs.rs::oid_to_hash_name)。eContent、detached CAdES では --detached-content のバイト(解決規則は §5.4.2)— に対して digest(content) を計算する。find_message_digest_attribute を介して signedAttrs を歩き messageDigest 属性(1.2.840.113549.1.9.4)を探し、その OCTET STRING 値を読み取る。digest_match、および別個の covered_content_sha256(署名自身のダイジェストアルゴリズムによらず、SHA-256 でハッシュされた生のカバー対象バイト — slice 016 の診断データ。これにより signed_content 検証対象オブジェクトのダイジェストは、SHA-384/512 署名であっても正しい)を担う ContentDigestCheck を生成する。この関数は判定を一切放出しない — 観測したダイジェストの事実を記録する。messageDigest_mismatch へのマッピングは後に aggregate_etsi(§5.7.4)で発生し、かつコンテンツが実際に供給された場合に限る(コンテンツのない detached 署名は、失敗としてではなく評価不能という事実として digest_match = false を生む)。
verify_signed_attrs(crates/pverify-core/src/cms/verify.rs)は signedAttrs を正規化し(§5.3.1)、署名者証明書の SubjectPublicKeyInfo DER を抽出し(RustCrypto 検証器が期待するオクテット列に一致するよう x509-cert を通して再エンコードする)、SPKI のアルゴリズムファミリと宣言されたダイジェストを対にして署名者の signatureAlgorithm OID を導出し、crate::crypto::verify_with_alg を介して検証する。
signatureAlgorithm の導出(derive_signer_sig_alg_oid)が必要なのは、構造パーサが SignerInfo の signatureAlgorithm フィールドを別個に表面化させないためである。この対応付けは、適合する SignerInfo が担うであろうのと同一の OID を再現する:
| SPKI アルゴリズム | ダイジェスト | 導出される signatureAlgorithm |
|---|---|---|
rsaEncryption (1.2.840.113549.1.1.1) |
SHA-1/256/384/512 | sha{N}WithRSAEncryption (1.2.840.113549.1.1.{5,11,12,13}) |
id-ecPublicKey (1.2.840.10045.2.1) |
SHA-1/256/384/512 | ecdsa-with-SHA{N} (1.2.840.10045.4.{1,3.2,3.3,3.4}) |
| Ed25519 (1.3.101.112) | 任意 | 1.3.101.112(Ed25519 内部の SHA-512 が検証を駆動する。CMS のダイジェストフィールドは参考情報である) |
| RSA-PSS, brainpool, GOST, SM2, … | — | 非対応 → None |
認識されない SPKI ファミリは SignedAttrsSignature::Failed { cause: UnsupportedAlgorithm } を生む。検証して失敗した場合は Failed { cause: Other }(または認識された OID で鍵がデコード不能の場合は MalformedPublicKey、slice 010)を生む。型付きの cause 列挙(SignedAttrsFailureKind)は失敗箇所で刻印されるため、aggregate_etsi は reason 文字列の接頭辞ではなく列挙でルーティングを行う(slice-029 の堅牢化 — 人間可読の理由のリファクタが、もはや判定を黙って再分類することはできない)。
結果は SignedAttrsCheck である。その signed_attrs_digest_match は独立したダイジェスト比較ではない。これは、署名全体に対する verify_with_alg(宣言されたダイジェストでハッシュされた正規化済み signedAttrs、署名者の SPKI、および signature オクテット)が成功したときにちょうど Some(true) に設定され、それ以外では Some(false) に設定される — したがってこのフィールドは、別個にチェックされる述語ではなく、検証結果の派生エイリアスである。RSA/ECDSA/Ed25519 の検証は、素のダイジェスト等価性ではなく署名プリミティブ全体をカバーするため、将来の PSS/ECDSA パスではここでハッシュのみの失敗と完全検証の失敗を区別しうる。signedAttrs が存在しない場合、RFC 5652 §5.4 の eContent への署名パスは配線されていない — 関数は構造的な Failed を表面化させ、パイプラインが判断できるようにする(CAdES B-B は signedAttrs を必須とする)。
verify_cades)verify_cades(crates/pverify-core/src/verify.rs:475)はトップレベルの署名ごとの CAdES 検証器であり、detect_format が非 PDF・非 XML・非 JSON・非 ASiC の入力を CAdES パスへルーティングした後に verify_with から呼び出される(detect_format は DetectedFormat::Cades へフォールスルーする)。ちょうど1つの SignatureEntry を返す。
パーサが格納した本体のみの証明書スライスは、完全な DER SEQUENCE へ再ラップされ(reconstitute_certificate_der)、crate::x509::parse_certificate で解析される。解析不能な単一の証明書はパイプラインを沈めるのではなくスキップされる。CMS に埋め込まれた証明書の個数(cms_cert_count)は、ホストの extra_certificates(例えばバンドルの certificates/)が追加される前に記録される。なぜなら、ホストの中間証明書はチェーン材料としては適格だが、署名者としては決して適格でない — 署名者は常に CMS 埋め込みの接頭部分から選択される(§5.5.1)からである。
ダイジェストおよびアーカイブタイムスタンプのインプリントのためのコンテンツ解決:埋め込みの eContent(attached CAdES)は、両方が存在しかつ空でないときは常に detached_content_bytes に勝る。CLI のプレフライトは、両方が供給されていればすでにバイト等価性を強制している。観測された出所は SignatureEntry に対し ContentSourceTag::Embedded または Detached として記録される。
project_unsigned_attrs(crates/pverify-core/src/cms/unsigned_attrs.rs)は unsignedAttrs 本体を歩いて UnsignedAttrsProjection へ投影する:
| フィールド | CAdES 属性 | OID |
|---|---|---|
signature_time_stamps |
signature-time-stamp (B-T) |
1.2.840.113549.1.9.16.2.14 |
archive_time_stamps_v3 |
archive-time-stamp-v3 (B-LTA) |
04 00 4D 49 02 04 (ETSI) |
certificate_values |
certificate-values (B-LT) |
1.2.840.113549.1.9.16.2.23 |
revocation_values_crls |
revocation-values.crlVals (B-LT) |
1.2.840.113549.1.9.16.2.24 |
revocation_values_ocsp |
revocation-values.ocspVals (B-LT) |
(同一属性の ocspVals ブランチ) |
これらは、レポートの EmbeddedValidationData 監査投影と、以下の失効/タイムスタンプパイプラインの双方に供給される。
signer_info_count > 1 の場合、verify_cades は cades_multi_signer_entry を返す — サブインディケーション cms_multi_signer_unsupported を伴う INDETERMINATE(crates/pverify-core/src/verify.rs:496、:1890)。これは slice-029 の事実報告に関する是正である。それ以前は、パーサが最初の SignerInfo のフィールドのみを黙って保持していたため、複数署名者の CMS が単一署名者が存在するかのように報告されていた — これは、検証者(信頼当事者)が連署や監査のワークフローで重視する署名者集合の証拠を隠していた。レポートが SignerInfo ごとに1エントリのモデルへ拡張されるまでは、誠実な答えは拒否であって、切り詰められた署名者集合に対する判定では決してない。これは憲章 §I「断言できないことは断言しない」の直接的な適用である。
slice 025-cades-signer-binding は、JNSA デジタル署名検証ガイドライン(第1.1版)の 表5.5.2-1 の必須チェック2件(JNSA ギャップ #90/#91)をクローズする。これを規律する明確化(specs/025-cades-signer-binding/spec.md):バインディング破綻 → TOTAL_FAILED、識別不能 → INDETERMINATE。PAdES はその埋め込み CMS の評価を CAdES パスへ委譲する(pades/mod.rs)ため、両チェックは自動的に PAdES へ拡張される。
slice 025 以前、pverify はヒューリスティック(「CMS 埋め込み集合の最初の非 CA 証明書」)で署名者を選択していた。署名値が照合して検証されるべき署名者は SignerInfo.sid(RFC 5652 §5.3)によって識別され、ヒューリスティックと真の sid ターゲットとの不一致は事実報告上のギャップである。
SignerIdentifier::parse(crates/pverify-core/src/cms/ess.rs)は捕捉した sid DER を解析する:
0x30 → IssuerAndSerial { issuer_dn, serial }。発行者の Name SEQUENCE は完全な DER へ再エンコードされ、Certificate::issuer_dn とまったく同様に match_dn へ供給できる。0x80(プリミティブの [0] IMPLICIT OCTET STRING)→ SubjectKeyId(octets)。Err → 不正経路。select_signer_by_sid(crates/pverify-core/src/verify.rs:1096)は次に、最初の cms_cert_count 個の埋め込み証明書の内部に限ってインデックスを解決する:
IssuerAndSerial:シリアル番号の等価性 かつ 発行者DNの連結(x509::name::match_dn)によって照合する。SubjectKeyId:証明書の subjectKeyIdentifier 拡張によって照合する。その結果は SignerIdentifierCheck { sid_form, match_outcome } へ投影される:
match_outcome |
意味 | インディケーション(§5.7.5) |
|---|---|---|
Matched |
sid が埋め込み証明書を指定している |
(効果なし。下流のゲートが適用される) |
NotFound |
well-formed な sid がいずれの埋め込み証明書にも一致しない(置換 / 署名者証明書の欠落) |
TOTAL_FAILED / signer_certificate_not_found |
Malformed |
sid が存在しないか解析不能 |
INDETERMINATE / signer_identifier_malformed |
NotFound/Malformed の場合、関数は監査用にチェーンを組み立てられるよう、依然としてレガシーのヒューリスティックインデックスをレポートの足場として返す — ただし判定はいかなる署名/ダイジェストゲートよりも前に match_outcome でゲートされるため、ヒューリスティックのフォールバックが黙って「照合対象として検証される証明書」になることは決してない。
ESS の signing-certificate(v1、SHA-1 固定)/ signing-certificate-v2(宣言されたアルゴリズム、SHA-256 デフォルト)署名属性は、署名者証明書をハッシュによって暗号学的に固定し、証明書置換(同一の鍵ペアで再発行または偽造された証明書)に対して防御する。parse_ess_signing_certificate(crates/pverify-core/src/cms/ess.rs)はこの属性(id-aa-signingCertificate 1.2.840.113549.1.9.16.2.12 / id-aa-signingCertificateV2 …2.47)を見つけ、その最初の ESSCertID/ESSCertIDv2 を解析する:
ESSCertID ::= SEQUENCE { certHash OCTET STRING, issuerSerial IssuerSerial OPTIONAL }
ESSCertIDv2 ::= SEQUENCE { hashAlgorithm AlgorithmIdentifier DEFAULT {id-sha256},
certHash OCTET STRING, issuerSerial IssuerSerial OPTIONAL }
署名者証明書のバインディングであるのは最初の ESSCertID のみであり、後続のエントリはチェーン証明書を記述するものでスコープ外である。v2 では、先頭の SEQUENCE が明示的な hashAlgorithm であり、その不在は SHA-256 デフォルトを意味する。issuerSerial の issuer は GeneralNames である。pverify は directoryName [4] GeneralName(構築型コンテキストタグ 0xA4)の内側の Name DER を抽出し、directoryName 以外の形式はすべて評価不能として扱う(Mandatory-if-Exists:失敗とはしない)。
compute_ess_binding(crates/pverify-core/src/verify.rs:1195)は sid で選択された署名者証明書に対してバインディングを計算する:
digest(signer_cert.der_bytes) を再計算し、certHash と比較する → CertHashCheck { hash_match }。issuerSerial が存在し directoryName 形式である場合:その DN 連結とシリアルを署名者証明書と比較する → IssuerSerialCheck { matches }。WeakAlgorithmFlag(役割 EssSigningCertificateDigest)が署名者チェーンステップに付加される。これは決して拒否しない(FR-012a)。3つの評価不能 / 不正の処理(いずれも憲章 §I「断言できないことは断言しない」):
hash_match: None。directoryName 以外の issuerSerial GeneralName → matches: None。hash_match: None を伴う present: true(不在とは区別される。不在は present: false のまま、インディケーションへの影響なし)。既存の優先順位はすでに、それ以外は合格となる署名における hash_match: null を INDETERMINATE / signing_certificate_digest_not_evaluable へルーティングするため、新たなサブインディケーションは不要であった。slice 026-cades-contenttype-binding は RFC 5652 §5.3 の content-type 署名属性の等価性チェック(JNSA が必須とする CAdES ステップ)を追加する。content-type 属性(1.2.840.113549.1.9.3)は EncapsulatedContentInfo.eContentType と等しい OID を担わなければならない(MUST)。さもなければ、署名を無効化することなくカプセル化されたコンテンツタイプを置換できる攻撃者が、署名されたバイトの意味を変えてしまうことになる。
find_content_type_attribute(crates/pverify-core/src/cms/verify.rs)は messageDigest の走査の構造的なクローンであり、属性 OID と、値 SET がちょうど1つの OBJECT IDENTIFIER を保持しなければならない点のみが異なる。これは Present(oid)、Absent、または Malformed を返す。compute_content_type_check(crates/pverify-core/src/verify.rs:1388)は生の OID バイト等価性によって比較し、結果を伴う ContentTypeCheck を生成する:
ContentTypeMatch |
条件 | インディケーション(§5.7.5) |
|---|---|---|
Matched |
content-type OID == eContentType | (効果なし) |
NotApplicable |
signedAttrs が不在(§5.4 eContent への署名) |
(効果なし) |
Mismatch |
OID が異なる | TOTAL_FAILED / content_type_mismatch |
Missing |
signedAttrs は存在するが属性が不在 |
TOTAL_FAILED / content_type_missing |
NotEvaluable |
属性が不正、または eContentType が利用不能 |
INDETERMINATE / content_type_not_evaluable |
RFC 5652 §5.3 は、いずれかの署名属性が存在する場合にこの属性を MUST-present とする。よって Missing は TOTAL_FAILED である一方、不正な属性または eContentType の不在(断定回避)は INDETERMINATE である。
各 signature-time-stamp トークン(RFC 3161、RFC 5126 §6.3)は署名者の signature OCTET STRING にコミットする。verify_cades はそれらのバイトを役割 SignatureTimestamp で verify_timestamp_token(第12章)へインプリント入力として渡す。埋め込み証明書は TSA チェーンの中間証明書として機能し、署名者と同一のアンカーストアに対して検証される。
各 archive-time-stamp-v3 について、archive_v3_imprint_input(crates/pverify-core/src/cms/canonical.rs)は ETSI EN 319 122-1 §6.3.4 の正規インプリント入力を再計算し、それを verify_timestamp_token(役割 ArchiveTimestamp)へ渡す。不一致は archive_timestamp_imprint_mismatch(TOTAL_FAILED)である。
正規入力は、次の順序で連結する:(1) コンテンツバイト(埋め込み eContent または detached)、(2) ディスク上の SET-OF 順(正規ソートではない)での各埋め込み証明書の完全な DER、(3) SignedData の CRL スロット(現状は常に空 — パーサが表面化させず、pverify の LTA フィクスチャは失効材料を unsignedAttrs に置く)、(4) signedAttrs の値バイト、(5) 署名者の signature 値バイト、(6) 再計算対象のアーカイブ TS およびそれより後のすべてを除去した unsignedAttrs。これにより、逐次的なアーカイブタイムスタンプの連鎖がハッシュチェーンを形成する(unsigned_attrs_without_archive_ts_at_or_after 内の checkpoint_index 機構)。
モジュールはこれを v0.3 の簡易エンコーダとして文書化している。これは決定論的なバイトレシピ(境界を明確に保つため、スロット 4〜6 に pverify 側の SA/SI/UA 長さ前置フレーミングを伴う)であり、ETSI §6.3.4 が問題とするあらゆる改変 — 改竄されたコンテンツ、埋め込み証明書/CRL、signedAttrs、signature、または自分自身以外の任意の unsigned-attribute の変更 — を検出するのに十分である。これはまだ完全に一般的な §6.3.4 エンコーダではない(複数アーカイブのレイヤリング、certVals/revocVals の更新セマンティクス、ディスク上の SET-OF 順序のコーナーケース)。これは黙った近似ではなく、認識されたスコープの制約である。インプリントが計算できない場合(コンテンツが利用不能)、インプリントは参考情報としてのみ扱われる。複雑な多層構造にわたる LTA アーカイブを検証する監査者は、インプリントの結果を完全な §6.3.4 適合性の断言ではなく、肯定的な指標として扱うべきである。
タイムスタンプトークンが populate された後、crate::vrt::derive_vrt(第12章)は再帰的な外側カバーのグラフを構築し、各オブジェクト — 署名者チェーン、署名者の署名、各 signature-TS、各 archive-TS — に固有の検証基準時刻(VRT)を割り当てる。署名者チェーンはその後、その VRT が request_at と異なる場合は常に vrt.signer_chain.value において再検証される(validate_path → wire_chain_step_signatures → 弱アルゴリズムのフラグ付け → ESS フラグの再付加 → apply_revocations を再実行する)。VRT が request_at と等しい場合、再検証はバイト単位で同一になるためスキップされる。各タイムスタンプの TSA チェーンも同様にそれ自身の VRT において再検証され、各々はそのオブジェクトの VRT において apply_tsa_chain_revocation_for_token(slice 033、crates/pverify-core/src/verify.rs:1552)を介して失効検証され、埋め込みの revocation-values OCSP/CRL 材料に加え、検証モードに応じた構成済みフェッチャを消費する。これはすべてのタイムスタンプに対して無条件で実行される。なぜなら、第一パスの verify_timestamp_token は失効を一切適用しないからである。失効が確認された TSA 証明書は INDETERMINATE / revoked_no_poe へ繰り下げられる — 「タイムスタンプを断言できない」のであって、決して偽造の主張ではない。
OCSP nonce(RFC 8954、slice 035)。 ライブ OCSP フェッチャが発火する場合(オンラインモード)、プラットフォームホストの NonceSource trait 実装(CSPRNG をバックエンドとする)が生成した 16 バイトの nonce を送信する。レスポンダの返答は、同一の nonce を返すか nonce 拡張を省略した場合にのみ受理される(RFC 8954 §2.1)。nonce が不一致の場合、応答は認証されていないものとして扱われ、OCSP の試みは不確定に降格される。実行内 OCSP キャッシュ(OcspCache)は単一の verify_with 呼び出し内でリクエストを重複排除し、同じ certID の取得は実行ごとに最大1回となる。
aggregate_etsi(crates/pverify-core/src/verify.rs:2343)は、チェーン/署名/ダイジェスト/アーカイブインプリントの所見を、重大度順 TOTAL_FAILED > INDETERMINATE > TOTAL_PASSED で、かつ重大度の層内では最初の所見が勝つという規則で、基本の EtsiIndication へ畳み込む。優先順位の階梯(簡略、評価順):
TOTAL_FAILED(例えば chain_constraints_failure)。archive_timestamp_imprint_mismatch(TOTAL_FAILED)。ocsp_responder_signature_invalid、TOTAL_FAILED)。cause でルーティングされる:Other → TOTAL_FAILED / signed_attrs_signature_failed、UnsupportedAlgorithm → INDETERMINATE / signature_algorithm_unsupported、MalformedPublicKey → INDETERMINATE / public_key_malformed。TOTAL_FAILED / messageDigest_mismatch。TOTAL_FAILED / signer_certificate_revoked[_via_ocsp]。INDETERMINATE。終端アンカーのステップのプレースホルダ失効レコードは失効スキャンから除外されるため、陳腐化した CRL のサブインディケーションがアンカー自身に対して誤って放出されることは決してない。
基本のインディケーションは次に、順序付けられた4つの層によって繰り下げられる — 決して上方へマスクされない。各層は、基本がすでに証明済みの TOTAL_FAILED でない場合にのみ作用する:
apply_signer_binding_indication(:1324):署名者識別のゲートは絶対的に最初である(NotFound/Malformed の sid は、いかなる署名/ダイジェスト結果よりも前に評価される。それらは正しく識別された署名者を前提とするからである)。次に ESS の certHash/issuerSerial の不一致は、TOTAL_FAILED でない基本を TOTAL_FAILED へ格上げする。ESS の評価不能なダイジェストは、TOTAL_PASSED となるはずのものを INDETERMINATE / signing_certificate_digest_not_evaluable へ繰り下げる。apply_content_type_indication(:1441):不一致/欠落 → TOTAL_FAILED、不正/利用不能 → INDETERMINATE。apply_algorithm_validity_indication(:1480):--algorithm-policy が供給された場合のみ。失敗/不確定のオブジェクトは INDETERMINATE / crypto_constraints_failure_no_poe へ繰り下げられ、ETSI CRYPTO_CONSTRAINTS_FAILURE_NO_POE を反映する。デフォルトでは無効。無効経路の出力はバイト単位で同一である(第13章)。apply_tsa_revocation_indication(:1622):いずれかのタイムスタンプにおいて失効が確認された TSA 証明書は INDETERMINATE / revoked_no_poe へ繰り下げられる。各層の「すでに証明済みの TOTAL_FAILED でない場合にのみ繰り下げる」というガードが、決してマスクしないという規律である。暗号学的に破綻した署名は、非推奨アルゴリズムや失効した TSA という事実よりも上位にあり、証明済みの失敗がより緩やかな不確定によって上書きされることは決してない。
本章のあらゆる処理は、閉じた SubIndication 列挙(crates/pverify-core/src/report/etsi.rs、ルートの report-schema.json へ 1:1 でミラーされる)へと解決される。自由形式の判定テキストがレポートに到達することはない。関連する CAdES パスのサブインディケーションは次のとおりである:signer_certificate_not_found、signer_identifier_malformed、signing_certificate_digest_mismatch、signing_certificate_issuer_serial_mismatch、signing_certificate_digest_not_evaluable、content_type_mismatch、content_type_missing、content_type_not_evaluable、signed_attrs_signature_failed、messageDigest_mismatch、signer_certificate_revoked、signer_certificate_revoked_via_ocsp、archive_timestamp_imprint_mismatch、cms_multi_signer_unsupported、および他の形式と共有されるさまざまなアルゴリズム/失効の不確定値。
SignedDataParser と verify_cades のオーケストレーションは、ツールの残りの部分が構築される基盤である。PAdES(第6章)は PDF をホスト側で解析し、埋め込み CMS をこの同一の SignedDataParser + §5.5/§5.6 のバインディングチェックを通して供給し、その上に /DSS//VRI の失効材料を重ねる。RFC 3161 タイムスタンプトークン(第12章)は id-ct-TSTInfo 型の SignedData として SignedDataParser によって解析される。ASiC(第7章)は各内部 CAdES 署名を verify_cades を通してルーティングする。したがって、署名者バインディングの優先順位、型付き cause による失敗ルーティング、決してマスクしない集約の規律は、CAdES 局所の慣行ではなく、観測された暗号学的事実がいかにして ETSI インディケーションになるかについてのツール全体の契約である。
本章では、pverify が PAdES(PDF Advanced Electronic Signatures、ETSI EN 319 142)をどのように検証するかを規定する。PDF パースを WASM クリーンなカーネルの外側に保つホスト/コアの分割、PDF 署名辞書の射影、/ByteRange の完全性と covers-EOF の規律、インクリメンタルアップデートのリビジョン処理、文書タイムスタンプ(/DocTimeStamp)、日本の公的 PDF の検証を可能にした RFC 5280 §6.1.5 の空ポリシーツリー緩和、そして — 詳細に — PDF が自身について保持する長期検証材料、すなわち文書レベルの /DSS 辞書(/Certs、/CRLs、/OCSPs)、その署名ごとの /VRI サブ辞書、および Adobe revocationInfoArchival CMS 属性を pverify が消費できるようにする branch-034 の作業を扱う。
PAdES 検証器は、意図的な設計として、CAdES を駆動するのと同じ CMS/チェーン/失効/タイムスタンプの機構を再利用する(フォーマットディスパッチについては第4章、CAdES については第5章を参照)。フォーマット固有の表面は小さく、ここではそこに焦点を当てる。オブジェクトごとの検証基準時刻(VRT)の導出は第12章、失効評価は第11章、レポート/スキーマモデルは第14章で扱う。本章では PAdES のパスがそれらに連なる箇所で相互参照する。
PDF 署名は PDF 署名辞書に埋め込まれた CMS SignedData(RFC 5652)であり、署名対象のコンテンツは CMS の eContent ではなく、/ByteRange によって選択された PDF バイト列である。したがって pverify は PAdES を バイトレンジで選択されたメッセージに対する detached CAdES として扱い、埋め込まれた CMS を detached CAdES に用いられるのと同一のパイプラインへ流す(crates/pverify-core/src/pades/mod.rs、モジュールドキュメントと verify_one_signature)。
pverify は以下の PAdES ベースラインレベルをサポートする。
| レベル | 機構 | pverify のサポート |
|---|---|---|
| PAdES-B / B-B (BES) | 署名証明書 + 署名属性、信頼されたタイムスタンプなし | 検証済み(埋め込み CMS チェーン + 署名属性 + コンテンツダイジェスト) |
| PAdES-B-T | 署名に対する信頼された署名タイムスタンプ(RFC 3161) | 検証済み — CMS の unsignedAttrs から射影された埋め込み signature-time-stamp トークン、および /DocTimeStamp 文書タイムスタンプ |
| PAdES-B-LT | 署名が自己完結するための埋め込み長期検証材料(証明書 + 失効情報) | 検証済み — branch 034 が文書レベルの /DSS + /VRI + Adobe revocationInfoArchival を消費する |
| PAdES-B-LTA | 構造を再保護するアーカイブタイムスタンプ | 検証済み — PAdES はアーカイブタイムスタンプの機構として /DocTimeStamp を用い、archive_timestamp ロールを持つ TimestampToken として表出する |
すべての PAdES SignatureEntry に対して出力されるフォーマット文字列は SignatureFormat::PAdESB である(crates/pverify-core/src/pades/mod.rs。エントリのコンストラクタはすべて format: SignatureFormat::PAdESB を設定する)。レベルの区別(B/T/LT/LTA)は、別個のフォーマット列挙値によってではなく、エントリが保持する 事実 — timestamps[] トークンの存在と検証、vrt ブロック(第12章)、および消費された失効材料の出所 — によって表現される。これは 憲章 §I の事実報告の規律と整合する。pverify は宣伝文句レベルの「これは有効な PAdES-LTA である」と断言するのではなく、観測した事実(検証済みのアーカイブタイムスタンプ、DSS に由来する CRL)を報告する。
複数の SignerInfo を保持する PAdES SignedData は誠実に拒否される。verify_one_signature はパース直後に parsed.signer_info_count > 1 を検査し、サブインディケーション CmsMultiSignerUnsupported を持つ INDETERMINATE エントリである multi_signer_entry を返す(crates/pverify-core/src/pades/mod.rs、verify_one_signature および multi_signer_entry)。これは CAdES のゲート(verify.rs:496、cms_multi_signer_unsupported)を映したものである。ソース中の修正ノートはその理由を記録している。029 以前のパーサは最初の SignerInfo のみを暗黙に保持していたため、複数署名者の PDF は最初の署名者の判定のみを報告していた — これは事実隠蔽のバグである。拒否することが §I の「断言できないことは断言しない」応答である。
複数の署名辞書を持つ1つの PDF(正当なインクリメンタルアップデートのレイアウト — §6.4 を参照、完全にサポートされ、辞書ごとに1つの SignatureEntry)と、複数の SignerInfo を持つ1つの CMS(拒否される)の区別に留意せよ。PAdES は単一の CMS 内で共署名しない。複数の PDF 署名者は、それぞれ連続するインクリメンタルアップデートで自身の署名辞書を追加する。
憲章 §V(ライブラリファースト)および §VI(WASM クリーンなコア、交渉不可)は、wasm32-unknown-unknown へコンパイル可能でなければならず lopdf/zip/flate2 をリンクしてはならない pverify-core の内部での PDF パースを禁じる。したがって、すべての PDF 構造のパースは、モデルに依存しないバイト構造を生成しそれらを追加的にカーネルへ供給する2つのホスト抽出器で行われる。
crates/pverify-cli/src/pdf.rs(lopdf を使用)。web/pverify-wasm/src/lib.rs(同じく lopdf を使用、意図的に分離された web/pverify-wasm ワークスペース内)。これら2つの抽出器は、互いの バイト単位で並行な移植 であることが要求される(CLIとWASMのパリティ不変条件)。カーネルは追加的な VerificationRequest フィールドを通じてパース済み構造を消費する(crates/pverify-core/src/verify.rs)。
pdf_signature_dicts: Vec<PdfSignatureDescriptor>(verify.rs:86) — 署名/DocTimeStamp 辞書ごとに1つのディスクリプタ。pdf_validation_data: PdfValidationData(verify.rs:95) — 文書レベルの /DSS+/VRI 材料(branch 034、§6.5)。verify_with におけるフォーマットディスパッチは、%PDF- をスニッフィングした入力を crate::pades::verify_pdf へルーティングし、両フィールドを渡す(verify.rs:241–247)。非 PDF 入力では両フィールドとも Default/空である(例: verify.rs:2205)ため、カーネルはスニッフィングしたフォーマットに合致するチャネルのみを参照する。
ホストは暗号処理や失効ロジックを一切行わない。PDF 構造の走査、ストリームのデコード(FlateDecode)、%%EOF のスキャン、および DER の抽出のみを行う。カーネルは、純粋なバイトスライス上で、すべての暗号処理、パス検証、失効評価、およびインディケーションの集約を行う。
PdfSignatureDescriptor 射影ディスクリプタ(crates/pverify-core/src/pades/mod.rs、PdfSignatureDescriptor)は、単一の署名辞書のホスト→コア射影である。
| フィールド | 型 | 意味 |
|---|---|---|
byte_range |
[u64; 4] |
/ByteRange [start1 len1 start2 len2] — 署名がコミットする2つのウィンドウ(/Contents プレースホルダ以外のすべて) |
contents |
Vec<u8> |
/Contents から取得した生の CMS SignedData DER。PDF の16進文字列パディングはホスト側で既に除去済み |
subfilter |
String |
/SubFilter(例: ETSI.CAdES.detached、ETSI.RFC3161)。参考情報であり、強制しない |
revision_end |
u64 |
この辞書が閉じるインクリメンタルアップデートのリビジョンが終わるバイトオフセット(その %%EOF)。単一リビジョンの PDF では pdf_bytes.len() に等しい(§6.4) |
dict_type |
PdfSigDictType |
Signature(/Type /Sig)または DocTimeStamp(/Type /DocTimeStamp) |
dict_type と revision_end は 入力 射影フィールドであり、レポートにシリアライズされない。ホスト抽出器は、/Type が /Sig または /DocTimeStamp であり、かつ /ByteRange と /Contents の両方を保持する辞書を受け入れる。それ以外はすべてスキップされる(extract_signatures、pdf.rs の match dict.get(b"Type") のアーム)。署名辞書はオブジェクトグラフのどこにでも存在しうる(最も一般的には AcroForm.Fields[*].V の下)ため、抽出器は AcroForm のみではなくすべてのオブジェクトを走査する(pdf.rs、for (_id, object) in doc.objects.iter() およびモジュールドキュメント)。
/Contents ブロブは末尾にゼロバイトのパディングを伴う固定幅の PDF 16進文字列である(PDF 32000-1 §12.8.1)。CMS SignedData は単一の DER SEQUENCE であるため、ホストは外側 TLV の宣言された長さに切り詰め、BER パーサがパディングを見ないようにする(pdf.rs、trim_to_outer_sequence。WASM 移植版は web/pverify-wasm/src/lib.rs で同一)。切り詰めは 0x30 の構造化 SEQUENCE タグと短形式/長形式の長さを読み取り、header_len + value_len <= bytes.len() を境界チェックし、いかなる不正形式でも生のブロブにフォールバックする。
verify_one_signature(crates/pverify-core/src/pades/mod.rs)は、いかなる暗号処理よりも前に構造的な ByteRange 検証を行う。それぞれ固有の閉じたサブインディケーションを持つ、2つの異なる失敗モードが存在する。
不正形式の ByteRange → PdfByteRangeMalformed を伴う TOTAL_FAILED。このチェック(オーバーフローを避けるためチェック付き算術を使用)は、start1 + len1 <= start2(2つのウィンドウは順序付けられ重複しない)と start2 + len2 <= pdf_bytes.len()(両ウィンドウがファイル内に収まる)を要求する。算術がオーバーフローする、あるいはウィンドウが順序外/範囲外であれば、malformed が真となり暗号処理は試みられない。
ByteRange が EOF をカバーしない → PdfByteRangeDoesNotCoverEof を伴う TOTAL_FAILED。これは、署名が(関連する)ファイル全体にコミットしていない署名 — すなわち、署名が保護しない、署名対象領域の後に追加されたコンテンツ — を捕捉する完全性チェックである。
covers-EOF の計算は リビジョンを意識する ものであり、§6.4 の主題である。単一リビジョンの PDF では、古典的な start2 + len2 == pdf_bytes.len() に帰着する。
署名がコミットするバイト列は、2つのウィンドウの連結である。
covered = pdf_bytes[start1 .. start1+len1] || pdf_bytes[start2 .. start2+len2]
この covered スライスは detached コンテンツとして CMS パイプラインへ供給される(detached CAdES が用いる --detached-content スロットを置き換える)。コンテンツダイジェストは verify_content_digest(&parsed, &covered) によって計算され、署名属性ダイジェストと署名は verify_signed_attrs(&parsed, &signer_cert) によって計算される — いずれも CAdES と共有される(第5章)。
PDF が署名辞書を一切保持しない場合、verify_pdf は単一の INDETERMINATE エントリ(no_signature_entry)を返し、消費側が空配列ではなく確定した事実を見るようにする(verify_pdf、sig_dicts.is_empty() ガード)。
/DocTimeStampPDF は インクリメンタルアップデート をサポートする。後続のリビジョンは、以前のバイトを書き換えることなくバイト(新しいクロスリファレンスセクション、新しいオブジェクト、新しい %%EOF)を追加する。リビジョン1で作成された署名は、正当にリビジョン1の %%EOF のみをカバーする。リビジョン2で追加された文書タイムスタンプや第2の署名は、リビジョン2の %%EOF までのファイルをカバーする。これはまさに日本の官報(e-官報)PDF のレイアウトであり、PAdES-LTA の中心的なパターンである。
branch 009-v07-pades-incremental(v0.7)は、そのような文書が pdf_byte_range_does_not_cover_eof を誤って報告する原因となっていた、結合した2つのバグを修正した(specs/009-v07-pades-incremental/spec.md)。
%%EOF のみをカバーするリビジョン1の署名は失敗した。/Type /Sig のみを尊重し /Type /DocTimeStamp を暗黙に破棄していたため、リビジョン2の範囲を定義する文書タイムスタンプが不可視であった。リビジョン境界は、すべての %%EOF マーカーについて生のバイトをスキャンすることでホスト側で発見される(pdf.rs、scan_revision_ends。パリティ移植版は web/pverify-wasm/src/lib.rs)。PDF §7.2 は %%EOF の後に EOL マーカーをちょうど1つ要求するが、一部の生成器は連続する2つの改行(%%EOF\n\n)を出力する。スキャナーは EOL シーケンスを最大1つだけ absorb する。すなわち1つの \r\n、\n、または \r を読み進め、そこで停止する — 2つ目の末尾改行は消費しない。この「最大1 EOL」ルール(PR #126 off-by-1 修正)により、LGPKI2 や類似の生成器による PAdES-LT/LTA 文書が covers_own_revision チェックを通過できる。これらのリビジョン境界は %%EOF 後の最初の EOL で終わり、start2 + len2 の covered-end がちょうどそこに一致するからである。スキャンは PDF 構造理解の一部であるためホストに留まる(憲章 §V/§VI)。コアはディスクリプタごとに結果として得られる revision_end のみを消費する。
各ディスクリプタの revision_end は、その辞書の covered-end start2 + len2 以上にある最小の %%EOF オフセットであり、以上にあるものが存在しない場合はファイル長にフォールバックする(pdf.rs、revision_ends.iter().copied().find(|&e| e >= covered_end).unwrap_or(pdf_len) の代入)。
VerificationRequest は pdf_last_revision_end: u64 フィールドを保持する(デフォルト 0 は pdf_bytes.len() を意味する)。ホストがこの値を設定すると、カーネルは FR-702 の末尾コンテンツチェックに際してこの値で pdf_bytes.len() を上限として用いる。これにより、/DSS を追記するインクリメンタルアップデートを検出した呼び出し元が、その /DSS 追記リビジョンに対する pdf_byte_range_does_not_cover_eof の誤検知を抑制できる。
カーネルは verify_one_signature(crates/pverify-core/src/pades/mod.rs)でこのルールを適用する。max_revision_end をすべての辞書にわたる最大の revision_end(verify_pdf で一度計算)とする。covered-end start2 + len2 を持つ署名について:
covers_own_revision = (start2 + len2 == revision_end) — 署名がそれが閉じるリビジョンを正確にカバーする(FR-701)。is_final_revision = (revision_end == max_revision_end)。trailing_uncovered = (max_revision_end < pdf_bytes.len())。covers_eof = covers_own_revision && !(is_final_revision && trailing_uncovered)。言葉で表すと、署名が自身のリビジョンをカバーするとき、かつそのときに限り EOF をカバーする。ただし それが最後の署名されたリビジョンを閉じるにもかかわらず、そのリビジョンの後にカバーされていないバイトが続く場合は除く(FR-702)。第2の節は本質的な完全性である。いかなる署名もコミットしない末尾コンテンツは実際の欠陥(悪意ある追加の可能性)であり、既存の pdf_byte_range_does_not_cover_eof を通じて表出される — 新しいクローズドな列挙タグは追加されなかった(specs/009-.../spec.md の clarification Q2→A)。単一リビジョンの PDF では revision_end == max_revision_end == pdf_len であるため、ルールは古典的な start2 + len2 == pdf_len に縮退し、v0.7 以前のフィクスチャとのバイト単位の同一性を保つ。
/DocTimeStamp 辞書は、自身の ByteRange でカバーされたバイト列に対する RFC 3161 タイムスタンプトークンを保持する — これが PAdES のアーカイブタイムスタンプ機構である。verify_pdf は小さな状態機械でディスクリプタリストを文書順に処理する(crates/pverify-core/src/pades/mod.rs、for desc in sig_dicts ループ)。
Signature ディスクリプタは verify_one_signature によって検証される → 1つの SignatureEntry。これは「直近の先行する署名」となる。DocTimeStamp ディスクリプタは verify_doc_timestamp によって検証される(ByteRange を構造的に再チェックし、カバーされたバイト列を再計算し、crate::timestamp::verify_timestamp_token を ArchiveTimestamp ロールで呼び出す)。結果として得られる TimestampToken は:
timestamps[] に追加され、かつ そのエントリの VRT グラフに対するカバーするアーカイブトークンとして記録される(clarification 2026-06-17 Q1→A: /DocTimeStamp は、それ自身のエントリとしてではなく、先行する署名に対する archive_timestamp ロールを持つ TimestampToken として表出される)。doc_timestamp_only_entry となる。このグルーピングは、エントリと並行してインデックス付けされた、カバーする TimestampToken(doc_ts_per_entry)と由来する /DocTimeStamp ディスクリプタ(doc_ts_descs_per_entry)の両方を保持する。ディスクリプタは第2パスで必要となる。各アーカイブトークン自身の CMS を再パースし、オブジェクトごとの VRT による失効検証のためにその TSA 証明書を回復するためである(033、§6.5.5)。
すべての /DocTimeStamp がグループ化された後、verify_pdf は署名ごとに第2パス(apply_pades_vrt)を実行する。カバーグラフは signer ← signature-time-stamp ← /DocTimeStamp である(entry.timestamps の先頭にある署名 TS トークン、末尾にあるアーカイブ /DocTimeStamp トークン)。crate::vrt::derive_vrt(第12章)は、再帰的な外側カバーのルールによって各オブジェクトの VRT を計算する。署名者チェーンは署名 TS の GenTime へ繰り上げられ、署名 TS チェーンは /DocTimeStamp の GenTime へ繰り上げられ、最も外側の /DocTimeStamp は request_at にフォールバックする。
署名者チェーンの VRT が request.verification_time と異なる場合 — かつそのときに限り — 署名者チェーンは 繰り上げられた時刻で再検証される(revalidate_pades_signer_chain)。CMS は再パースされ、validate_path + wire_chain_step_signatures + apply_revocations および ETSI/バインディング/コンテンツタイプのレイヤリングが VRT で再実行される。これが PAdES-LTA の目玉である — 署名後に失効した署名証明書も、カバーするアーカイブタイムスタンプが署名時点で有効であったことを証明するため、依然として検証に成功する。チェーンを繰り上げるタイムスタンプが存在しない場合(一般的な PAdES-B/-T のケース)、第2パスは at_request_time プレースホルダの無操作の上書きとなるため、それらのレポートは追加的な vrt ブロックを除いてバイト単位で同一に保たれる。
/DSS、/VRI、revocationInfoArchival(branch 034)本節は本章の主たる主題である。branch 034-pades-dss-vri(JNSA ガイドラインのギャップレビュー)は、PAdES 文書が 自身について 保持する失効材料を pverify に消費させる。034 以前、PAdES のパスは空の埋め込みスライス(&[], &[])で失効パイプラインを呼び出していたため、完全に自己証明された オフライン の PAdES-LT/LTA は、必要とするすべての CRL および OCSP レスポンスが自身の /DSS の中に存在するにもかかわらず、INDETERMINATE / revocation_not_checked_offline で停滞していた(docs/pades-dss-vri.md)。034 はその材料をホスト側で読み取り、既存の 失効パイプラインへ通す。
| ソース | 存在場所 | キー方式 | 出所タグ |
|---|---|---|---|
文書 /DSS プール |
PDF カタログ /Root → /DSS → /Certs //CRLs //OCSPs |
文書グローバル | pdf_dss |
/DSS /VRI サブ辞書 |
/DSS → /VRI → <KEY> → /Cert //CRL //OCSP |
署名の /Contents の大文字16進 SHA-1 |
pdf_vri |
Adobe revocationInfoArchival |
CMS 署名済み/署名なし属性、OID 1.2.840.113583.1.1.8 |
署名自身の CMS に埋め込み | signature_embedded |
3つすべては 追加的な証拠 である。失効パイプラインは CRL を発行者+シリアルで、OCSP レスポンスを certID で照合するため、材料のスーパーセットは、不確定なものを確定した good/revoked へ 解決する ことしかできない — good な結果を捏造することは決してできない(data-model VR-5)。この性質こそが、リビジョン全体にわたる和集合と DSS グローバルフォールバックの挙動を安全にする。
/DSS+/VRI 抽出extract_validation_data(crates/pverify-cli/src/pdf.rs。パリティ移植版は web/pverify-wasm/src/lib.rs の extract_pdf_validation_data)は、文書レベルの材料を純粋データの PdfValidationData へパースする。
pades::PdfValidationData { dss_certs, dss_crls, dss_ocsps, vri: Vec<VriEntry> }
コードに根拠を置く抽出ルール:
lopdf は最終的なオブジェクトグラフを解決するが、より早いリビジョンに置き去りにされた古い /DSS を残す生成器に寛容であるため、抽出器はカタログの /DSS および すべてのオブジェクトを走査して見つかったその他すべての /DSS 状の辞書を収集し、それらのプールをすべて和集合する(pdf.rs、doc.catalog() とオブジェクト走査から構築される dss_dicts ベクタ)。和集合は、材料が発行者/シリアル/certID で照合される追加的な証拠であるからこそ安全である。collect_pool は、!out.contains(&der) の場合にのみデコードされたストリームのバイトを追加する — 重複排除はパース済みの同一性ではなく正確な DER による。ストリームは decompressed_content()(FlateDecode などを適用)でデコードされ、デコードエラー時には生のコンテンツにフォールバックする。/VRI のインデックス付け。 各 /DSS /VRI サブエントリは、その辞書名をキーとし、大文字化して contents_sha1_hex を形成し、その /Cert//CRL//OCSP プールを VriEntry に収集する。重複するキー(同一署名に対する後続リビジョンの VRI)はスキップされる。VRI エントリは決定論的な順序のためキーでソートされる(pdf.rs、data.vri.sort_by(...))。MAX_DSS_ENTRIES_PER_POOL = 4096 と MAX_DSS_STREAM_BYTES = 16 MiB が作業量を上限付ける。空または過大なストリームはスキップされる。不正形式または敵対的な文書は、無制限の作業を行ったりパニックしたりするのではなく「使用可能な材料なし」に縮退する — 部分的に不正形式な DSS でも、クリーンにパースできたエントリは依然として得られる。PdfValidationData は、すべての非 PDF 入力および /DSS を持たない PDF に対しては空(Default)である。これがバイト単位の同一性の保証(FR-008)である。/DSS を持たない PDF のレポートは、034 以前のレポートと schema_version 文字列のみが異なる(e2e テスト crates/pverify-cli/tests/pades_dss_vri_e2e.rs の no_dss_document_is_strictly_additive によって検証され、pdf_dss/pdf_vri の出所が一切現れないことをアサートする)。
2つの抽出器はバイト単位で並行な移植である(CLIとWASMのパリティ不変条件)。WASM 移植版は同一の MAX_DSS_* 定数と、同一の collect_pool/resolve_dict/scan_revision_ends/trim_to_outer_sequence ロジックを保持する。
カーネルは resolve_material(crates/pverify-core/src/pades/mod.rs)でオブジェクトごとの純粋計算による解決を行う。
key = uppercase_hex(sha1(object.contents))
material = vri.find(|e| e.contents_sha1_hex == key && !e.is_empty()) // → PdfVri
.unwrap_or(dss_{crls, ocsps, certs}) // → PdfDss
キーがオブジェクトの /Contents SHA-1 と一致する空でない /VRI エントリが優先される(出所 PdfVri)。さもなければ文書レベルの /DSS プールが用いられる(出所 PdfDss)。空の VRI エントリ(材料を一切参照しないもの)は優先されない — 解決はグローバルプールへフォールバックする(VriEntry::is_empty、ユニットテスト dss_vri_empty_vri_entry_falls_back_to_dss により行使される)。解決された証明書 DER はパス構築のためにパースされる。パースに失敗するエントリはスキップされる(resolve_material、filter_map(|der| crate::x509::parse_certificate(der).ok()))。
SHA-1 キーは sha1_uppercase_hex(crates/pverify-core/src/pades/mod.rs)によって計算され、コアの既存の SHA-1 機能(SHA-1 署名検証のため既に存在 — 新たな依存関係なし、FR-012)を再利用する。ソースのコメントは明示的かつ監査上重要である。ここでの SHA-1 は辞書ルックアップのキーとしてのみ用いられ、セキュリティプリミティブとしては決して用いられない。 その暗号学的弱さは無関係である。衝突が起こりうるのは 追加的な 証拠を誤ルーティングすることだけであり、それは good な結果を捏造できないからである(VR-5)。キー導出は、空文字列および "abc" に対する既知の SHA-1 ベクタに対して固定されている(dss_vri_sha1_uppercase_hex_matches_known_vectors)。
ResolvedMaterial は、provenance ∈ {PdfDss, PdfVri} を持つ内部の非ワイヤ構造体 { crls, ocsps, certs, provenance } である。
verify_one_signature において、署名者証明書を選択し中間証明書候補集合を構築した後、解決された材料が適用される(crates/pverify-core/src/pades/mod.rs)。
/Certs は中間証明書候補にのみ加わり、アンカーには決して加わらない(Q7/FR-005/VR-2)。 解決された各証明書は、未だ存在しなければ intermediates に追加される(fingerprint_hex で重複排除)。DSS 証明書はパス構築の候補である。構成されたトラストストア(anchors.anchors())がトラストアンカーの唯一のソースであり続ける。構成済みアンカーのフィンガープリントにたまたま一致する DSS 証明書も、依然として文書からではなく ストアから アンカーされる(出所 trust_anchor)。revocationInfoArchival は CMS から射影される(project_revocation_info_archival)、その CRL/OCSP DER は署名者側の材料へマージされる。これは PDF 構造ではなく CMS の中に存在するため、出所 pdf_dss ではなく signature_embedded を保持する。apply_revocations 呼び出しへ供給される(crates/pverify-core/src/pades/mod.rs、以前は &[], &[] を受け取っていた apply_revocations(..., &signer_ocsps, &signer_crls, mode) 呼び出し)。tag_document_revocation_provenance は結果として得られたチェーンステップを走査し、消費された DER が(供給されたバイトの SHA-256 をレコードの body_sha256/response_sha256 に対して)一致する任意の CRL/OCSP レコードについて、pdf_source = PdfDss または PdfVri を設定する。照合は正確かつ決定論的である — 再パースなし。revocationInfoArchival 材料はタグ付けされない(空の source_uri → signature_embedded のデフォルトを保持する)。revocationInfoArchival の射影(extract_rev_info_archival_from_attrs)は、CMS の署名済み/署名なし属性ブロブにわたるベストエフォートの BER 走査である(両方がスキャンされる。この属性は signedAttrs に「最も一般的」)。OID ボディ 2a 86 48 86 f7 2f 01 01 08 を照合し、値 SET → SEQUENCE → [0] crl SEQUENCE OF CertificateList / [1] ocsp SEQUENCE OF OCSPResponse をパースして、各内側 TLV を再エンコードする。[2] otherRevInfo は 文書化された逸脱 である — 本スライスでは無視される(crates/pverify-core/src/pades/mod.rs、_ => {} のアームとドキュメントコメント)。不正形式の入力はスキップされ、決して致命的ではない(iter_tlv ウォーカはエラーを返すのではなく最初の不正形式な長さで停止する、FR-011)。
第2パス apply_pades_vrt は、各 タイムスタンプの TSA チェーンに対し、そのオブジェクト自身のオブジェクトごとの VRT で失効を適用する(033 + 034)。entry.timestamps 内の各トークンについて:
/DocTimeStamp アーカイブトークンについてはそのトークン自身のディスクリプタの CMS 証明書(doc_ts_descs)。/Contents をキーとして resolve_material 経由で解決される — 埋め込み署名 TS については署名者の /Contents(埋め込み署名 TS 材料の VRI キー)、アーカイブカバーについては /DocTimeStamp 自身の /Contents。crate::verify::apply_tsa_chain_revocation_for_token(033 の共有ヘルパー、verify.rs:1552)が、解決された OCSP/CRL DER(以前は &[], &[])とともに、トークンの VRT で呼び出される(crates/pverify-core/src/pades/mod.rs、apply_pades_vrt のトークンごとのループ)。tag_document_revocation_provenance が TSA チェーンの消費されたレコードをタグ付けする。トークンごとのループの後、apply_tsa_revocation_indication が TSA チェーンの失効判定を最後にレイヤリングする。これが 033 の作業がもたらす一貫性である。失効した TSA 証明書はエントリを revoked_no_poe を伴う INDETERMINATE へ縮退させる(「断言できないことは断言しない」応答 — TSA 自身の証明書が失効していたため、pverify はタイムスタンプの時刻を証明できない)、偽造の主張を捏造するのではなく。
決定的な不変条件(FR-004a/Q5/VR-4)。DSS/VRI/revocationInfoArchival 材料は、ライブ取得 / CDP ルックアップ自体が不確定であるときに のみ 発火する 019 で導入されたのと同じ埋め込み CRL/OCSP チャネル に乗る。apply_revocations および apply_tsa_chain_revocation_for_token 呼び出しは、その材料を埋め込み OCSP / 埋め込み CRL の引数として受け取り、失効エンジン(第11章)はそれらのチャネルをオーバーライドとしてではなくフォールスルーとして参照する。帰結:
revocation_not_checked_offline で停滞していた オフライン 検証は、文書自身の証拠から確定的な結果に到達するようになる(e2e テスト dss_borne_crl_reaches_definitive_offline_and_is_tagged_pdf_dss の目玉。これは ネットワークアクセスなし で署名者チェーンが good であることを証明し、消費された CRL を pdf_dss とタグ付けする)。これは恒常的なオフラインの誠実性の規律(憲章 §VII)である。--offline はソケットを開かず、誠実な IndeterminateRevocationOffline は、文書がそれを確立する証拠を保持するときにのみ確定した結果へ置き換えられる。
DSS/VRI が供給する CRL または OCSP レスポンスは、既存の RevocationOutcome 値を生む — 新しい結果なし、新しいサブインディケーションなし(Q2/FR-007)。具体的には(docs/pades-dss-vri.md):
TOTAL_FAILED(既存の失効集約を経由)。INDETERMINATE / revoked_no_poe。唯一の追加的な ワイヤ 表面は出所であり、次に説明する。
034 のワイヤ表面は純粋に追加的な出所であり、ルートの report-schema.json へ 1:1 で反映される。
ValidationObjectOrigin 値、pdf_dss および pdf_vri。既存の5つの 後ろに 追加される(crates/pverify-core/src/report/validation_objects.rs、enum ValidationObjectOrigin)。宣言順序が origin[] 配列のソート順序 そのもの であるため、既存の配列はバイト安定に保たれる。導出はレコードの PdfRevocationSource を出所へマップする(validation_objects.rs、Some(PdfRevocationSource::PdfDss) => ValidationObjectOrigin::PdfDss のアーム)。unsignedAttrs 埋め込みのものと区別できるようにする。CrlSummary.pdf_source および OcspAttempt.pdf_source、いずれも Option<PdfRevocationSource>(crates/pverify-core/src/report/mod.rs、enum PdfRevocationSource { PdfDss, PdfVri }、および pdf_source フィールド)。いずれも不在時に省略されるため、1.5.0 のリーダはそれらを無視する。SCHEMA_VERSION はブランチ 034 の着地時に 1.5.0 → 1.7.0 にバンプした(crates/pverify-core/src/report/schema.rs)。後続スライス(035 OCSP nonce、CdpEntry 構造化)によってさらに 1.9.0(現在値)まで進んでいる。本節は 034 固有のワイヤ追加を文書化する。完全な lineage は第14章を参照。バンプは追加的な MINOR 変更である。クローズドな列挙値またはオプションフィールドの追加は MINOR であり、削除または再目的化は MAJOR である(第14章)。スキーマのドキュメントコメントは、新しい失効結果なし、新しい ETSI サブインディケーションなし、新しいトップレベルフィールドなし であること、および 034 が 憲法的な表面ではない(ポリシーテーブルなし。失効語彙を再利用する)ことを明示する。/DSS//VRI/revocationInfoArchival を持たない入力は、schema_version 文字列を除いて 1.5.0 のボディとバイト単位で同一である。
別個の、より早い修正(PR #48、commit 0650b2d)は、実際の日本の公的 PDF が検証できるようになる以前に必要であり、PAdES モジュール内ではなく共有のパス検証ポリシー処理(crates/pverify-core/src/path/policy.rs、compute_outcome)の中に存在する — しかしその動機となるユースケースは PAdES であるため、ここに文書化する。
RFC 5280 §6.1.5 は証明書ポリシー処理のリーフでの締め括りを統べる。#48 以前の実装は、空の valid_policy_tree を 無条件に 拒否しており、これが実際の GPKI/官報チェーンを失敗させていた。そのようなリーフは発行者と異なるポリシー OID を policyMappings なしで保持するため、チェーンが健全であっても valid_policy_tree はリーフで正当に空になる。修正されたロジック(compute_outcome、is_leaf ブロック)は RFC 5280 に正確に従う。
requireExplicitPolicy が発火したとき のみである(explicit_policy_pending == Some(0) としてモデル化)。requireExplicitPolicy が一度も主張されなかった場合、{anyPolicy} のユーザ初期ポリシー集合の下での空のツリーは 有効 である — explicit_policy > 0 または ツリーが非 NULL のとき、パス処理は成功する。requireExplicitPolicy が発火したとき、空のツリーは Inconclusive { EmptyTree } を生み、anyPolicy のみを含む(特定のポリシーが生き残らなかった)ツリーは Inconclusive { ExplicitPolicyViolated } を生む。user_initial_policy_set(CLI の --required-policy)はリーフでさらに絞り込む。少なくとも1つのツリーノードが集合内にある特定のポリシーを保持しなければならず、さもなければ Inconclusive { EmptyTree }。対応する ETSI 表面は、requireExplicitPolicy の空ツリーおよびポリシー集合の積が空となるケースに対する IndeterminatePolicyRejected(crates/pverify-core/src/report/etsi.rs)である。より古い PolicyProcessingInconclusive タグは保存済みレポートのデシリアライズのために保持される。PAdES のパスは同じ required_policies を validate_path へ通す(branch 029、verify_one_signature および revalidate_pades_signer_chain)ため、PAdES は CAdES と同一のユーザ初期ポリシー集合の絞り込みを強制する — 空のスライスは依然として {anyPolicy} を意味し、--required-policy なしの実行との互換性を保つ。
| 条件 | 結果 | サブインディケーション / 出所 |
|---|---|---|
| ByteRange ウィンドウが順序外 / 範囲外 / オーバーフロー | TOTAL_FAILED |
pdf_byte_range_malformed |
| 署名が自身のリビジョンをカバーしない、または最終リビジョンがカバーされていない末尾バイトを残す | TOTAL_FAILED |
pdf_byte_range_does_not_cover_eof |
生成器が %%EOF\n\n(二重改行)を出力する |
誤検知しない(最大1 EOL ルール、PR #126) | — |
| PDF が署名辞書を保持しない | INDETERMINATE |
(単一の署名なしエントリ) |
CMS が複数の SignerInfo を保持する |
INDETERMINATE |
cms_multi_signer_unsupported |
オフライン、ライブ失効なし、しかし /DSS//VRI/revocationInfoArchival が CRL/OCSP を供給する |
確定(good なら TOTAL_PASSED) |
消費された材料を pdf_dss / pdf_vri / signature_embedded とタグ付け |
| DSS に由来する CRL/OCSP により署名者証明書が失効 | TOTAL_FAILED |
既存の失効結果 |
| DSS に由来する CRL/OCSP により TSA 証明書が失効 | INDETERMINATE |
revoked_no_poe(033) |
| 供給された材料に一致する失効レコードなし | 変更なし(不確定) | 追加的な証拠は決して good を捏造しない |
リーフの valid_policy_tree が空、requireExplicitPolicy なし |
拒否されない(§6.1.5 緩和) | — |
| DSS 証明書が構成済みアンカーに一致 | ストアからアンカーされる | 出所 trust_anchor、決して pdf_dss ではない |
| DSS/VRI/revInfoArchival が存在しない | 034 以前とバイト単位で同一 | schema_version のみが異なる(1.9.0) |
| LGPKI2 署名者証明書(LDAP のみの CRL) | オフライン検証は /DSS 埋め込み CRL に依存。lgpki2-org-ca-r2.json トラストアンカー台帳追加済み |
— |
全体を通じて、PAdES 検証器はシステムの横断的な規律を尊重する。PDF パースはホスト側に留まる(憲章 §V/§VI)。カーネルは I/O を行わず WASM クリーンである。追加的な材料は、ライブの成功を決してオーバーライドしないフォールスルーとしてのみ流れる(再現性 / バイト単位の同一性)。そして事実が確立できないところでは、検証器は判定を捏造するのではなく、正確なクローズドな列挙のサブインディケーションを伴う INDETERMINATE へ縮退する(憲章 §I、「断言できないことは断言しない」)。
detect_format)、エンドツーエンドのデータフロー。apply_pades_vrt が使用する。schema_version のセマンティクス、ValidationObjectOrigin、derive_validation_objects の出所導出。本章では、pverify が XML 高度電子署名(XAdES、ETSI EN 319 132 / JIS X 14533‑2) および関連署名コンテナ(ASiC、ETSI EN 319 162)をどのように検証するかを述べる。 両者はいずれも委譲型の形式である。ホストクレートである pverify-xades と pverify-asic がすべての XML/ZIP のパースと 正規化(C14N)を実施し、I/O を行わない no_std カーネル(pverify-core)へ モデルに依存しないバイト構造を引き渡す。カーネルはその後、CAdES(第5章を参照) および PAdES(第6章を参照)に用いるものと同一の RFC 5280 パス検証、 RFC 6960/5280 失効検証、RFC 3161/5816 タイムスタンプ、 ETSI EN 319 102‑1 インディケーション機構を通してそれらを処理する。 アーキテクチャ上の境界とクレートのトポロジは第3章に記述されている。 本章は XML 署名と ZIP コンテナに形式固有なものに焦点を当て、 どのプロファイルが検証されるか、あるいは未対応として明示されるかを正確に示す。
対応する AdES レベルは埋もれてしまわないよう冒頭で明示する。
| 形式 | B‑B | B‑T | B‑LT | B‑LTA |
|---|---|---|---|---|
| XAdES(enveloped、Exclusive C14N) | ✅ 検証 | ✅ SignatureTimeStamp + SigAndRefsTimeStamp |
✅ 埋め込み CertificateValues/RevocationValues |
✅ ArchiveTimeStamp インプリント検証済み(ds:Signature 全体の exc‑C14N から後続 ATS ノード集合を subtract、ブランチ 036) |
| XAdES(detached / enveloping、スタンドアロン) | ❌ INDETERMINATE xades_unsupported_profile |
— | — | — |
| XAdES(非 Exclusive C14N) | ❌ INDETERMINATE xades_unsupported_canonicalization |
— | — | — |
| ASiC‑S/E(CAdES へ委譲) | ✅ CAdES パイプライン経由 | ✅ | ✅ | ✅(CAdES archive‑time‑stamp‑v3) |
| ASiC‑S/E(XAdES へ委譲) | ✅ XAdES パイプライン経由 | ✅/⚠️ 上記 XAdES に準拠 | ✅ | ⚠️ 上記 XAdES に準拠 |
全体を通じて譲れない唯一の原則は、憲法 §I の 「断定回避」(断言できないことは断言しない)の規律である。pverify が事実を 確立できない場合(未対応の正規化、パース不能なタイムスタンプトークン、 コンテナに存在しないデータオブジェクトなど)には、正確なクローズドな列挙 (閉じた語彙)のサブインディケーションを伴って INDETERMINATE へ降格し、 捏造された TOTAL_FAILED や TOTAL_PASSED を返すことは決してしない。
カーネルは XML をパースできない。それを行えば DOM と正規化機構を no_std/WASM クリーンなコアに引き込むことになり、憲法 §V(ライブラリファースト) および §VI(WASM クリーンなコア)に違反する。代わりに、ホストクレート pverify-xades(crates/pverify-xades/src/lib.rs)が XML をパースし、 監査済みの bergshamra-c14n クレートを通して Exclusive XML 正規化を実施し、 ds:Signature 要素ごとに1つの、モデルに依存しない XadesComponents 値を 出力する。この境界型はカーネルが所有しており(crates/pverify-core/src/xades.rs、 XadesComponents)、ホストクレートから再エクスポートされるため、 VerificationRequest へ渡る形状について単一の真実の源が存在する。境界を越えるのは Vec<u8> と小さなクローズドな列挙のみであり、XML ノードも std 型も渡らない。 カーネルの検証器 verify_xades(crates/pverify-core/src/xades.rs)は1つの XadesComponents を消費して1つの SignatureEntry を生成し、署名者証明書が 手に入った時点から既存の RFC 5280 / 失効 / ETSI パイプラインを再利用する。 これは verify_cades(crates/pverify-core/src/verify.rs)と意図的に同一である。
境界フィールドは、いずれもホストが既にデコードと正規化を済ませており、 カーネルが XML に触れることは決してない。各フィールドは以下のとおりである。
フィールド(XadesComponents) |
意味 |
|---|---|
signed_info_c14n |
ds:SignedInfo の Exclusive‑C14N 正規化バイト列 — 署名値が検証される対象のメッセージ |
signature_method |
SignatureMethod URI から解決された XadesSigAlg(RSA PKCS#1 v1.5 / ECDSA / RSA‑PSS / Unsupported) |
signature_value |
base64 デコードされた ds:SignatureValue。ECDSA の場合、ホストは既に XML‑DSIG の固定幅 r‖s を DER の Ecdsa-Sig-Value に変換済み |
signer_cert_der |
ds:KeyInfo/ds:X509Data/ds:X509Certificate の DER。None ⇒ 署名者証明書が利用不可 |
references |
すべての ds:Reference。既に変換 + 正規化済み(transformed_c14n)で、その digest_alg と expected_digest を伴う |
signing_cert_v2 |
XAdES の SigningCertificateV2 バインディング(CertDigest + alg + 任意の IssuerSerialV2) |
profile |
EnvelopedBB、DetachedResolved(ASiC)、または Unsupported{reason} |
signature_timestamps |
B‑T の SignatureTimeStamp トークン + 正規化済みインプリント入力 |
sig_and_refs_timestamps |
B‑T の SigAndRefsTimeStamp トークン + 再構築された type‑1 インプリント入力 |
unverified_timestamps |
RefsOnlyTimeStamp トークン、およびインプリントを計算できない ArchiveTimeStamp トークン(例:XPath トランスフォームを使用するもの — §7.5.1 参照)を XadesUnverifiedTimestamp として表出。各エントリは事前計算済みの imprint_input: Vec<u8>(未対応形式は空)を保持する |
embedded_certs_der / embedded_crls_der / embedded_ocsp_der |
B‑LT の CertificateValues / RevocationValues 材料 |
B‑T/B‑LT のフィールドはすべて追加的であり、ブランチ 019 により導入された。 素朴な B‑B 署名は UnsignedSignatureProperties を持たないためそれらは空のまま 残り、すなわち B‑B レポートは 019 以前の出力とバイト単位で同一になる (再現性の不変条件、憲法 §II)。同じバイト同一性の規律はカーネルの挙動も統制する。 追加的な埋め込み失効情報のチャネルは、ライブ/CDP チャネルが判定不能なときにのみ 発火する。
XML 正規化は XML 署名検証の中で要となる部分であり、また歴史的に最も 誤りを生みやすい部分でもある。ダイジェスト計算され署名される対象のバイト列は、 ディスク上のリテラルなバイト列ではなく、ノード集合の正規シリアライズだからである。 pverify は2つの異なる正規化アルゴリズムを用い、それぞれ特定の箇所に適用する。 いずれも監査済みの bergshamra-c14n クレートが提供する (crates/pverify-xades/src/c14n.rs)。
Exclusive XML 正規化(xml-exc-c14n#、W3C)。ds:SignedInfo (検証されるメッセージ)と各々の同一文書内 ds:Reference サブツリーに適用する。 Exclusive C14N は、可視的に使用されていない継承された名前空間宣言を刈り取り、 InclusiveNamespaces PrefixList が存在する場合はそれを尊重する。ホストは その接頭辞リストを CanonicalizationMethod/Transform 要素から読み取り (inclusive_prefixes、crates/pverify-xades/src/parse.rs)、 exc_c14n_subtree へ引き渡す。
素の Canonical XML 1.0(REC-xml-c14n-20010315、コメントありおよびなし)。 RFC 3161 タイムスタンプのメッセージインプリント計算(§7.5)にのみ適用する。 Exclusive C14N とは異なり、素の C14N はスコープ内のすべての名前空間を出力し、 継承された宣言は刈り取られない。したがって inclusive‑prefix リストを取らない。 これは bergshamra-c14n の既存の C14nMode::Inclusive 上で実装されている (B‑B ビルドが既にこれをコンパイルするため、B‑T では新たな依存は追加されなかった — crates/pverify-xades/src/c14n.rs::plain_c14n_subtree)。
「タイムスタンプインプリントには素の C14N、ds:SignedInfo には Exclusive C14N」 という使い分けは恣意的な選択ではない。これは実際の EU DSS XAdES コーパスに対して バイト単位で正確であることが確認された(019 のリサーチノートは、ds:SignedInfo 自体が Exclusive C14N を使う場合であっても、SignatureTimeStamp がインプリント する対象のデータに対しては素の C14N 1.0 を宣言することを記録している)。 両方の正規化面は libxml2 のゴールデンオラクルに対してバイト単位で回帰固定されており (crates/pverify-xades/tests/exc_c14n_golden.rs および tests/plain_c14n_golden.rs)、正規化器のいかなるドリフトも、現場での検証 不一致として発見されるのではなく、決定論的に捕捉される。
enveloped 署名変換(W3C XML‑DSIG §6.6.4)は、正規化に隣接する3つ目の操作である。 URI="" と #enveloped-signature 変換を伴う参照は、文書全体から ds:Signature サブツリーを差し引いたものをダイジェストする。ホストは、 全文書のノード集合を取り、署名サブツリーを差し引き、その後 Exclusive C14N を 適用することでこれを計算する(exc_c14n_enveloped、 crates/pverify-xades/src/c14n.rs)。まさにこの URI="" + enveloped 変換 参照の存在が、署名を対応済みの enveloped プロファイルとして分類する根拠となる (§7.4)。
verify_xades(crates/pverify-core/src/xades.rs)は、固定された事実順序の 手順を実行する。各失敗は特定の結果へマップされる。この順序は意図的なものであり、 確定的な偽造が、より弱い判定不能の条件より先に検出され、それによって決して 隠蔽されないようにしてある。
ステップ 1 — プロファイルとアルゴリズムのゲーティング。 ホストが profile を Unsupported と分類した場合、カーネルは直ちに INDETERMINATE を返す。 サブインディケーションは xades_unsupported_profile(detached/enveloping の パッケージング)または xades_unsupported_canonicalization(非 Exclusive C14N) のいずれかである。Unsupported な SignatureMethod URI は INDETERMINATE signature_algorithm_unsupported を返す。これらは断定回避の結果である。 pverify は形状を認識するが、実装していないものの検証を、推測するのではなく辞退する。
ステップ 2 — 署名者証明書の存在。 signer_cert_der == None → INDETERMINATE xades_signer_certificate_unavailable。同じ結果は、DER パースに 失敗した証明書や、SubjectPublicKeyInfo を再エンコードできない証明書 (extract_spki_der)も対象とする。
ステップ 3 — 参照ダイジェスト。 すべての ds:Reference について、カーネルは digest_alg(transformed_c14n) を再計算し、格納された expected_digest と 比較する。いかなる不一致も TOTAL_FAILED xades_reference_digest_mismatch であり、署名された内容が改変されたという積極的証拠である。これは署名値より前に 検査される。なぜなら参照の不一致は、署名値の暗号処理とは独立した内容完全性の 失敗だからである。
ステップ 4 — SignedInfo に対する署名値。 カーネルは、署名者証明書の SubjectPublicKeyInfo を用いて、signed_info_c14n に対する signature_value を 検証する。RSA‑PSS はホストが解決したソルト長を伴って直接ディスパッチされる (verify_rsa_pss)。RSA PKCS#1 v1.5 と ECDSA は共有の OID 駆動の verify_with_alg に到達する(ホストは既に ECDSA の r‖s を DER に変換済みのため、 既存の検証器は変更されない)。真正な検証失敗は TOTAL_FAILED signed_attrs_signature_failed(CMS のクラスを再利用 — XML 署名値は CMS 署名者 署名の対応物である)。この時点で表出した未対応のアルゴリズムは INDETERMINATE signature_algorithm_unsupported へ降格し、「誤り」と 「評価できない」の間の事実報告上の区別を保持する。
ステップ 5 — SigningCertificateV2 バインディング。 署名が XAdES の SigningCertificateV2 を持つ場合、カーネルは宣言された DigestMethod の下で 署名者証明書 DER のダイジェストを再計算し、格納された CertDigest と比較する。 不一致は TOTAL_FAILED xades_signing_certificate_mismatch であり、証明書 すり替えに対する防御である(攻撃者が埋め込み証明書を、その鍵もまた再署名された SignedInfo を検証できる別の証明書にすり替える攻撃はここで捕捉される)。 このバインディングはダイジェストに基づく。IssuerSerialV2 は記録されるが 判別子としては用いられない。
ステップ 6 — パス検証、失効、ETSI。 ここから先のフローは verify_cades と 同一である。validate_path(検証時刻における RFC 5280 §6 のパス構築)、 wire_chain_step_signatures(RustCrypto による証明書署名検証)、署名者チェーンに 対する apply_revocations、そして aggregate_etsi による ETSI 集約である。 埋め込まれた B‑LT 証明書はチェーン材料として利用可能とされ、埋め込まれた OCSP/CRL DER は既存の埋め込み材料チャネルを通して供給されるため、 オフラインで確定する署名者失効の判定に到達できる。
成功時、レポートは XAdES 署名がコミットするバイト列を記録する。signed_content 検証対象オブジェクトのダイジェストは、Exclusive‑C14N の ds:SignedInfo の SHA‑256 に設定される(covered_content_sha256)。CMS 形状の signed_attrs と content_digest ブロックは該当なしの事実として記録される(XAdES には CMS の messageDigest が存在しない)ため、レポートを読む消費者は、捏造された一致では なく誠実な「該当なし」を目にする。
弱いアルゴリズムの取り扱い。 SignatureMethod または任意の参照の DigestMethod における SHA‑1 は認識して旗を立てるのであり、決して拒否しない — これは CAdES/PAdES の姿勢および憲法 §I と整合する。カーネルは署名を通常どおり 検証し、署名者ステップに対して WeakAlgorithmFlag を記録し(flag_if_weak 経由)、ホストの aggregate_for_header がそれをレポートヘッダに表出する。これは オプトインのアルゴリズムポリシー判定(§7.6)とは区別される。後者は別個の、 時刻でスコープ付けされた評価である。
ホストは XML アルゴリズム URI を crates/pverify-xades/src/algid.rs で モデルに依存しない記述子へ解決する。対応する SignatureMethod のファミリは 以下のとおりである。
| URI ファミリ | XadesSigAlg |
|---|---|
xmldsig#rsa-sha1、xmldsig-more#rsa-sha{256,384,512} |
RsaPkcs1v15{digest} |
xmldsig-more#ecdsa-sha{1,256,384,512}(2001/04 および 2007/05 名前空間) |
Ecdsa{digest} |
xmldsig-more#sha{256,384,512}-rsa-MGF1(レガシーな RFC 4051) |
RsaPss{digest, salt_len = digest_len} |
xmldsig-more#rsa-pss(RFC 6931) |
RsaPss。digest/SaltLength は <pss:RSAPSSParams> 子要素から読み取る |
| その他 | Unsupported{uri} → INDETERMINATE |
DigestMethod URI は SHA‑1/256/384/512 へマップされる。認識されないダイジェスト URI は未対応である。SHA‑1 は妥当な認識済みのダイジェストであり(検証された後に 弱いとして旗を立てる)、未対応値ではない — ここでも事実報告の原則が働く。
スタンドアロンの XAdES 対応は、Exclusive 正規化を伴う enveloped プロファイルに意図的にスコープを絞っている。これは e‑Gov の公文書 XML 署名 (官職署名)、法務省の電子定款、および EU の LOTL/トラステッドリストで用いられる 形状である。ホストは process_reference / extract_one (crates/pverify-xades/src/parse.rs)でプロファイルを次の優先順位で決定する。
CanonicalizationMethod が Exclusive C14N でない場合 → Unsupported{NonExclusiveC14n}。resolver が供給され(ASiC のケース、§7.7)、非フラグメントの ds:Reference URI が ZIP エントリへ解決された場合 → DetachedResolved。URI="" + enveloped 署名参照が存在しない場合 → Unsupported{DetachedOrEnveloping}。EnvelopedBB。DetachedResolved への引き上げは狭い。それは、ASiC 抽出器が既に参照 URI を 具体的なバイト列へ解決済みである場合にのみ、detached のゲートを引き上げる。 スタンドアロンの detached/enveloping XAdES は依然として Unsupported へ マップされるため、ASiC コードが存在するか否かにかかわらず enveloped パス (011/019)はバイト単位で同一である。これが、pverify が別個の detached 検証器を 導入することなく ASiC‑E XAdES のために XAdES の参照ダイジェスト機構を再利用する 方法である(§7.7)。
ブランチ 019 が B‑T および B‑LT 対応を追加した。スコープ内の B‑T タイムスタンプは SignatureTimeStamp と SigAndRefsTimeStamp である。両者は RFC 3161 トークンであり、 共有の crate::timestamp::verify_timestamp_token を通して検証される。ホストは インプリント対象のデータを正規化し(カーネルは生のインプリントバイト列のみを 受け取る、§V)、カーネルは CAdES がその署名タイムスタンプに対して行うのと まったく同様にインプリントを計算して比較する。
SignatureTimeStamp。 インプリントは素の C14Nで正規化された ds:SignatureValue にコミットする(R‑1、DSS コーパスに対してバイト確認済み)。 ホストは ds:SignatureValue を素の C14N 1.0 で正規化し、それを imprint_input として格納する。カーネルは RFC 3161 トークンとインプリントを検証する。
SigAndRefsTimeStamp。 これは本スライスで最も難しい計算である。 ETSI TS 101 903 §7.5.1 の type‑1 カバレッジに従い、インプリントは順序付き連結に 対して計算される。各サブツリーは素の C14N で正規化され、ds:SignatureValue に 続いてすべての SignatureTimeStamp、CompleteCertificateRefs、 CompleteRevocationRefs、および(存在する場合)AttributeCertificateRefs / AttributeRevocationRefs を、UnsignedSignatureProperties の下でタイムスタンプ 自体まで(自体は含まない)文書順に辿る(build_sig_and_refs_imprint、 crates/pverify-xades/src/parse.rs)。この再構築は本形式において最も高リスクな バイト単位の正確さを要する操作である。設計は明示的に誠実な判定不能である。 再構築された連結がインプリントを再現しない場合、カーネルは message_imprint_match = false を記録してそれを表出し、一致を捏造することは しない。選定された DSS フィクスチャに対しては type‑1 インプリントが実際に再構築 され、降格ではなく真正な TOTAL_PASSED を生む。
インプリント不一致の判定。 スコープ内のタイムスタンプのインプリントが一致 しない(または検証できなかった — 例えばタイムスタンプが未対応の正規化を宣言した、 canonicalization_supported = false)場合で、かつ署名の残りがそれ以外は妥当である (基底の集約が TOTAL_PASSED を返した)場合、カーネルは INDETERMINATE xades_timestamp_imprint_mismatch へ降格する。署名は偽造されていないが、その 主張された時刻を確証できない(ETSI EN 319 102‑1)。真正な TOTAL_FAILED の 署名者/参照失敗は優先を保つ。降格は通過した判定にのみ作用し、証明された失敗を 隠蔽することは決してない。
B‑LT 埋め込み材料。 CertificateValues/EncapsulatedX509Certificate は チェーン材料となる。RevocationValues/CRLValues/EncapsulatedCRLValue と OCSPValues/EncapsulatedOCSPValue は既存の埋め込み CRL/OCSP チャネルへ供給される。 形式固有の微妙な点が1つある。XAdES の EncapsulatedOCSPValue は完全な RFC 6960 の OCSPResponse(responseStatus + responseBytes ラッパ)を運ぶのに対し、 カーネルは(CAdES の revocation-values.ocspVals と同様に)裸の BasicOCSPResponse を消費する。したがってホストは、それを引き渡す前に最小限の 手書き DER 走査(unwrap_ocsp_response、crates/pverify-xades/src/parse.rs)で OCSPResponse を内側の BasicOCSPResponse へアンラップする。構造が一致しない ものはそのまま返されるため、カーネルはそれを事実として評価または拒否し、決して パニックしない。いずれかの B‑LT ペイロードの存在は、報告される format を XAdES-B-LT へ昇格させる。LT ペイロードのない検証済み署名タイムスタンプは XAdES-B-T であり、いずれでもなければ XAdES-B-B である。
オブジェクトごとの VRT。 あらゆる形式と同様、各オブジェクト(署名者チェーン、 署名者署名、各署名タイムスタンプ)は、再帰的な外側カバーのエンジン (crate::vrt::derive_vrt、第12章を参照)によって導出される、それ自身の 検証基準時刻(VRT)で判定される。署名タイムスタンプの GenTime が署名者チェーンの VRT を request 時刻より後へ繰り上げる場合、カーネルはその繰り上げられた時刻で チェーンを再検証する(crates/pverify-core/src/xades.rs)。これにより、署名後に 失効はせず期限切れになったが、有効な間にタイムスタンプされた署名者証明書も 依然として検証される — これが B‑LT の主たる成果である。TSA チェーンの失効検証は、 共有の apply_tsa_chain_revocation_for_token ヘルパ(ブランチ 033)を通して、 各タイムスタンプそれ自身の VRT においてタイムスタンプごとに適用され、TSA 失効 整合性に関して XAdES を CAdES/PAdES と同等の水準にする。
ArchiveTimeStamp インプリント検証(ブランチ 036)ブランチ 036-xades-blta は XAdES B‑LTA の ArchiveTimeStamp インプリント再計算を実装し、 この層に関する §I のギャップをクローズする(ETSI TS 101 903 v1.4.2 Annex A.1.5)。
SignatureFormat::XadesBLta("XAdES-B-LTA")は、UnsignedSignatureProperties に少なくとも1つの ArchiveTimeStamp が存在するときに昇格する。B‑T/B‑LT の各レベルと同様、 昇格は認識された構造的事実であり、主張されたプロファイルではない。
インプリント構築。 インプリント入力は、ds:Signature 要素全体を対象とした Exclusive‑C14N(bergshamra-c14n)シリアライズから、後続の ArchiveTimeStamp 要素 (現在のトークンをインデックス k-1 とするとき、インデックス k 以降のもの)の ノード集合を差し引いたものである。これが「非循環 imprint」の規律である。各 ATS は それより前のすべての ATS と署名本体にコミットし、前向きのハッシュチェーンを形成する。 一方、再計算対象のトークン自身は除外されるため、そのインプリントを独立に検証できる。 事前計算済みバイト列はホスト(pverify-xades)によって XadesUnverifiedTimestamp.imprint_input: Vec<u8> として格納される。 カーネル(verify_xades)は XML に触れることなく、これらのバイトで verify_timestamp_token を呼び出すだけである。
共有 LTA インフラ。 LongTermArchivalIndication と ArchiveTimestampImprintRecord は CAdES B‑LTA パス(第5章)と共有され、新しい型は不要である。検証済みの XAdES アーカイブタイムスタンプは embedded_validation_data.long_term_archival_indication に反映される (以前は LT ペイロードが存在する場合でも XAdES では no_archive_timestamp であったが、 インプリントが検証された際に実際の LongTermArchivalIndication が設定されるようになった)。
スコープと制限。 現在の実装がカバーするもの:
ArchiveTimeStamp、またはノード集合が明確に定義される短い順序付き連鎖。ArchiveTimeStamp(ArchiveTimeStampV3 は非対応)。スコープ外:
ArchiveTimeStamp チェーン(長い ATS チェーンのループ処理は将来のスライス)。ArchiveTimeStamp トークン:ホストに XPath 評価器が ないため再計算不可。これらは空の imprint_input を持つ unverified_timestamps に表出され、 判定を降格しない(断定回避)。DSS XAdES‑LTA 相互運用フィクスチャが XPath トランスフォームを 使用するため、これらは expectations.json から除外されている。arcTst(別フォーマット、本スライスのスコープ外)。RefsOnlyTimeStamp は従来どおり「存在するが未検証」として表出される(インプリント再計算なし)。
オプトインのアルゴリズムポリシーが有効な場合(--algorithm-policy、既定では無効)、 evaluate_xades_algorithm_validity(crates/pverify-core/src/xades.rs)は オブジェクトごとのインベントリを組み立て、各オブジェクトのダイジェスト / 署名 ファミリ / 鍵長を、そのオブジェクトの VRT 時点でポリシーに照らして評価する (031 の vrt ブロックから逐語的に読み取り、決して再計算しない)。署名者署名の ファミリとダイジェストは SignatureMethod 記述子から、鍵長は署名者 SPKI から 得られる。署名者チェーンオブジェクトはリーフ証明書それ自身の signatureAlgorithm を用いる。各署名タイムスタンプはそのインプリントダイジェスト で判定される。結果は apply_algorithm_validity_indication を通してインディ ケーションに重ねられる。これは ETSI の CRYPTO_CONSTRAINTS_FAILURE_NO_POE の 姿勢を反映し、TOTAL_FAILED を決して生成せず、また決してそれを隠蔽しない。 ポリシーが無効のとき、このフィールドは None であり、レポートはポリシーなしの 実行とバイト単位で同一である(憲法 §II)。アルゴリズムポリシーのエンジン自体に ついては第13章を参照。
ASiC(ETSI EN 319 162)は、1つ以上のデータオブジェクトを1つ以上の detached な AdES 署名へ束ね、加えてコンテナのプロファイルを識別する mimetype エントリを持つ ZIP アーカイブである。pverify は ASiC‑S(単一データ オブジェクト)と ASiC‑E(複数データオブジェクト、マニフェストで記述)に対応し、 各内部署名を既存の detached CAdES または detached XAdES パイプラインへ委譲する ことで検証する。唯一の真に新しい暗号上の判断は、ASiC‑E CAdES のマニフェスト ダイジェストチェーンである(§7.9)。
XAdES と同様、カーネルは ZIP/DEFLATE をパースできない。それを行えば zip + flate2 + miniz_oxide をコアグラフへ引き込むことになる(scripts/cargo-tree-gate.sh によりゲートで禁止されている)。ホストクレート pverify-asic (crates/pverify-asic/src/lib.rs)はコンテナをメモリ上で読み取り、 META-INF/ の署名材料とデータオブジェクトを発見し、ASiCManifest をパースし、 detached XAdES のために ZIP 相対の ds:Reference URI を解決し、署名ごとに1つの、 モデルに依存しない AsicSignature を出力する(境界型は crates/pverify-core/src/asic.rs が所有する)。ホストはこれらを VerificationRequest.asic_signatures へ引き渡す。カーネルの検証器 verify_asic_signature(crates/pverify-core/src/asic.rs)は、各々をその AsicPayload のバリアントが選択する再利用パスを通してマップする。
ディスパッチの優先順位。 ZIP コンテナはローカルファイルヘッダのマジック PK\x03\x04 で始まる。これはバイトのスニフがそのままでは CAdES へルーティングして しまうものである。そこで verify_with(crates/pverify-core/src/verify.rs)は 形式スニフより前に request.asic_signatures を検査する。ホストがいずれかの ASiC 署名を抽出した場合、それらが優先され、各々が独立に検証される(署名ごとに 1つの signatures[] エントリとなるため、1つの失敗が他を抑制することは決してない)。 ホストがいかなる ASiC 署名も生成しなかった ZIP マジック入力 — 認識可能な ASiC コンテナではない ZIP — は INDETERMINATE asic_unsupported_container (asic_unsupported_entry)として報告され、CAdES のスニフへ落ちて誤分類される ことは決して許されない。
ZIP リーダ(crates/pverify-asic/src/zip.rs)は純粋 Rust の zip クレートを ラップし、ASiC が必要とするセキュリティの封じ込めを加える。すなわち、4096 エントリの上限、256 MiB の総展開予算、エントリごとの 128 MiB の上限(zip 爆弾の 拒否。黙って切り詰めるのではなく明示的な ResourceBoundExceeded を返す)、および パストラバーサルの拒否(enclosed_name() == None、加えて .. 構成要素と絶対パスに 対する多層防御の検査)である。すべてはメモリ上に読み込まれ、一時ファイルは 用いない。
detect(crates/pverify-asic/src/detect.rs)はプロファイルをベストエフォートで 決定し、いかなるパッケージングの逸脱も黙って許容するのではなく記録する — これは §I の誠実性の原則をコンテナ適合性に適用したものであり、実際の DSS 出力が出すものと 整合させてある。適合する mimetype はプロファイルを権威的に確定する。
mimetype 値 |
プロファイル |
|---|---|
application/vnd.etsi.asic-s+zip |
ASiC‑S |
application/vnd.etsi.asic-e+zip |
ASiC‑E |
| その他の値 | 逸脱 mimetype_non_conformant_value、レイアウト推論へフォールバック |
| 欠落 | 逸脱 mimetype_missing、レイアウト推論へフォールバック |
ETSI EN 319 162 は mimetype が ZIP の最初のエントリであり、かつ STORED (非圧縮)であることを要求する。pverify は両方を検査し、mimetype_not_first_entry と mimetype_compressed を逸脱として表出する(AsicDeviation、 crates/pverify-core/src/report/mod.rs)が、依然としてコンテナを検証する。 逸脱の集合はコンテナから生じるすべての SignatureEntry に付与される (asic_conformance_deviations)ため、監査者は、判定が抑制されることなく、コンテナが どの ETSI パッケージング規則に違反したかを正確に把握できる。
mimetype が欠落しているか非適合の場合、infer_profile は META-INF/ の レイアウトからプロファイルを導出する。いずれかの ASiCManifest*.xml ⇒ ASiC‑E。 単一の META-INF/signature.p7s(数値接尾辞なし)⇒ ASiC‑S、数値接尾辞または2つ以上の 署名 ⇒ ASiC‑E。signatures.xml 対 signaturesNNN.xml についても同様である。 認識には何らかの署名材料が必要である — 認識可能な META-INF/signature*.{p7s,xml} も適合する mimetype も持たない ZIP は ASiC では ない(detect は None を返す)。また適合する mimetype のみでは不十分である (検証すべき何かが存在しなければならない)。署名ファイル名の照合は標準に従い 大文字小文字を区別する(is_cades_signature、is_xades_signature、 is_asic_manifest)。ASiCArchiveManifest(LTA、スコープ外)と OpenDocument の 情報用 manifest.xml は意図的にマニフェスト認識から除外される。
コンテナは両方のファミリ(混在した signature*.p7s と signatures*.xml)を 運びうる。extract は存在する両方のファミリを処理するため、いかなる署名も 黙って捨てられることはない(crates/pverify-asic/src/lib.rs)。Detection.family フィールドは診断用に過ぎず、ディスパッチには用いられない。
各 AsicSignature は、再利用パスを選択する AsicPayload を運ぶ (crates/pverify-core/src/asic.rs::verify_asic_signature)。
CadesDirect(ASiC‑S CAdES)。 detached CAdES の messageDigest は単一の データオブジェクトの生バイト列を直接カバーする(マニフェストなし)。カーネルは verify_cades_detached を data_object.bytes を detached コンテンツとして呼び出す — CAdES パス全体(チェーン構築、コンテンツダイジェスト、失効、タイムスタンプ、 ETSI)が逐語的に再利用される。単一のデータオブジェクトは、mimetype でもなく META-INF/ の下でもない唯一のエントリである(sole_data_object)。
CadesManifest(ASiC‑E CAdES) — 二段のダイジェストチェーン。 これは ASiC スライスにおける唯一の新しい検証ロジックである(R‑1)。
ASiCManifest の XML バイト列を逐語的に署名する。 カーネルは verify_cades_detached を manifest_bytes を detached コンテンツと して呼び出す。<asic:DataObjectReference> は DigestMethod と DigestValue を宣言する。ホストは各参照 URI を実際の ZIP エントリのバイト列へ 解決する(resolve_entry。先頭の ./ と DSS が出すパーセントエスケープを許容する)。 カーネルは digest_alg(actual_bytes) を再計算し、マニフェストが宣言した ダイジェストと比較する(apply_manifest_digest_chain、 crates/pverify-core/src/asic.rs)。この再計算はカーネル内に存在するため、 完全性の判断は再現可能なレポートの一部となる。各再計算は asic_manifest_checks に記録される(§III の監査証跡。URI、宣言された ダイジェスト OID、一致のブール値)。判定ロジックはその後、次を強制する。
| 条件 | 結果 |
|---|---|
ステップ 1 の CAdES が既に TOTAL_FAILED |
そのまま据え置き(最も重大。ステップ 1 の原因が報告される) |
| いずれかのデータオブジェクトのダイジェスト不一致 | TOTAL_FAILED asic_data_object_digest_mismatch — ステップ 1 が通過していても |
いずれかのデータオブジェクトがアーカイブに不在(actual_bytes == None) |
INDETERMINATE asic_data_object_missing |
| すべて存在し一致 | ステップ 1 の判定が成立する |
ダイジェストの不一致(改竄の積極的証拠)は、欠落したオブジェクト(これは内容を 評価できないことを意味するに過ぎない)に優先する。不一致は TOTAL_FAILED、 単なる不在は INDETERMINATE であり — これは改変の証明と検査不能との間の断定回避の 区別である。重要なことに、データオブジェクトのダイジェスト不一致は、マニフェストに 対する CAdES 署名が完璧に検証された場合でも失敗として報告される。なぜなら、 マニフェストは署名を、実際のバイト列がもはや一致しない内容へ束ねており — 完全性の チェーンはステップ 2 で断ち切られているからである。
Xades(ASiC‑S/E XAdES)。 ホスト(extract_xades、 crates/pverify-asic/src/lib.rs)は各 META-INF/signatures*.xml を pverify_xades::extract_detached を通してパースし、各 ZIP 相対の ds:Reference URI を名前付きエントリのバイト列へマップするリゾルバを供給する。結果として得られる XadesComponents は DetachedResolved プロファイル(§7.4)を運び、カーネルは verify_xades を変更せず呼び出す。参照ダイジェストの検査(§7.3 のステップ 3)は その後、解決された各データオブジェクトのバイト列を、格納された DigestValue に 照らして検証する。解決不能な URI は参照バイト列を空のままにするため、その検査は 誠実に失敗し、黙って通過することは決してない。これが、ASiC‑E XAdES — 既定の DSS ASiC‑E 出力 — を、enveloped XAdES とまったく同一の参照ダイジェスト機構を再利用して、 detached ゲートを狭く引き上げるだけで検証する方法である。
すべてのエントリは、どのペイロードパスがそれを生成したかにかかわらず、その container フィールドが設定され(ASiC-S / ASiC-E)、適合性の逸脱が付与される。
XAdES/ASiC のパスは新しいトップレベルの Indication を追加しない。それらは TOTAL_PASSED / INDETERMINATE / TOTAL_FAILED を再利用し、次のクローズドな SubIndication 値を提供する(crates/pverify-core/src/report/etsi.rs。ルートの report-schema.json へ 1:1 で反映されている)。
| サブインディケーション | インディケーション | 意味 |
|---|---|---|
xades_reference_digest_mismatch |
TOTAL_FAILED | ds:Reference のダイジェストが一致しなかった |
xades_signing_certificate_mismatch |
TOTAL_FAILED | SigningCertificateV2 バインディングの不一致 |
xades_unsupported_profile |
INDETERMINATE | detached/enveloping(スタンドアロン)のパッケージング |
xades_unsupported_canonicalization |
INDETERMINATE | CanonicalizationMethod が Exclusive C14N でない |
xades_signer_certificate_unavailable |
INDETERMINATE | 使用可能な ds:X509Certificate がない |
xades_timestamp_imprint_mismatch |
INDETERMINATE | それ以外は通過する署名において、スコープ内のタイムスタンプインプリントが一致しなかった / 検証できなかった |
xades_unsupported(保持) |
INDETERMINATE | ds:Signature が1つも抽出されなかった場合の包括的な拒否 |
asic_data_object_digest_mismatch |
TOTAL_FAILED | ASiC‑E マニフェストの DataObjectReference のダイジェストが実際のエントリと食い違った |
asic_data_object_missing |
INDETERMINATE | マニフェストが宣言したデータオブジェクトがアーカイブに不在 |
asic_unsupported_container |
INDETERMINATE | 認識可能な ASiC コンテナではない ZIP |
レビュアーが実装に課すべき形式固有の不変条件は次のとおりである。
pverify-xades と pverify-asic は WASM クリーンな抽出クレートであり、暗号処理を一切運ばない(パース、正規化、 バイト抽出のみ)。それらは pverify-core の実行時グラフから外れていることが ゲートにより保証される。カーネルは Vec<u8> とクローズドな列挙のみを見る。web/pverify-wasm/src/lib.rs)は バイト単位で同一な pverify_xades::extract / pverify_asic::extract と、同一の verify_with カーネルを実行し、同一の入力に対してバイト単位で同一のレポートを 生成する。UnsignedSignatureProperties なし)とアルゴリズムポリシー無効の実行は、schema_version 文字列を除いて スライス以前の出力とレポートをバイト単位で同一に保つ。埋め込み失効情報の チャネルは、ライブ/CDP チャネルが判定不能なときにのみ発火する。INDETERMINATE へ降格する — 捏造された通過や失敗を返すことは決してない。TOTAL_FAILED であり、より弱い タイムスタンプや失効のインディケーションによって上書きされることは決してない。 タイムスタンプインプリントによる降格とマニフェストダイジェストの強制は、明示的に 失敗していない基底にのみ作用する。レポート形状のバージョンは SCHEMA_VERSION = "1.10.0" である (crates/pverify-core/src/report/schema.rs)。XAdES/ASiC のサブインディケーションと ContainerFormat / AsicDeviation / AsicManifestCheck 型は追加的なクローズドな 列挙/構造体である(値の追加は MINOR バンプ)。ブランチ 036 は SignatureFormat::XadesBLta("XAdES-B-LTA")と XadesUnverifiedTimestamp の imprint_input フィールドを追加した。これらはいずれも追加的かつ後方互換の MINOR 追加であり、schema_version には影響しない。これらのパスが再利用する共有のパス、 失効、タイムスタンプ、VRT のエンジンについては、第9章、第11章、第12章を参照。
JAdES(JSON Advanced Electronic Signatures、ETSI TS 119 182-1)は、pverify が検証する ETSI AdES ファミリの第4の構成要素であり、CAdES(EN 319 122)、PAdES(EN 319 142)、XAdES(EN 319 132)と並ぶものである。他の3ファミリがそれぞれ CMS、PDF、XML の上に構築されるのに対し、JAdES は RFC 7515 の JSON Web Signature(JWS)構造の上に構築され、RFC 7797 のペイロード非エンコード拡張を伴う。本章では、JAdES の処理経路において pverify が実際に検証する内容、すなわち JWS JSON Serialization 上の JAdES baseline-basic(JAdES-B-B) レベルについて、ホスト側の JSON 抽出器と no_std の検証カーネルとの境界、そして適合性を過大に主張するのではなく意図的に INDETERMINATE として拒否する形の正確な集合を文書化する。
実装は2箇所に存在し、第3章で述べたアーキテクチャ全体のホスト/カーネル分離に対応する。すなわち、JSON パースと全バイト抽出を行うホスト抽出クレート pverify-jades(crates/pverify-jades/)と、モデルに依存しない境界型を所有し、暗号検証および共有された RFC 5280 のパス/失効/ETSI パイプラインを実行するカーネルオーケストレータ crates/pverify-core/src/jades.rs である。このサポートを導入したスライスは specs/027-jades-bb-verify/ である。
この処理経路の規律は、pverify が実際に供給された資料について 誠実な 結果を報告し、別の実装に一致させるために判定を調整することは決してない、という点にある。次の2つの事実は厳密に分離される(specs/027-jades-bb-verify/spec.md、Clarification Q3)。
JAdES baseline プロパティを欠く well-formed な JWS は、暗号的には依然として意味を持つが、pverify はそれを JAdES-B-B とラベル付けしない。それは検出されたが baseline ではないものとして報告され、AdES レベルの主張については INDETERMINATE を返す。これにより、素の JWS に対して JAdES 適合性を過大に主張することを回避する。
サポートされる形と拒否される形を以下にまとめる。「拒否」とは常に正確な INDETERMINATE のサブインディケーションを意味し、捏造された pass や failure を意味することは決してない(憲法 §I、「断言できないことは断言しない」)。
| 観点 | サポート(本スライス) | 拒否 → INDETERMINATE |
|---|---|---|
| AdES レベル | JAdES-B-B(sigT 署名時刻 + 署名証明書バインディング) |
B-T(sigTst)、B-LT(xVals/rVals)、B-LTA(arcTst)——ETSI TS 119 182-1 §5.2.3–§5.2.7——ブランチ 037 でサポート済み。rfsTst/tstVd → jades_unsupported_profile |
| シリアライゼーション | JWS JSON Serialization — general(signatures[])および flattened(signature) |
JWS Compact Serialization → jades_unsupported_serialization(予約済み。JSON 抽出器には到達しない) |
| ペイロード参照 | Enveloping(JWS 構造内のペイロード) | JAdES sigD detached ペイロード → jades_unsupported_serialization |
| ペイロードエンコーディング | b64:true(RFC 7515)および b64:false(RFC 7797 非エンコード) |
— |
alg |
RS256/384/512、PS256/384/512、ES256、ES384、EdDSA(Ed25519) | ES512/P-521、Ed448、none、その他すべて → signature_algorithm_unsupported |
| 署名者証明書 | x5c[0](RFC 7515 §4.1.6 標準 base64 DER) |
ホスト側ルックアップを伴わない x5t#S256 のみ → jades_signer_certificate_unavailable |
| 署名証明書バインディング | JOSE x5t#S256(RFC 7515 §4.1.8、SHA-256) |
JAdES x5t#o 署名済みプロパティは MVP のスコープ外 |
クローズドな SubIndication 列挙(crates/pverify-core/src/report/etsi.rs)は、5つの JAdES 固有の値を保持し、ルートのレポートスキーマに1対1で反映される。すなわち jades_payload_digest_mismatch、jades_signing_certificate_mismatch、jades_unsupported_profile、jades_unsupported_serialization、jades_signer_certificate_unavailable である。SignatureFormat 列挙(crates/pverify-core/src/report/mod.rs)は JAdES-B-B(肯定的に検証された baseline 署名)と JAdES-unsupported(拒否された形に対して保持されるラベル)を追加する。これらは追加的で後方互換性のある成長であった。スライス 027 は既存のクローズドな列挙に値を追記しただけであり、レポートの最上位の形には変更がない。レポートが保持する schema_version は本章の内容とは独立しており、その後のスライス(033 TSA 失効、034 PAdES DSS/VRI、035 OCSP nonce + CdpEntry、037 JAdES B-T/B-LT/B-LTA——これにより 1.9.0 → 1.10.0 へバンプし、3つの新たな SignatureFormat バリアント JAdES-B-T、JAdES-B-LT、JAdES-B-LTA が追加された)を通じて 1.10.0 へと進んでいる。本章は 027 について特定のバンプを主張しない。
カーネルオーケストレータ verify_with(crates/pverify-core/src/verify.rs)は、detect_format におけるコンテンツのスニフによってルーティングする。%PDF- → PAdES、XML で始まる入力 → XAdES、JSON で始まる入力 → JAdES、それ以外は CAdES(ASiC は asic_signatures が非空のときに先行して処理される)。JSON スニフは looks_like_json(verify.rs:375)である。これはオプションの UTF-8 BOM をスキップし、次に ASCII 空白をスキップし、最初の非空白バイトが { である場合にのみ受理する。最上位の配列([)は明示的に JWS JSON Serialization 文書では ない とされ、スニフによって拒否される。JSON({)と XML(<)は最初の非空白バイトにおいて相互排他的であるため、XAdES と JAdES のスニフが衝突することは決してない(verify.rs:354-369、research R-8)。
ホスト(pverify-cli/src/main.rs::looks_like_json および web/pverify-wasm/src/lib.rs)はこのスニフをバイト単位で反映し、CLI とブラウザ内 WASM ビルドが JSON で始まるペイロードに対してのみ JAdES 抽出器を呼び出すようにする。これは CLI と WASM のパリティ不変条件の一例である。ネイティブ CLI では次のとおりである。
let jades_components = if looks_like_json(&signature_bytes) {
pverify_jades::extract(&signature_bytes).unwrap_or_default()
} else { Vec::new() };ここでは抽出のハードエラーが空の Vec に飲み込まれるが、これは黙示的な pass ではない。verify_with において、JSON とスニフされた入力に対する空の jades_components は crate::jades::jades_unsupported_entry にフォールバックし、これは JAdES-unsupported / jades_unsupported_profile / INDETERMINATE を発する(verify.rs:303-306)。したがって、消費者は常に確定的な事実を受け取る。
JadesComponentsいかなる JSON/XML/PDF パーサも pverify-core にリンクされてはならないというアーキテクチャ全体の規則(憲法 §V)に従い、すべての JSON パースはホスト側の pverify-jades で行われ、モデルに依存しないバイトのみがカーネルへと渡る。境界型 JadesComponents は crates/pverify-core/src/jades.rs で宣言され(したがって no_std 側がその形を所有する)、pverify-jades(crates/pverify-jades/src/lib.rs)によって再エクスポートされる。文書内の JWS 署名ごとに1つの値が生成される。general シリアライゼーションでは N 個、flattened では1個である。
| フィールド | 型 | 意味 / 出所 |
|---|---|---|
signing_input |
Vec<u8> |
JWS Signing Input。ホストによって組み立てられる(§8.4)。コアはこれを再計算しない(コアに JSON はない)。 |
signature_method |
JadesSigAlg |
検証可能なファミリにマップされた alg protected ヘッダ値(§8.6)。 |
signature_value |
Vec<u8> |
base64url デコードされた JWS 署名。ECDSA についてはホストが既に raw r‖s → DER Ecdsa-Sig-Value へ変換している。 |
signer_cert_der |
Option<Vec<u8>> |
x5c[0] からの署名者証明書の DER。None ⇒ 署名者証明書が利用不能。 |
extra_certs_der |
Vec<Vec<u8>> |
パス構築のために提供される残りの x5c チェーン証明書(XAdES を反映)。 |
signing_cert_binding |
Option<JadesSigningCertBinding> |
JOSE x5t#S256 ダイジェストバインディング(存在する場合)。 |
profile |
JadesProfile |
サポートされる形では EnvelopedBB、それ以外では Unsupported { reason }。 |
ホストは抽出した Vec<JadesComponents> を追加的な VerificationRequest.jades_components フィールドへ通す(verify.rs:110)。JAdES 以外の入力ではこれは空のままとなり、他のあらゆるフォーマットについてバイト単位の同一性の不変条件を保つ。抽出器自身は 暗号処理を一切行わない。JSON パースとバイト抽出のみである(crates/pverify-jades/src/lib.rs)。
signing input は、JWS 署名が計算される対象となるバイト列である。ホストはこれを crates/pverify-jades/src/signing_input.rs::build で構築する。
signing_input = ASCII(BASE64URL(UTF8(protected_header))) || '.' || payload_segment
load-bearing な機微:ホストは文書中の protected メンバーの base64url 文字列を 逐語的に 使用する。デコードされたヘッダを再エンコードすることは しない。JSON が異なる空白で再出力されたとき、再エンコードは生成者が署名した内容からずれる可能性があり、signing input を破損させてしまう。デコードされたヘッダは、そのメンバー(alg、b64、x5c、x5t#S256、profile キー)を読むためにのみ使用される。署名検査へ供給されるバイト列は、伝送された生の形である。
payload_segment は RFC 7797 の b64 protected ヘッダフラグに依存する(signing_input.rs::build、parse.rs:123)。
b64 が不在または true(RFC 7515 のデフォルト):文書の payload メンバーは それ自体が BASE64URL(payload_bytes) であり、その ASCII 文字列がそのまま付加される。b64:false(RFC 7797 非エンコードペイロード):生のペイロードバイトが直接付加され、第2の base64url ステップは行われない。現在の実装では両方の分岐が payload_str.as_bytes() を付加する(b64:false の分岐は、文書の payload メンバーを UTF-8 文字列として運ばれる生のペイロードバイトとして解釈する)。本スライスは両方を意図的にサポートする。なぜなら、ETSI DSS 参照ベクトルを含む実際の JAdES 出力は、日常的に b64:false を設定するからである(Clarification Q1)。抽出器は、生成者が宣言した b64 値を、それが crit にも列挙されていない場合(RFC 7797 §6 に照らせば生成者のプロトコルエラー)であっても、誠実に記録する。それでもなお生成者が署名したとおりに signing input を構築し、カーネルの署名検査がその真実を報告する。この構築は、signing_input.rs のユニットテストにおいて RFC 7515 §A.1 および RFC 7797 §4.2 の正準ベクトルに対して検証される。
署名者証明書はホスト側で crates/pverify-jades/src/parse.rs::resolve_x5c において解決される。x5c ヘッダは protected ヘッダから優先的に取得され、保護されない header へフォールバックする。RFC 7515 §4.1.6 に従い、各 x5c 要素は 標準 base64(パディング付き) DER である。JWS の他のあらゆる箇所で使われる base64url との意図的な対比に注意されたい。パーサは x5c には STANDARD エンジンを、protected/signature/x5t#S256 には URL_SAFE_NO_PAD を使用する。x5c[0] は signer_cert_der となり、残りは extra_certs_der のパス候補となる。これは XAdES に埋め込まれた証明書の流れを反映している。x5c が存在しない場合、結果は (None, []) となり、カーネルは jades_signer_certificate_unavailable(INDETERMINATE)を表出する。MVP はホスト側の x5t#S256 証明書ストアルックアップを 行わない。したがって x5c を伴わない x5t のみの文書は、解決されるのではなく利用不能となる(parse.rs のコメント、R-3)。
署名証明書バインディング は証明書すり替えに対する防御である。ホストは JOSE x5t#S256 サムプリント(RFC 7515 §4.1.8 — 署名者証明書の固定 SHA-256 ダイジェストで、base64url デコードされる)を JadesSigningCertBinding { cert_digest, digest_alg: Sha256 } へ抽出する(parse.rs:147-158)。JAdES x5t#o 署名済みプロパティは MVP のスコープ外である。カーネルは verify_jades のステップ5でこのバインディングを検証する(jades.rs:262-271)。compute_digest(SHA-256, signer_der) を再計算し、主張されたダイジェストと比較する。不一致は TOTAL_FAILED / jades_signing_certificate_mismatch である。これは XAdES の xades_signing_certificate_mismatch の挙動を反映し、CAdES の ESS 署名証明書バインディングの JSON 版に相当する(第5章を参照)。
alg → 検証可能なファミリ)ホストは JWS alg protected ヘッダ文字列を crates/pverify-jades/src/algid.rs::map_alg で JadesSigAlg へマップする。このマッピングは 新しい暗号プリミティブを一切導入しない。サポートされるすべての alg は、pverify-core::crypto が既に検証するファミリに解決される(research R-7 / FR-004)。
JWS alg |
JadesSigAlg |
備考 |
|---|---|---|
| RS256/384/512 | RsaPkcs1v15 { digest } |
RSA PKCS#1 v1.5、RFC 7518 §3.3 |
| PS256/384/512 | RsaPss { digest, salt_len } |
RSASSA-PSS、同じダイジェスト上の MGF1。salt 長 = ダイジェスト長(32/48/64) |
| ES256 / ES384 | Ecdsa { digest } |
曲線はコア内部で署名者 SPKI から解決される。alg はダイジェストのみを固定する |
| EdDSA | Ed25519 |
RFC 8037。Ed448 はスコープ外 |
none、ES512/P-521、Ed448、その他すべて |
Unsupported { alg } |
生の文字列をラウンドトリップし、レポートがそれを逐語的に名指しできるようにする |
none は拒否される。決して pass ではない(RFC 7518 §3.6。algid.rs のユニットテスト refuses_alg_none)。Unsupported な alg はカーネルに到達し、INDETERMINATE / signature_algorithm_unsupported として表出する。失敗した入力が監査可能となるよう、locus が alg を名指しする(alg:ES512)。
ECDSA については、JWS 署名は固定幅の連結 r‖s(RFC 7518 §3.4)であるのに対し、RustCrypto の検証器は DER Ecdsa-Sig-Value SEQUENCE { r INTEGER, s INTEGER } を期待する。ホストはこれを parse.rs::ecdsa_rs_to_der(pverify-xades を反映)で変換し、最小整数エンコーディングと、最上位ビットが立っている場合の先頭ゼロパディングを正しく処理する。奇数長の(不正な)r‖s はそのまま素通しされ、カーネルが panic するのではなく 事実 として拒否するようにする。
ホストはプロファイルを parse.rs::classify_profile で分類し、サポートされる EnvelopedBB と4つの JadesUnsupportedReason の判別子との間で決定する。検査の順序は重要である。
sigD がまず勝つ:JAdES detached ペイロード参照はスコープ外である(FR-008)。これは後に jades_unsupported_serialization にマップされる。sigTst(B-T)、xVals/rVals(B-LT)、または arcTst/atsHashIndex(B-LTA)の存在は、もはや入力を HigherLevel とマークしない。ブランチ 037 はこれらを ETSI 検証パスへルーティングする。詳細は §8.13 を参照のこと。rfsTst/tstVd のみ未サポートのまま:これらは入力を HigherLevel → jades_unsupported_profile とマークする。EnvelopedBB は sigT 署名時刻ヘッダ と 署名証明書バインディングの 両方 を要求する(Clarification Q3)。JOSE x5t#S256 サムプリントがバインディングとして数えられる。いずれも満たさない well-formed な JWS は NotJadesBaseline → jades_unsupported_profile となる。要求されれば暗号的には検証されるが、決して JAdES とラベル付けされることはない。カーネルはこれらの理由を verify_jades のステップ1でサブインディケーションへマップする(jades.rs:164-180)。NotJadesBaseline → JadesUnsupportedProfile、DetachedSigD と CompactSerialization → JadesUnsupportedSerialization。HigherLevel は現在 rfsTst/tstVd についてのみ発火し → JadesUnsupportedProfile。CompactSerialization は予約済みのバリアントである。JWS Compact 文字列は決して JSON オブジェクトを根とせず、したがって JSON 専用の抽出器には到達しない。ホストの JSON スニフはそれをここへルーティングしないであろう。
verify_jadescrate::jades::verify_jades(crates/pverify-core/src/jades.rs:154)は1つの JadesComponents を消費し、1つの SignatureEntry を生成する。構造的には verify_xades と同一である。JAdES 固有のゲートを実行し、その後、解決された署名者証明書を、他のあらゆるフォーマットが使う 同一の RFC 5280 パス/失効/ETSI 集約パイプラインへ引き渡す。順序付けられたステップは次のとおりである。
ステップ4 — 署名検証。 RSA-PSS は明示的なダイジェストと salt 長を伴って crate::crypto::verify_rsa_pss へ直接ディスパッチする。それ以外はすべて jades_sig_alg_oid を介して signatureAlgorithm OID を解決し、verify_with_alg を呼び出す(jades.rs:228-242)。SPKI は署名者証明書 DER から extract_spki_der を介して再抽出される。検証器が UnsupportedSignatureAlgorithm エラーを返すと、結果は INDETERMINATE / signature_algorithm_unsupported となる。その他のエラー(真正の署名不一致)は TOTAL_FAILED / jades_payload_digest_mismatch である。このサブインディケーションは XAdES の xades_reference_digest_mismatch の JSON 版に相当する。JAdES についてはこれが、改ざんされたペイロード(signing input を変える)と改ざんされた署名値の 両方 をカバーする点に注意されたい。いずれも signing input に対する署名検証の失敗として現れるからである(e2e テスト tampered_payload_… と tampered_signature_…、いずれも → jades_payload_digest_mismatch)。
ステップ6 — 共有パイプライン。 署名者証明書と extra_certs_der のパス候補は validate_path(RFC 5280 §6 basic-plus パス構築 — 第9章を参照)へ引き渡され、次に wire_chain_step_signatures が各チェーンステップの証明書署名を RustCrypto プリミティブで検証し、次に apply_revocations が署名者チェーンの失効を評価する。決定的に重要なこととして、apply_revocations は 空の埋め込み CRL スライスと空の埋め込み OCSP スライス を伴って呼び出される(&[], &[]、jades.rs:295-305)。JAdES-B-B は埋め込み検証材料を一切保持しないため、失効はアクティブな Mode の下でのライブ/CDP チャネルによってのみ駆動される。キャッシュ材料のない --offline モードでは、これは誠実に IndeterminateRevocationOffline / revocation_not_checked_offline(INDETERMINATE)へ縮退し、レスポンダ接触を捏造することは決してない(憲法 §VII)。
SignedAttrsCheck は合成された「検証済み」レコードとして報告される(jades_signed_attrs_ok、jades.rs:575)。JWS にはそれ自体としての CMS signedAttrs は存在しない。signing input に対する署名 こそが 完全性のバインディングであり、それは既にステップ4で検査されている。ContentDigestCheck は signing input の SHA-256 を covered_content_sha256 として記録し、016 の validation_objects[] 資料インベントリが署名済み材料を表出できるようにする(jades.rs:312-313)。これは XAdES を反映している。
基底の ETSI インディケーションは、共有された aggregate_etsi(jades.rs:315)によって、チェーンの結果、(検証済みの)signed-attrs レコード、content-digest レコードにわたって畳み込まれる。重大度の順序は TOTAL_FAILED > INDETERMINATE > TOTAL_PASSED で、最初の所見が勝つ。供給材料において失効問題のない、供給アンカーへ至る健全なチェーンは TOTAL_PASSED を返す(e2e rs256_enveloping_total_passed、es256_enveloping_total_passed)。失効した、またはパスが破断したチェーンは、CAdES と全く同様に対応する INDETERMINATE/TOTAL_FAILED を返す。JAdES の処理経路はチェーンレベルの新しい判定ロジックを一切導入しない。
JAdES-B-B はカバーするタイムスタンプを一切保持しない(本スライスでは etsiU は空である)ため、オブジェクトごとの検証基準時刻(VRT)ブロックは誠実な縮退ケースである。verify_jades はそれでも空のタイムスタンプスライスと CoveringSource::Jades を伴って共有された crate::vrt::derive_vrt を呼び出す(jades.rs:332-339)。エンジンは、signer_chain と signer_signature の両方が derivation == request_at かつ value == verification_time を持つ Vrt ブロックを返す(FR-005 / FR-011a)。ブランチ 037 では、sigTst(B-T)トークンと arcTst(B-LTA)トークンが wire 形の変更なしに共有された再帰的な外側カバーエンジンを通って流れる。arcTst のハッシュ入力は非循環である: BASE64URL(protected).BASE64URL(payload).BASE64URL(signature).BASE64URL(sigTst_verbatim_b64u) — 現在の arcTst トークン自体はハッシュ入力から除外される。オブジェクトごとの VRT は第12章で詳細に文書化されている。
後続の2つの横断的レイヤは配線されているが、今日の B-B については縮退している。
--algorithm-policy がアクティブな場合、evaluate_jades_algorithm_validity(jades.rs:394)は2つの ObjectUnderEvaluation を組み立てる。すなわち署名者チェーン(リーフ証明書自身の signatureAlgorithm OID により判定される)と署名者署名(JWS の alg 記述子から取られたファミリ/ダイジェスト。例えば RS256 → RSA + SHA-256 で、OID 1.2.840.113549.1.1.11 へマップし戻される)であり、いずれも(リクエスト時刻の)VRT において共有された CRYPTREC/NIST ポリシー評価器を実行する。結果は algorithm_validity を充填し、TOTAL_FAILED でない基底を縮退させ得るのみで、TOTAL_FAILED を生成することは決してない(e2e jades_algorithm_validity_populates_when_enforced)。デフォルトでは無効であり、これによりレポートはバイト単位で同一のまま保たれる。apply_tsa_revocation_indication は空のトークンスライスを伴って呼び出される(jades.rs:363)。カバーするタイムスタンプが存在しないため、B-B については no-op である。この継ぎ目は、将来の JAdES タイムスタンプレベルが PAdES/CAdES と同じ共有された apply_tsa_chain_revocation_for_token の経路を通じてそのインディケーションを縮退させられるように存在する。肯定的に検証された署名は format: JAdES-B-B と content_source: Embedded(ペイロードが enveloping である)を伴って報告される。拒否されたすべての形は format: JAdES-unsupported と、対応する INDETERMINATE のサブインディケーションを伴って報告される。signer_cert_der/extra_certs_der は標準の 016 導出を通じて統合された validation_objects[] インベントリへ供給される。JAdES は新しい ValidationObjectOrigin バリアントを一切導入しない(ブランチ 034 の DSS/VRI 固有の pdf_dss/pdf_vri は PAdES 専用である)。
ネイティブ CLI とブラウザ内 WASM ホストは、バイト単位で同一の抽出器(pverify_jades::extract)と同一の verify_with カーネルを実行するため、同一の入力はバイト単位で同一のレポートを生成する。これが CLI と WASM のパリティ不変条件であり、web/pverify-wasm/src/lib.rs のパリティテスト(wasm_jades_sniff_and_extract_at_parity)と CLI e2e スイート crates/pverify-cli/tests/jades_bb_e2e.rs によって固定される。e2e テストは5つの正準シナリオをカバーする。すなわち RS256 と ES256 の enveloping ハッピーパス(TOTAL_PASSED)、1バイトのペイロード改ざんと署名改ざん(TOTAL_FAILED / jades_payload_digest_mismatch)、署名証明書バインディングの不一致(TOTAL_FAILED / jades_signing_certificate_mismatch)、署名者証明書の利用不能(INDETERMINATE)、sigD/素の JWS の拒否(INDETERMINATE / jades_unsupported_serialization および jades_unsupported_profile)である。再現性は、カーネルがキャプチャされた verification_time(Clock::now は決して使わない)からのみ時刻を読むことに由来する。したがって、与えられた(入力、アンカー、時刻、失効材料)のタプルは決定論的なレポートを生成する。
| 標準 | 遵守箇所 |
|---|---|
| ETSI TS 119 182-1(JAdES) | プロファイル分類:sigT + 署名証明書バインディング = B-B。sigTst(B-T)、xVals/rVals(B-LT)、arcTst(B-LTA)——ETSI TS 119 182-1 §5.2.3–§5.2.7——ブランチ 037 で検証済み。rfsTst/tstVd は拒否のまま(parse.rs::classify_profile) |
| RFC 7515(JWS) | JSON Serialization パース(general + flattened)、§5.1 signing input、§4.1.6 x5c 標準 base64 DER、§4.1.8 x5t#S256(parse.rs、signing_input.rs) |
| RFC 7797(非エンコードペイロード) | signing_input.rs::build における b64:false 処理。宣言されたフラグの誠実な記録 |
| RFC 7518 / RFC 8037(JWA / EdDSA) | alg → ファミリのマッピング。PSS の salt 長 = ダイジェスト長。none 拒否(algid.rs) |
| RFC 5280(パス検証) | 解決された署名者 + x5c チェーンにわたる共有された validate_path(jades.rs ステップ6。第9章) |
| RFC 6960 / 5280(OCSP / CRL) | 共有された apply_revocations(B-B には埋め込み材料なし)。オフラインの誠実性を保持 |
| ETSI EN 319 102-1(インディケーション) | 共有された aggregate_etsi。クローズドな SubIndication/Indication 列挙 |
JAdES の処理経路は意図的に保守的である。AdES baseline の梯子を1段(B-B)登り、署名者証明書が手に入った時点から確立済みのパス/失効/ETSI 機構の全体を再利用し、完全には検証できないものすべてを、推測ではなく正確で監査可能な INDETERMINATE をもって拒否する。上位レベル(B-T/B-LT/B-LTA)および結果レポートの JWS 封筒テックデモは、本スライスでは明示的にスコープ外である(specs/027-jades-bb-verify/spec.md)。注: ブランチ 037 が B-T/B-LT/B-LTA サポートを実装した——実装の詳細については §8.13 を参照のこと。
ブランチ 037(specs/037-jades-bt-blta/)は、ETSI TS 119 182-1 §5.2.3–§5.2.7 の完全な unsigned-properties の梯子をカバーするよう JAdES 検証パスを拡張した。実装では3つの新たな SignatureFormat バリアント——JAdES-B-T、JAdES-B-LT、JAdES-B-LTA——を追加し、schema_version を 1.9.0 から 1.10.0 へバンプする。crates/pverify-cli/tests/jades_bt_blta_e2e.rs の3つの e2e テストはすべて PASS している。
JWS の unprotected ヘッダ内の sigTst unsigned プロパティは、JWS Signing Input に対する1つ以上の RFC 3161 タイムスタンプトークンを運ぶ。ハッシュ入力は次のとおりである:
sigTst_hash_input = BASE64URL(protected) || "." || BASE64URL(payload)
これはまさに JWS Signing Input(signing_input、§8.4)であり——署名者の秘密鍵が操作した同じバイト列である。ホストは unprotected ヘッダから sigTst トークン DER を抽出し、JadesComponents.sig_tst_tokens を介してカーネルへ渡す。カーネルはトークンのインプリントを signing input の再計算に対して検証し、トークンを apply_tsa_chain_revocation_for_token(033 共有ヘルパー)に通して処理し、検証が成功した場合にはトークンの genTime を署名者オブジェクトのオブジェクトごとの VRT に設定する。sigTst を持つ入力は JAdES-B-T へ昇格される。
xVals および rVals unsigned プロパティは、JWS の unprotected ヘッダに埋め込まれた証明書チェーンおよび失効情報(CRL / OCSP レスポンス)を運ぶ。ホストはこれらを JadesComponents.x_vals および JadesComponents.r_vals としてデコードして渡す。カーネルはそれらを、PAdES-LT および 034 DSS パスが使用するのと同じ embedded_crls / embedded_ocsp_responses チャネルを介して apply_revocations へ供給する——新たな失効ロジックはない。xVals/rVals を持ち(かつ sigTst も持つ)入力は JAdES-B-LT へ昇格される。
arcTst unsigned プロパティは、現在の arcTst トークン自体を除いた(非循環)、ここまでに蓄積された JWS 構造全体に対するアーカイブタイムスタンプトークンを運ぶ。ハッシュ入力は次のとおりである:
arcTst_hash_input = BASE64URL(protected) || "." || BASE64URL(payload) || "." || BASE64URL(signature) || "." || BASE64URL(sigTst_verbatim_b64u)
ここで sigTst_verbatim_b64u は、unprotected ヘッダに現れる sigTst プロパティのリテラルな base64url 文字列である。現在の arcTst トークンは自身のハッシュ入力から意図的に除外される。カーネルは arcTst インプリントを検証し、トークンを apply_tsa_chain_revocation_for_token に通して処理し、オブジェクトごとの VRT アンカーを arcTst の genTime に設定する。完全に証拠を持つ入力は JAdES-B-LTA へ昇格される。
以下はブランチ 037 では明示的にスコープ外であり、jades_unsupported_profile / INDETERMINATE として拒否されたまま残る:
rfsTst および tstVd unsigned プロパティ(検証レポート / タイムスタンプ検証データ)。sigD detached ペイロード参照。draft-miyachi-ltv-jws-00 は、JWS にトップレベルの ltv オブジェクトを追加し、SIG-B から SIG-LTA までの4つの累進的なレベルで検証エビデンスを運ぶ拡張を定義する。pverify は同じ pverify-jades 抽出器と verify_with カーネル経路を使い、4つのレベル全てに対して実験的なサポートを実装している(新クレート・新パイプラインなし)。
ltv ヘッダーマップは crates/pverify-jades/src/parse.rs でパースされる。4つのレベルは SignatureFormat 値 LTV-JWS-SIG-B・LTV-JWS-SIG-T・LTV-JWS-SIG-LTV・LTV-JWS-SIG-LTA にマップされる。ltv キーを持ちながら分類できない形(不正または未認識の構造)は LTV-JWS-unsupported / INDETERMINATE / jades_unsupported_profile として報告される。
レベル判定規則(classify_ltv_level、parse.rs に実装):
| レベル | 判別特徴 |
|---|---|
| SIG-LTA | ltv.archive が存在し decode 可能 |
| SIG-LTV | ltv.timestamp が存在し、デコードした JSON に validations キーがある |
| SIG-T | ltv.timestamp が存在し、validations キーがない |
| SIG-B | ltv.signing が存在し、上位レベルのキーがない |
各 ltv.* 値は Base64URL エンコードされた JSON(インライン JSON でなく)であるため、抽出器は各フィールドを decode して再パースする。ltv.signing オブジェクトは署名者証明書チェーンを運び、既存のパス検証パイプラインへの橋渡しとなる:署名者リーフと中間証明書の DER バイトが x5c 相当のチェーン候補として validate_path へ供給される。ltv.signing または ltv.timestamp に埋め込まれた失効材料は、PAdES-LT が使用するのと同じ embedded_crls/embedded_ocsp_responses チャネルを通じて apply_revocations/apply_tsa_chain_revocation_for_token へ投入される。これは既存インフラの追加的な再利用であり、新たな失効ロジックは導入されていない。
docs/research/ltv-jws-compatibility-notes.md に記録されたinteropの知見には:ltv.timestamp/ltv.signing の BASE64URL ラッピング(素のインライン JSON に対して);ltv.archive の {"timestamp": BASE64URL} dict 形状;SIG-LTV/T の判別子としての validations キー;などがある。これらはドラフト著者へのフィードバック候補である。
スコープと状態. これは draft-miyachi-ltv-jws-00 を追跡する実験的な実装である。ドラフトの進化に伴い形式が変わる可能性がある。4つの LTV-JWS-* SignatureFormat 値は既存のクローズド列挙への追加的追加であり後方互換。憲法改正は不要。crates/pverify-cli/tests/ltv_jws_fixtures.rs の18の統合テストが4つのレベル全てと unsupported-profile フォールバックをカバーする。
リリース: v1.3.0 · レポートスキーマ: 1.9.0
本章では、pverify-core の X.509 証明書パスエンジン、すなわち、リーフ(エンドエンティティまたはCA)証明書、候補となる中間証明書のプール、トラストアンカーの集合、および検証時刻が与えられたときに、証明書パスを構築し、その上で 構造的な RFC 5280 §6 の不変条件を評価するサブシステムを規定する。本エンジンは、あらゆる AdES 形式の判定が依拠する基盤である。すなわち、CAdES、PAdES、XAdES、JAdES、および ASiC が委譲する内部署名のすべてが、同一の validate_path エントリポイントへ収束する(形式ディスパッチの概要については第4章を参照)。
本エンジンは全面的に crates/pverify-core/src/path/ に存在する:
| ファイル | 責務 |
|---|---|
path/mod.rs |
パス構築(DFS + バックトラック)、時刻における有効性、BasicConstraints、KeyUsage、構造的所見の集約、名前制約とポリシー状態を各ステップに通すオーケストレーション |
path/name_constraints.rs |
RFC 5280 §4.2.1.10 / §6.1.4(g) の名前制約ステートマシン |
path/policy.rs |
RFC 5280 §6.1 valid_policy_tree 処理、ポリシーマッピング、policyConstraints/inhibitAnyPolicy のカウントダウン |
path/bridge.rs |
ブリッジCA相互認証証明書のクロッシング検出 |
もう一つの関心事、すなわち、各非アンカーステップの signature をその親の SubjectPublicKeyInfo に対して検証する処理は、意図的に path/mod.rs には 含めていない。これはオーケストレータ(crates/pverify-core/src/verify.rs、wire_chain_step_signatures、verify.rs:1944)に配線されており、これにより RustCrypto ディスパッチが依存グラフへ厳密に一度だけ取り込まれ、CMS署名者署名検証と共有される。この分離については §9.8 で説明する。
本エンジンは 純粋な計算 を行う。すなわち、すでにパース済みの Certificate プロジェクションと TrustAnchor バイト構造(ホストが、第3章で説明した 憲章 §V/§VI の境界に従って、すべての DER 取り込みとトラストアンカー読み込みを済ませている)を受け取り、ChainOutcome を返す。ソケットを開かず、ファイルを読まず、Clock::now を一切参照しない。唯一の時刻入力は、呼び出し側が供給する verification_time 引数であり、これは第12章(タイムスタンプ / VRT)で説明する手順で導出された、オブジェクトごとの検証基準時刻(VRT)である。これこそが、同一のエンジンをネイティブと wasm32-unknown-unknown ターゲットの双方でバイト単位で同一に実行できる理由である。
本章は、仕様 specs/003-v03-rfc5280-path/(v0.3 — 名前制約、ポリシー処理、アンカー駆動の初期化)および specs/008-v06-bridge-acceptance/(v0.6 — DFS+バックトラックのパス構築、複数ブリッジ走査、サイクル検出)に基づいている。
RFC 5280 §6 のパス検証には、明確に異なる二つの側面がある:
basicConstraints、keyUsage、名前制約、証明書ポリシー。この側面は path/mod.rs とその兄弟モジュールで実装されており、本章の主たる主題である。signatureValue が、発行者の subjectPublicKeyInfo に対応する秘密鍵によって生成されたことを検証する。この側面は verify.rs::wire_chain_step_signatures(§9.8)で実装されている。本エンジンは、何を強制し何を強制しないかについて誠実である。とりわけ、extendedKeyUsage 拡張は、すべての ChainStep に パースおよび表出 されるが(ChainStep.extended_key_usages、path/mod.rs:334)、パスエンジンによって 強制はされない。すなわち、必要な EKU を欠くエンドエンティティ証明書の拒否は行われず、また監査者にとって特筆すべきこととして、パスエンジン内で TSA 証明書に対する id-kp-timeStamping(OID 1.3.6.1.5.5.7.3.8)要件は課されない(path/ と timestamp.rs を grep しても EKU 比較は見つからない)。EKU は、憲章 §I の事実報告の規律に整合して、観測した事実として報告される。EKU の強制を必要とする検証者(信頼当事者)は、表出された extended_key_usages フィールドを読む。本スコープ記述は、埋もれさせるのではなく、関連する箇所で以下に再掲する。
唯一のエントリポイントは次のとおりである:
pub fn validate_path(
leaf: &Certificate,
intermediates: &[Certificate],
anchors: &[TrustAnchor],
verification_time: OffsetDateTime,
required_policies: &[String],
) -> ChainOutcome(path/mod.rs:103)。その入力と各々に対する契約は次のとおりである:
| パラメータ | 意味 |
|---|---|
leaf |
パス深さ N の証明書(署名者証明書、またはタイムスタンプの署名者チェーンを検証する場合は TSA 証明書) |
intermediates |
候補となる CA 証明書のフラットで順序のないプール(CMS certificates 集合、/DSS /Certs、埋め込みの certificate-values などに由来) |
anchors |
トラストアンカーの集合。各々は DER バイト列、SHA-256 フィンガープリント、生のサブジェクトDN、およびホストが算出した validity_window_covers_request_time フラグを保持する(crate::traits::TrustAnchor) |
verification_time |
オブジェクトごとのVRT(031-vrt-per-object、FR-002a)。署名者チェーンは vrt.signer_chain.value で検証され、各 TSA チェーンは次の外側のカバーするタイムスタンプの GenTime で検証される。関数自体は、与えられた任意の時刻における有効性/鮮度を単純に判定するだけである。 |
required_policies |
RFC 5280 §6.1.1(c) に従う user-initial-policy-set。空スライスは {anyPolicy} を意味する(v0.1/v0.2 の挙動互換)。CLI の --required-policy <OID> フラグ(FR-053)によって投入される |
戻り値:
pub struct ChainOutcome {
pub chain_result: ChainResult, // the per-step report tree
pub structural_indication: Option<StructuralFinding>, // the dominant path-level finding
}chain_result は、レポートシリアライザが消費する ChainResult であり、終端アンカーを含む各証明書につき一つの ChainStep、加えて terminating_anchor_fingerprint と bridge_attempts ベクタからなる。structural_indication は、パス検証が構造的な異議を生じなかった場合は None であり(TOTAL_PASSED に至る前には、依然として下流の CMS / 失効 / ダイジェスト層が実行されなければならない)、構造的な所見が生じた場合は、aggregate_etsi(§9.9)が署名ごとの ETSI 判定へ畳み込む Indication + SubIndication + locus(step[N] または anchor/chain)を保持する Some(StructuralFinding) である。
StructuralFinding は常に locus を保持しており(path/mod.rs:81)、これによってすべての判定が問題のある特定の証明書を指し示す。これは、レポートが裸の真偽値ではなく failing_locus を伴う事実インベントリであるべきという 憲章 §I の要件に沿うものである。
パス構築は、construct_chain(path/mod.rs:477)によって実装される、バックトラックを伴う深さ優先探索である。これは、ブランチ 008(FR-601/FR-602)において v0.5 の単一パス「最初に一致する中間証明書」を辿る方式を置き換えたもので、複数ブリッジのチェーンや行き止まりの中間証明書を正しく扱えるようにしつつ、単一パス方式が既に生成していた単一アンカーの直線チェーン(V1–V23)についてはバイト単位で同一の出力を維持する。各深さにおいて、ウォーカは依然として 最初に 一致する候補を優先するため、直線チェーンではバックトラックを行う必要が決して生じない。
construct_chain の鍵となる不変条件:
path/mod.rs:536-572):
OfficialStatusCA 2019 対 2024 鍵更新であり、同一のサブジェクト DN を持ちながら SKID が異なる。TrustAnchor.subject_key_identifier に SKID バイトを、Certificate.authority_key_identifier に子証明書の AKID keyid を保持し、子証明書が実際に署名を受けたアンカーの公開鍵を選択する。signature_verification: Failed を引き起こす。署名者の証明書は世代 A に発行されたにもかかわらず、ストアに A と B の両方が含まれており DN のみの照合が B を先に選んでしまうためである。これが、有効期限切れだが一致するアンカーでもチェーンを終端できる理由である(その後フラグが立てられる、§9.4)。.linkage を介して match_dn(rfc4518-minimal 比較器、§9.10)を用いる。ウォーカは連結の判断のために生の DER バイト列を比較することは決してない。tried_indices[i] は、チェーン深さ i+1 で試行すべき次のプールインデックスを記録する。ウォーカが中間証明書 i を選択すると、スロットを i+1 へ進める(path/mod.rs:614)ため、バックトラック時には同じ中間証明書を再選択するのではなく、次の 兄弟 が試行される。これは、旧来の単一パス不変条件を明示的インデックス指定で復元したものである。MAX_CHAIN_DEPTH = 10(path/mod.rs:62)。これは v0.6 において最悪ケースの複数ブリッジチェーン(leaf → ApCA-foreign → Foreign-Bridge → Domestic-Bridge → DomesticRoot、5ステップ)に対して再評価され、10 のまま維持された。上限に到達してもパニックしない。ウォーカはバックトラックへ落ち、最終的にチェーン不完全の INDETERMINATE を表出する(§9.6)。相互認証グラフ(とりわけ GPKI BridgeCA ↔︎ ピアブリッジ)はサイクルを含みうる。素朴な探索は無限ループに陥りうる。construct_chain は、完全な DER エンコード済み証明書の SHA-256 フィンガープリント(cert.fingerprint、path/mod.rs:496-497)でキー付けされた、パスローカルな BTreeSet<[u8; 32]>(cycle_set)を保持する。現在のパス上に既に存在する候補の中間証明書はスキップされる(path/mod.rs:580)。この集合は push 時に投入され、バックトラック時にクリアされる(backtrack、path/mod.rs:667)。
同一性キーとして、SubjectPublicKeyInfo のフィンガープリントや (Subject DN, SPKI) タプルではなく 完全な証明書 DER のフィンガープリント を選択したことは意図的であり、specs/008-v06-bridge-acceptance/spec.md(Clarification Q7、FR-602)に文書化されている。GPKI / FBCA の相互認証証明書のローテーション期間中、トラストストアは正当に、同一の (Subject DN, SPKI) を持ちながら署名と有効期間ウィンドウが異なる二つのブリッジCA証明書を保持する。SPKI ないしタプルでキー付けしたサイクル検出は、これらを一つの論理ノードとして扱い、二つ目の有効なクロッシングを誤って拒否してしまう。完全な DER フィンガープリントはこれらを正しく区別する。RFC 5280 §6 はサイクル検出アルゴリズムを規定していない(事前構築済みのパスを前提としている)ため、これは RFC が義務付ける同一性ではなく、pverify 独自の停止保証である。
construct_chain は (chain, bridge_pairs, terminating_anchor) を返す:
chain — リーフ先頭の証明書パス(chain[0] = リーフ、chain[len-1] = アンカーに最も近い中間証明書)。bridge_pairs — 観測されたすべての相互認証証明書のクロッシングを [child_fp, attached_fp] ペアとして表したもの(§9.7)。直線チェーンでは空。terminating_anchor — アンカーへのパスが見つかった場合は Some(anchor)、そうでなければ None。構築後、validate_path は chain をリーフ先頭で辿り(path/mod.rs:144)、ステップごとに構造的事実と所見を記録する。
各証明書について、本エンジンは cert.validity.covers(verification_time)(path/mod.rs:159)を評価する。ウィンドウが当該時刻をカバー しない 場合、サブインディケーションを誠実にするため、本エンジンは「まだ有効でない」と「期限切れ」を区別する(path/mod.rs:163):
| 条件 | インディケーション | SubIndication |
|---|---|---|
verification_time < not_before |
TOTAL_FAILED |
SignerCertificateNotYetValidAtTime |
verification_time > not_after |
TOTAL_FAILED |
SignerCertificateExpiredAtTime |
トラストアンカーは anchor_chain_step(path/mod.rs:700)によって別途扱われる。これはホストが算出した validity_window_covers_request_time フラグを用い、期限切れのアンカーは TOTAL_FAILED ではなく INDETERMINATE / AnchorExpiredAtTime(path/mod.rs:715)を生じる。この非対称性は意図的である。ウィンドウ外のリーフ/中間証明書はパスの厳格な有効性失敗であるのに対し、ウィンドウ外の アンカー は検証者(信頼当事者)が異なる方法で解決しうる構成上の問題であるため、pverify は偽造を主張するのではなく、INDETERMINATE で事実を報告する(憲章 §I「断言できないことは断言しない」)。
アンカー終端ステップは、アンカー DER を一度再パースし(path/mod.rs:709)、リーフ/中間ステップと同じフィールド形状 — サブジェクトのテキスト、有効期間ウィンドウ、鍵用途、基本制約、署名アルゴリズム — を保持できるようにする。DER のデコードに失敗した場合(ホストが読み込み時に検証済みであるため、本来は起こり得ない)でも、レポートをシリアライズできるよう、プレースホルダフィールドを伴ってステップが発行される(<unparseable anchor>、path/mod.rs:742)。これは 憲章 §I のノーパニック姿勢である。
すべての 非リーフ ステップ(idx > 0)において、basicConstraints.cA は true でなければならない(path/mod.rs:182)。拡張が存在しないか cA == false の場合、本エンジンは INDETERMINATE / KeyUsageMissingRequiredBit を発行する。これは、コードのコメントにあるとおり、cA ビットの欠如が「keyCertSign 欠如の構造的な双子」であり、v0.3 が独自の missing-cA サブインディケーションを新設しないことを選択したため、より広い KeyUsageMissingRequiredBit サブインディケーションを再利用するものである(path/mod.rs:187-194)。
数値の pathLenConstraint はレポートへ 表出される(ReportBasicConstraints.path_len_constraint)が、数値的には強制されない(path/mod.rs:179-181 のコメント:「v0.1 は path_len_constraint を数値的に無視する」)。監査者はこの点に留意すべきである。すなわち、ある中間証明書が宣言した pathLenConstraint を超過するパスは、当該制約を可視化したうえで報告されるが、それのみを根拠に拒否されることはない。§9.3.1 の厳格な深さ上限(MAX_CHAIN_DEPTH = 10)が、実際に強制される唯一の深さ制限である。
| ステップ | 要件(拡張が存在する場合) | 失敗 |
|---|---|---|
リーフ(idx == 0) |
digitalSignature または nonRepudiation をアサートしなければならない。かつ 拡張が存在しなければならない(空 = 不在 ⇒ 失敗) |
TOTAL_FAILED / KeyUsageMissingRequiredBit |
| 非リーフ | keyUsage 拡張が存在する場合、keyCertSign をアサートしなければならない(拡張の不在は許容される) |
TOTAL_FAILED / KeyUsageMissingRequiredBit |
(path/mod.rs:202-225。)リーフ規則は RFC 5280 が厳密に要求するよりも厳しい。RFC 5280 は、拡張が不在の場合に要求するかスキップするかを実装に委ねているが、pverify v0.1+ は、署名意図を明示するため、リーフが署名ビットを伴う keyUsage を保持することを 要求する(path/mod.rs:208-209)。digitalSignature と並んで nonRepudiation(別名 contentCommitment)を受容することは、否認防止証明書が一般的な GPKI/JNSA プロファイルに整合する。
名前制約処理はアンカー先頭であるのに対し、チェーン探索はリーフ先頭であるため、本エンジンは、リーフ先頭のステップループがそれを参照する前に、各チェーン深さにおける累積状態を構築する 事前パス(compute_name_constraints_states、path/mod.rs:822)を実行する。事前パスは次を行う:
nameConstraints(initialise_from_anchor、name_constraints.rs:83)から深さ 0 の状態を初期化する — これは FR-046a/Q3 が義務付ける RFC 5937 のアンカー駆動の規律である。これは、国内ルートが PKI 全体の制約を宣言する GPKI ブリッジCAにとって運用上重要なケースである。apply_intermediate(name_constraints.rs:97)を呼び出し、各吸収の後に状態を記録し、その後ベクタを反転して、リーフ先頭のステップループがそれを直接インデックスできるようにする(path/mod.rs:859-873)。状態(NameConstraintsState、name_constraints.rs:54)は、RFC 5280 §6.1.4(g) の「各名前形式について維持される制約」を反映して、形式ごとに独立したリストを保持する:
permitted: BTreeMap<GeneralNameForm, Vec<SubtreeConstraint>>excluded: BTreeMap<GeneralNameForm, Vec<SubtreeConstraint>>unsupported_forms_pending: Vec<String>RFC 5280 に従い、permittedSubtrees(許可サブツリー)は 積(intersection) によって、excludedSubtrees(除外サブツリー)は 和(union) によって合成される。実装は、形式ごとに各深さの許可エントリの和を保持し、チェック時 に積のロジックを実行する。これはサポートされる形式集合について深さごとの積と等価であり、その旨が文書化されている(name_constraints.rs:109-117)。
サポートされる GeneralName 形式 は directoryName、dNSName、rfc822Name および uniformResourceIdentifier である(name_constraints.rs:34、FR-043)。制約中に現れるそれ以外の形式(x400Address、ediPartyName、registeredID、otherName)は unsupported_forms_pending に記録され、§9.5.4 の保守的な拒否を発火させる(FR-614)。
check_subject)check_subject(name_constraints.rs:165)は、サブジェクトおよび SAN エントリを累積状態に対して評価し、厳格な形式ごとの規律を適用する(Q1)。形式をまたいだ照合は禁じられている:
directoryName は証明書の subject(および任意の directoryName SAN エントリ)に照合される。照合規則は RFC 5280 の「制約された DN が名前の(DIT の観点での)接頭辞である」である。RFC 4514 は RDN を最も具体的なものを先頭としてレンダリングするため、実装は rfc4518-minimal 正規化(小文字化、RDN 区切り周辺の空白除去、canonical_dn、name_constraints.rs:446)のもとで、カンマ境界チェックを伴う大文字小文字非依存の 接尾辞 比較を行う(dn_under、name_constraints.rs:468)。この制限はコード中で開示されている(name_constraints.rs:289-295)。すなわち、ASN.1 レベルの RDN 順序や属性ごとの照合に依存する制約は、v0.3 の規則の範囲外である。dNSName — 大文字小文字非依存。先頭の . は「の厳密なサブドメイン」を意味する(.example.jp は host.example.jp に照合するが example.jp には照合しない)。ドットが無い場合、制約は厳密なホストまたは任意のサブドメインに照合する(matches_dns、name_constraints.rs:388)。rfc822Name — @ を含む制約は特定のメールボックスである。先頭ドットの制約は厳密なサブドメインに照合する。裸ドメインの制約は、ドメインがちょうどそのドメインであるアドレスに照合する(matches_rfc822、name_constraints.rs:400)。RFC 5280 §6.1.4(g) の注記に従い、rfc822Name 制約は、rfc822Name SAN が存在 しない場合に限り、subject 内の emailAddress AVA にも 追加で 照合する(name_constraints.rs:224-249)。uniformResourceIdentifier — 制約は URI オーソリティの ホスト成分のみ に適用され(uri_host がスキーム、ユーザ情報、ポートを剥がして抽出する)、その後 dNSName として照合される(matches_uri_host、name_constraints.rs:418)。RFC 5280 §6.1.4(g) の注記に従い、名前制約は、自己発行の非リーフ 証明書のサブジェクトには 適用されない。その場合 check_subject は NameConstraintsOutcome::SkippedSelfIssued を返す(name_constraints.rs:171-176)。リーフは自己発行であっても常にチェックされる。ステップごとのレポートは name_constraints_check.skipped_self_issued = true を設定し、適用除外が監査可能となる。自己発行の判定は match_dn(issuer, subject).linkage である(path/mod.rs:230)。
check_subject の結果は構造的所見スタックへ畳み込まれる(path/mod.rs:258-284)。v0.6 の再ルーティング(T626、FR-632、Clarification Q3)は、v0.3 の挙動からクローズドな列挙の表面を変更したため、監査者にとって重要である:
NameConstraintsOutcome |
インディケーション | SubIndication |
|---|---|---|
Failed { side, form, value } |
INDETERMINATE |
IndeterminateNamedConstraint |
UnsupportedForm { tag } |
INDETERMINATE |
IndeterminateNamedConstraint |
Malformed { reason } |
INDETERMINATE |
NameConstraintsMalformed |
Verified / SkippedSelfIssued |
(所見なし) | — |
その論拠(path/mod.rs:241-257)は、まさに 憲章 §I に立脚する。すなわち、制約 失敗 は、「検証者(信頼当事者)は、チェーンのステートマシンが結論づけたものとは異なる名前制約ポリシーを持ちうる」ため、v0.3 の TOTAL_FAILED/ChainConstraintsFailure タグから INDETERMINATE/IndeterminateNamedConstraint へ再ルーティングされる。pverify は、チェーンが偽造であると主張することなく、「制約ステートマシンによって拒否された」ことを表出する。サポートされない形式 は、保守的に同じ INDETERMINATE タグへ拒否される(FR-614)。構造的に 不正形式(malformed) の拡張のみが、それ自身の NameConstraintsMalformed タグを保持する。なぜなら、不正形式 DER は「制約がチェーンを能動的に拒否した」とは区別される構造的健全性のシグナルだからである。
ポリシー処理は、同じアンカー先頭の事前パスの規律を用いる(compute_policy_states、path/mod.rs:884)。状態(PolicyState、policy.rs:56)は、valid_policy_tree(nodes: Vec<PolicyTreeNode>)に加えて、三つの §6.1.4 カウントダウン explicit_policy_pending、policy_mapping_pending、inhibit_any_policy_pending、および malformed スロットをモデル化する。
certificatePolicies(§4.2.1.4、OID 2.5.29.32)policyMappings(§4.2.1.5、OID 2.5.29.33)policyConstraints(§4.2.1.11、OID 2.5.29.36) — requireExplicitPolicy / inhibitPolicyMappinginhibitAnyPolicy(§4.2.1.14、OID 2.5.29.54)initialise(policy.rs:111)は、アンカーの certificatePolicies が存在する場合はそれから(RFC 5937 アンカー駆動、FR-046a)、そうでなければ {anyPolicy} から、ツリーをシードし、その後 user_initial_policy_set(空 = {anyPolicy}、RFC 5280 §6.1.1(c))を通してフィルタする。アンカーの policyConstraints / inhibitAnyPolicy がカウントダウンをシードする。
apply_intermediate(policy.rs:153)は、§6.1.3(d)(証明書が宣言したポリシーとツリーの積、intersect_with_cert_policies、policy.rs:206)および §6.1.4(a)/(b)/(c)(カウントダウン。次の証明書が減算後の値を見るよう、各証明書の後にゼロで飽和させて減算される、policy.rs:186-191)を実装する。ポリシーマッピングは issuerDomainPolicy ノードを subjectDomainPolicy で置き換え(apply_mappings、policy.rs:266)、policy_mapping_pending == 0 を尊重する(マッピング予算が枯渇した場合、マッピングは §6.1.4(b) に従って黙って破棄される)。
v0.3 の簡略化は policy.rs:33-39 に開示されている。すなわち、expected_policy_set はすべてのノードについて [valid_policy] として投入される(v0.3 はそれらを分岐させる複数マッピングのケースを行使しない)。ポリシーマッピングのサイクル検出は、チェーン探索がチェーン終端後に停止する(自己制限的である)ため、別途実装されていない。
compute_outcome(policy.rs:304)は §6.1.5 の締め処理をリーフでのみ行う。回帰テストを伴う、根幹に関わる機微は、空ツリーのケースである:
RFC 5280 §6.1.5 に従い、パス処理は
explicit_policy > 0またはvalid_policy_treeが非 NULL のときに成功する。したがって、空ツリーが拒否となるのはrequireExplicitPolicyが発火した場合(explicit_policy_pending == Some(0)としてモデル化される)に限られる。
(policy.rs:316-326。)これが重要なのは、実際の GPKI / 官報(Kanpō)チェーンが、policyMappings を持たず、リーフのポリシー OID が発行者のものと異なるという形態を一般的に取るからである。すなわち、ツリーはリーフで正当に空になるが、いずれの証明書も requireExplicitPolicy をアサートしないため、チェーンは 受容される。空ツリーを無条件に拒否していた以前のバグは、それらの実際のチェーンを誤って INDETERMINATE にしていた。ガードテスト compute_outcome_empty_tree_at_leaf_without_explicit_policy_is_verified とその with_explicit_policy 対応版(policy.rs:413-443)が、修正後の挙動を固定している。
その他の §6.1.5 拒否: ツリーに anyPolicy のみがある状態での inhibitAnyPolicy == 0(InhibitAnyPolicyViolatedAtLeaf)、特定のポリシーが無い状態での requireExplicitPolicy 発火(ExplicitPolicyViolated)、およびツリーに一致する特定のポリシーが無い非空の user-initial-policy-set(EmptyTree)(policy.rs:342-366)。
PolicyOutcome |
インディケーション | SubIndication |
|---|---|---|
Inconclusive { reason } |
INDETERMINATE |
IndeterminatePolicyRejected(v0.6、FR-632) |
Malformed { extension: PolicyMappings } |
INDETERMINATE |
PolicyMappingsMalformed |
Malformed { extension: other } |
INDETERMINATE |
PolicyProcessingInconclusive |
Verified |
(所見なし) | — |
(path/mod.rs:300-323。)名前制約と同様に、v0.6 の再ルーティングは、能動的に拒否されたポリシーパスを新しい IndeterminatePolicyRejected タグへ反転させ、構造的健全性(不正形式 DER)のシグナルには PolicyMappingsMalformed を保持する。すべてのポリシー判定は INDETERMINATE であり、TOTAL_FAILED には決してならない。ポリシー不一致は「チェーンが この ポリシー集合を満たさない」ことであって、偽造の証明ではない。
pverify は、相互認証証明書を通じたドメイン間の信頼をサポートする。これは、ブリッジCAが複数のドメインルートと相互認証される GPKI BridgeCA モデルである。二つの機構が協調する。
bridge::detect_crossing(path/bridge.rs:56)は、候補の中間証明書の issuer/subject DN をアンカー集合に対して比較することで、二つの典型的な相互認証証明書の形態を認識する:
!issuer_in_anchors && subject_in_anchors)。issuer_in_anchors && !subject_in_anchors)。いずれも、BridgeAttempt.crossing_pair フィールド向けにクロッシングペア [intermediate_fp, anchor_side_fp] を返す。
v0.6 では、ウォーカはブリッジを拒否するのではなく能動的に走査する。リーフより上のすべてのホップはクロッシングペアとして記録される(path/mod.rs:618-643):
[current_fp, intermediate_fp] として記録される — これが、中間証明書から中間証明書へのブリッジホップ(V24 複数ブリッジ形態)が捕捉される仕組みである。bridge::detect_crossing ヒューリスティックが参照される。V9 単一ブリッジ形態はこの分岐に当たる。バックトラック時には、一致する bridge_pairs エントリ(その二つ目のフィンガープリントが pop された証明書に等しいもの)も pop される(backtrack、path/mod.rs:682-690)ため、記録されたクロッシングは常に最終的なチェーンを反映する。
チェーンが構築され、ステップごとの所見が収集された後、各クロッシングは BridgeAttempt へ変換される(path/mod.rs:397-445):
| 条件 | BridgeAttemptResult |
|---|---|
チェーンに対し記録された名前制約の Failed が一つでもある |
RejectedConstraintFailure(side/form/value の詳細文字列付き) |
記録されたポリシーの Inconclusive が一つでもある |
RejectedPolicyInconclusive(理由の詳細付き) |
| いずれも無い | Accepted |
複数ブリッジチェーンについては、v0.6 実装は(あれば)最初の失敗をすべてのクロッシングに適用する。ブリッジごと・所見ごとの相関は将来の改善として記されている(path/mod.rs:386-393)。各 BridgeAttempt はさらに name_constraints_evaluated を保持する。これは、ブリッジ側の証明書が nameConstraints 拡張を保持し かつ ブリッジ側の制約失敗が記録されなかった場合に true となり(path/mod.rs:424-437、FR-633/US3-AS1)、当該クロッシングの制約がチェックされたという積極的な証拠を監査者に与える。
bridge_ca_required_unsupportedv0.1/v0.2 では、相互認証証明書を必要とするチェーンは INDETERMINATE / bridge_ca_required_unsupported を生成していた。FR-048 はその発行を 削除した。当該サブインディケーションはクローズドな列挙に残っている(追加的スキーマの規律に従い、保存済みレポートのデシリアライズのために保持される)が、もはや発行されない(verify.rs:2670-2673)。v0.3+ は代わりに chain_result.bridge_attempts[*].result/detail を通じてブリッジCAの結果を報告し、v0.5(FR-510)は旧来の単一フィールドを bridge_attempts ベクタへ改称した。
チェーンが真にいずれのアンカーにも到達せず かつ クロッシングが記録されなかった(bridge_pairs が空)場合、本エンジンは locus = "chain" でサブインディケーションを持たない汎用の構造的 INDETERMINATE を表出する(path/mod.rs:372-378) — これは誠実な「このパスは不完全である」というシグナルである。
path/ の外側に配線)validate_path は、各非アンカー ChainStep.signature_verification をプレースホルダ ChainStepSignature::Failed { reason: "cert signature verification deferred to T034" } で投入する(path/mod.rs:340)。このプレースホルダは、構造的所見スタックによって chain_signature_failed 所見へ 決してマッピングされない(path/mod.rs:22-24)ため、レポートを汚染し得ない。validate_path は構造的サブインディケーションのみを表出する。
その後オーケストレータは wire_chain_step_signatures(verify.rs:1944。CAdES 署名者チェーンについては verify.rs:568、オブジェクトごとの VRT 再検証については verify.rs:783、TSA チェーンについては verify.rs:1585 で呼び出される)を呼び出して、プレースホルダを実際の結果で置き換える。各非ルートステップについて、それは次を行う:
parent_spki_for_step、verify.rs:2037)。(tbsCertificate DER, signatureValue) に分割する(split_certificate_for_verification、verify.rs:2063)。crate::crypto::verify_with_alg(sig_alg_oid, parent_spki, tbs, sig) へディスパッチする。これは OID + SPKI ファミリによって RSA PKCS#1 v1.5、RSA-PSS、ECDSA P-256/P-384、Ed25519 および ML-DSA をルーティングする(RustCrypto のみ — WASM クリーンな暗号制約については第3章を参照)。結果のマッピング(verify.rs:1994-2011):
verify_with_alg の結果 |
ChainStepSignature |
下流のインディケーション |
|---|---|---|
Ok(()) |
Verified |
— |
Err(UnsupportedSignatureAlgorithm(oid)) |
Failed { "unsupported certificate signatureAlgorithm: …" } |
INDETERMINATE / SignatureAlgorithmUnsupported |
Err(MalformedPublicKey(msg)) |
Failed { "malformed public key: …" } |
INDETERMINATE / PublicKeyMalformed |
Err(other) |
Failed { "<error>" } |
(§9.9 の留意点を参照) |
パス構造の検証と暗号ステップの検証を分離したのは、RSA / ECDSA / Ed25519 / ML-DSA の依存を path/ の内側で重複させるのではなく、コアグラフへ厳密に一度だけ取り込み、CMS 署名者署名パスと共有するためである。
署名ごとの ETSI インディケーションは aggregate_etsi(verify.rs:2343。全体のレイヤリングについては第5章 §5.7 を参照)によって算出される。パス検証の所見は、その優先順位の梯子の 最上位 に入る:
TOTAL_FAILED — chain.structural_indication が TOTAL_FAILED 所見(期限切れ/まだ有効でない証明書、リーフの KeyUsage 失敗、非リーフの keyCertSign 失敗)である場合、他のすべての原因に先んじて直ちに返る(verify.rs:2375-2378)。pick_dominant_finding(path/mod.rs:819)が、この時点より前に最も重大な所見を選択する(TotalFailed > Indeterminate > TotalPassed、同点の場合は最初に push されたものが勝つ)。RevokedOnCrl/RevokedOnOcsp) — これらは構造的 TF の後にレイヤリングされる CMS/失効の原因である(CMS、失効、タイムスタンプの各章を参照)。verify.rs:2548-2576 で走査される。cert_signature_alg OID がサポート表に無いステップは INDETERMINATE / SignatureAlgorithmUnsupported を生じる。OID は認識されるが signature_verification の理由が "malformed public key:" で始まるステップは INDETERMINATE / PublicKeyMalformed を生じる。INDETERMINATE — IndeterminateNamedConstraint、NameConstraintsMalformed および IndeterminatePolicyRejected について、それ自身の優先順位スロットで発行される(verify.rs:2604-2668)(これらは通常すでに structural_indication 経由で到達するが、ステップごとの走査は防御的なものである)。監査者に関連する留意点。 汎用の 暗号的な検証失敗を生じるチェーンステップ — verify_with_alg が UnsupportedSignatureAlgorithm でも MalformedPublicKey でもないエラー、すなわち中間証明書の証明書に対する真の不正署名 — を返す場合、それは aggregate_etsi において、現時点では専用の TOTAL_FAILED / ChainSignatureFailed インディケーションへは ルーティングされない。SubIndication::ChainSignatureFailed 値はクローズドな列挙に存在する(report/etsi.rs:41)が、コードベース内でのその唯一の構築はユニットテスト内にある(verify.rs:2971)。集約は、サポートされない OID および不正形式の鍵の形態については明示的に走査するが、正真正銘の偽造された中間証明書署名は、レポートツリー内の当該ステップの signature_verification = Failed { reason } という事実にのみ反映され、トップレベルの TOTAL_FAILED へは昇格されない。実務上、署名者自身の署名(CMS 層、梯子の §2 で検証される)と失効が支配的な攻撃面をカバーし、偽造された中間証明書は通常 DN/issuer 連鎖にも失敗するだろう。しかし、記録されたステップの事実と集約された判定との間のこのギャップは、糊塗するのではなく、レビューのためにここに文書化する。
rfc4518-minimalすべての issuer↔︎subject 連結の判断 — パス構築、自己発行検出、ブリッジ検出、およびステップごとの dn_match_with_parent 証拠 — は、match_dn(crates/pverify-core/src/x509/name.rs)、すなわち rfc4518-minimal 比較器を通る。それは、意図的に縮小された stringprep プロファイルを適用する(name.rs:1-24):
A–Z → a–z)。[\t\n\f\r ] の連なりを一つのスペースへ畳み込み、先頭/末尾をトリムする。UTF8String、PrintableString、BMPString、TeletexString および IA5String を Unicode 文字列へデコードしてから 1–2 を適用する。それは意図的に、NFC 対 NFD の Unicode 合成の差異を 畳み込まない。開示(name.rs および v0.1 リサーチノート R-005)は、これまでに観測された実際の GPKI 不一致がエンコーディングタグと末尾空白の差異であり、そのいずれもこの最小プロファイルが捕捉することを記録している。手法文字列は、すべてのレポートで dn_match_method == "rfc4518-minimal-v0.1" として固定されており、FR-061 は v0.3 系統内でそれを変更することを禁じている(完全な stringprep は後のブランチとなる)。各 ChainStep は DnMatchEvidence(method、bytewise_equal、および適用された緩和)を保持しているため、監査者はバイト単位非等価の連結がどのように確立されたかを正確に確認できる(path/mod.rs:155-157、report/mod.rs:678)。
ChainStep)パス内のすべての証明書 — リーフ、中間証明書、および終端アンカー — は、一つの ChainStep を生成する(crates/pverify-core/src/report/mod.rs:611)。パス検証監査に最も関連するフィールド:
| フィールド | 出所 / 意味 |
|---|---|
subject_dn_text、subject_fingerprint |
サブジェクトの RFC 4514 テキストと SHA-256 hex |
issuer_subject_fingerprint |
親ステップ / アンカーのフィンガープリント(アンカーでは None) |
validity_not_before / not_after / validity_window_covers_verification_time |
有効期間ウィンドウと、VRT がその内側に入るかどうか |
key_usage_bits |
keyUsage ビット名(§9.4.3 に従い表出 + 強制される) |
extended_key_usages |
EKU OID — 表出され、強制されない(§9.1.1) |
basic_constraints |
{ ca, path_len_constraint } — ca は強制され、path_len_constraint は表出のみ(§9.4.2) |
cert_signature_alg |
証明書の signatureAlgorithm OID。§9.9 のサポートされない OID 走査を駆動する |
signature_verification |
Verified / Failed { reason } / NotApplicableForRoot(アンカー) — §9.8 によって配線される |
dn_match_with_parent |
rfc4518-minimal からの DnMatchEvidence(§9.10) |
name_constraints_check |
ステップごとの NC プロジェクション。スコープ内に制約が無い場合は None(v0.3 以前のフィクスチャについてバイト同一性を保持する、SC-017) |
policy_check |
ステップごとのポリシープロジェクション。自明な {anyPolicy} 状態については None(SC-017) |
aia_ocsp_uris |
AIA id-ad-ocsp URI(失効層が消費する、失効の章を参照) |
revocation |
パスエンジンからのプレースホルダ IndeterminateNoCrl。下流で実際の失効結果に置き換えられる(apply_revocations) |
パスエンジン自身が発行する revocation レコードは、常にプレースホルダ(placeholder_revocation、path/mod.rs:787、anchor_revocation_placeholder、path/mod.rs:804)であり、IndeterminateNoCrl と当該証明書の CDP URI を保持する — 誠実な「この層ではまだ評価されていない」という値である。実際の CRL/OCSP の結果は失効サブシステム(失効の章を参照)によって書き込まれる。これが、aggregate_etsi が失効所見を走査する際にアンカーのプレースホルダレコードが明示的にスキップされる理由である(verify.rs:2476-2489)。
name_constraints_check および policy_check フィールドが Option であり、自明な状態については不在となることは、再現性を保持する設計である(#[serde(skip_serializing_if = "Option::is_none")]、report/mod.rs:634、643)。すなわち、制約/ポリシーを持たないチェーンは v0.3 以前のレポート形状とバイト単位で同一にシリアライズされ、憲章 §II の追加機能のバイト同一性の不変条件を尊重する。
wasm32 で同一の ChainOutcome。MAX_CHAIN_DEPTH = 10 上限が、敵対的な相互認証グラフ上で construct_chain を停止させる。TOTAL_FAILED。ウィンドウ外のアンカーは INDETERMINATE(偽造ではなく構成の問題)。INDETERMINATE であり、決して TOTAL_FAILED ではない — pverify は、チェーンが偽造であると主張することなく、「このアンカー/ポリシー集合のもとでステートマシンによって拒否された」ことを報告する(憲章 §I)。pathLenConstraint は表出され、強制されない。 これには、パスエンジン内で TSA 証明書に対する id-kp-timeStamping 要件が無いことが含まれる。Accepted / RejectedConstraintFailure / RejectedPolicyInconclusive の判定を保持する BridgeAttempt として記録される。レガシーの bridge_ca_required_unsupported の発行は廃止された。ChainSignatureFailed 判定へ集約されない(§9.9 の留意点)。下流の層 — CMS 署名者署名検証、失効(CRL/OCSP、PAdES /DSS+/VRI 材料を含む)、タイムスタンプと VRT 導出、アルゴリズムポリシー、および ETSI 集約 — は、本エンジンが返す ChainOutcome の上に構築される。それぞれの章を参照されたい。
リリース: v1.3.0 · レポートスキーマ: 1.9.0
本章では、pverify のトラストアンカーがどこに由来するのか、候補となるルートがどのように認証され配布物へ受け入れられるのか、検証カーネルがそれらをどのように消費するのか、そして同等に重要なこととして、何が意図的にアンカーでないのかを説明する。トラストアンカーの選定は、署名検証器が下す決定のうち最も重大な単一の決定である。すなわち、それは公理的に、どの署名が TOTAL_PASSED に到達しうるかを決定する。したがって pverify は、アンカー管理を RFC 6024(Trust Anchor Management Requirements)に支配される*出所(プロビナンス)*の問題として扱い、暗号カーネルの厳格に外側に保ち、憲章 §I に沿って監査可能な事実として表出する。
本章の内容は、第3章で定義されたカーネル境界(crate::traits::TrustAnchorStore 能力トレイト)の上に位置し、第9章で説明されるパス検証エンジンへと供給される。ここで論じる取り込みクレート(pverify-eutl、pverify-aatl、pverify-anchor-inventory)は、ホスト専用の std ライブラリであり、cargo-tree ゲート(scripts/cargo-tree-gate.sh)が WASMクリーンなコアグラフから排除している。トポロジーについては第3章 §3.2 を参照。
検証カーネル pverify-core は、組み込みのトラストアンカーをゼロで出荷される。アンカー集合は、常に呼び出し側が単一の能力トレイトを通じて供給する。
// crates/pverify-core/src/traits.rs
pub trait TrustAnchorStore {
fn anchors(&self) -> &[TrustAnchor];
}
pub struct TrustAnchor {
pub der_bytes: Vec<u8>, // full Certificate SEQUENCE DER
pub fingerprint: [u8; 32], // SHA-256 of der_bytes
pub subject_dn: Vec<u8>, // raw encoded Name SEQUENCE
pub validity_window_covers_request_time: bool, // FR-025, computed host-side
pub subject_key_identifier: Option<Vec<u8>>, // SKID keyid bytes、AKID タイブレーク用
}これは意図的なアーキテクチャ上のコミットメントであり、docs/trust-anchor-policy.md §10 に文書化されている。すなわち 「pverify-core はトラストアンカーを埋め込まない。アンカー集合は常に呼び出し側が供給する(CLI は --trust-anchors 経由、ブラウザはバンドルされた roots.json 経由)」。これは PKI 監査人に関係する三つの帰結を持つ。
pverify-core は no_std + alloc であり、I/O を行わず unsafe を禁止している(第3章 §3.1)ため、ルートをコンパイル時に埋め込んだり、取得したり、その他の手段でカーネル内部に実体化したりする経路は存在しない。パス検証に到達するすべてのアンカーは、運用者が検査可能なホストローダーを通過している。TrustAnchor のドキュメントコメントが述べるとおり、これらのフィールドは「ルーティングと同一性の表面のみ」である。der_bytes の構造的 DER パースはパスエンジン(x509::Certificate)内部で行われ、発行者→主体者のマッチングは RFC 4518 の最小 DN マッチャー(crate::x509::name::match_dn)を用いて生の subject_dn バイトで行われる。subject_key_identifier は SKID keyid バイトを保持し、同一 DN を持つ複数世代のアンカーが存在する場合の AKID ベースタイブレークに用いられる(第9章 §9.3 アンカー優先選択参照)。validity_window_covers_request_time は、検証時刻 t に対してホストがロード時に計算する(crates/pverify-cli/src/trust.rs)。ストアは「意図的に不活性」である。すなわち、期限切れのものを含むすべてのアンカーを、供給された順序で表出し、重複排除もフィルタリングも決して行わない(InMemoryTrustAnchorStore、traits.rs)。パス構築は、その後、フラグが false であるアンカーをすべてスキップする。期限切れのアンカーを完全に除外してしまうと、運用者がそれを供給したという事実を覆い隠してしまう。それを表出することで、レポートが期限切れアンカーの所見を記録できる。監査人から繰り返し寄せられる質問(docs/trust-anchor-policy.md §9 で詳述)は、アンカーの認可元の公表(例:kanpo.go.jp で告知される官報 SECOM ルート)が依然として有効か、あるいは配布ホストの TLS が依然として妥当かを、pverify が検証時に再確認すべきかどうかである。それは行ってはならず、意図的に行わない。 このポリシーは RFC 6024 の確立(一回限りの安全な帯域外認証であり、authority_url + SHA-256 ピン + retrieved_at として記録される)と検証(既に確立されたローカルのアンカー集合に対するパス検証)の分離に従う。ひとたびアンカーのフィンガープリントが台帳にピン留めされれば、それは自己認証的である。検証時にソースを再取得することは §II 再現性を破壊し、021 net-guard によって閉じられた SSRF 表面を再導入し、証明に何も加えない。これは、OS/ブラウザのルートストアが、TLS ハンドシェイクのたびに各 CA のウェブサイトに再接続したりしないのと正確に同じである。検証時にカーネルがアンカーに対して行う唯一の証明書チェックは妥当性ウィンドウであり、これはパス検証が既に t で評価している。公表の再アテステーションはガバナンス/ライフサイクルの活動(台帳更新またはリリースケイデンス)であって、ランタイムの振る舞いではない。
pverify は複数の配布チャネルからアンカーを認識し、それぞれに data_source 出所タグを付す。これらのチャネルは三つの信頼ティアに分かれており、docs/trust-anchor-policy.md §2 で列挙されている。
web/roots/*.json)主要なチャネルは機械可読な JSON 台帳(ルートごとに1ファイル)であり、それ自体がポインタ+整合性ピンのトラステッドリストである。docs/trust-anchor-policy.md §4 のとおり、証明書バイトはコミットされない。各台帳エントリは、ソース URL(retrieved_from)、SHA-256 ピン、data_source、purpose、主体者 DN、妥当性ウィンドウ、採用根拠を記録し、web ビルド(build-roots.mjs)がビルド時にバイトをダウンロードし、ピンと一致しない場合は失敗する(改竄されたダウンロード、または発行者鍵のローテーションのいずれかを捕捉する)。四つの受け入れ条件(ポリシー §3)は次のとおりである。公式の authority_url を伴う認可された出所であること。SHA-256 ピンの一致。構造的妥当性(自己署名、BasicConstraints cA=TRUE、keyCertSign、未来日付)。そして完全な出所記録。
台帳に保持される確認済みの data_source トークンは次のとおりである。
data_source |
意味 | ルートの例 |
|---|---|---|
web-publication |
発行主体が公に告知するルート | SECOM RSA Root CA 2023(現行の官報 / e-官報 PDF アンカー — 注:SECOM 商用 PKI であって、GPKI ではない) |
gpki |
政府認証基盤(GPKI)の自己署名ルート | JGCA(Japanese Government Root CA、2023–2048、WebTrust 監査済、Microsoft Trusted Root 2025年5月更新)、GPKI BridgeCA、OfficialStatusCA、JPKI sign/auth |
lgpki2 |
J-LIS 地方公共団体情報システム機構 PKI 第2世代 | LGPKI2 Organization CA R2(lgpki2-org-ca-r2.json、シリアル 5b:87:8c:23、§10.2.2 参照) |
fpki |
米国 Federal PKI トラストルート | Federal Common Policy CA G2(FCPCA G2)、単一の自己署名ルート |
accredited-ca |
GPKI BridgeCA によって相互認証された認定認証業務(電子署名法) | AOSign、TOiNX、TDB TypeA、Secom Passport for G-ID、DIACERT/DIACERT-PLUS、e-Probatio |
commercial-registration |
商業登記電子認証(法務省) | 東京法務局登記官ルート(2022/2025/2026) |
eutl-ojeu / eidas-lotl-signer |
EU LOTL の XAdES 署名者(取り込みのブートストラップであって、文書署名用アンカーではない) | EU LOTL 署名証明書 |
adobe-aatl |
Adobe Root CA G2(AATL 配布物のリスト署名者ブートストラップ) | Adobe Root CA G2 |
lgpki2-org-ca-r2.json)web/roots/lgpki2-org-ca-r2.json は地方公共団体の公文書電子署名を担う J-LIS LGPKI2 Organization CA R2(シリアル 5b:87:8c:23)を記録する。
なぜ信頼できるか。 J-LIS 公式 CA 情報ページ(authority_url: https://www.lgpki.go.jp/CAInfo/install.htm、retrieved_from: https://www.lgpki.go.jp/CAInfo/ocar2ver2.cer)から取得し、SHA-256 フィンガープリント(ca137029…)を台帳にピン留めしている。ビルドはライブバイトとピンが不一致の場合に失敗する。CP/CPS および技術仕様書(C-6-4-5_LG_tech_LGPKI_spec)も同一ドメインで公開されている。
信頼スコープ。 GPKI ブリッジ CA と相互認証されているが、BCA 自体はいかなる公開ルートストア(OS・ブラウザ・Microsoft Trusted Root)にも含まれていないため、実質的に閉じた PKI である。主に都道府県・市区町村の公文書署名が対象。
失効確認。 加入者証明書の CDP は DirectoryName 形式(CN=CRL{N},OU=Organization CA R2,O=LGPKI2,C=JP)のみ; www.lgpki.go.jp:389 はインターネット到達可能(2026-06-26 確認)で dirname_to_ldap_url ヒントテーブル(§11.4)経由で取得。オフライン検証は /DSS 埋め込み CRL を使用。
別個の実験的ティアが FPKI BRAWL Dev FCPCA D1 ルートを保持する。これは pverify のポスト量子検証パス(010-ml-dsa-verify)を行使する ML-DSA-87(FIPS 204、OID 2.16.840.1.101.3.4.3.19) の自己署名ルートである。purpose: pqc-interop-lab でタグ付けされ、web UI は明示的な「experimental lab — not a production trust anchor」バッジを表示する。あらゆる表面で data_source/purpose によって隔離されており、本番の検証パスを暗黙のうちに終端させることは決してない。
二つの data_source トークン、eidas-lotl-signer と AATL の aatl-list-signer は、文書署名用アンカーではない。これらは取り込みソースを認証するために用いられるピン留め署名者である(EU LOTL XML、Adobe AATL PDF)。ポリシーは明示的である(§2)。これらは「文書署名用アンカーではない(purpose: …-signer、検証トラストストアには注入されない)」。LOTL を通じて受け入れられた国家 CA は purpose: eidas-qualified の下で隔離される。AATL 経由で受け入れられた政府ルートは data_source: adobe-aatl の下で隔離される。ブートストラップ署名者が --trust-anchors に入ることは決してない。
pverify-eutl、スライス 014)pverify-eutl は ETSI TS 119 612 の TrustServiceStatusList(TSL)を取り込み、取り込み出所でラップされた通常の pverify_core::traits::TrustAnchor 値を発行する。これはネイティブの std クレートであり、コア/WASM ランタイムグラフからゲート排除されており、それ自身ではネットワーク I/O を一切行わない。LOTL 探索中の各国リストの取得は、呼び出し側が供給する fetch クロージャ(crates/pverify-eutl/src/ingest.rs、ingest_lotl)であり、これによりクレートは決定論的で、捕捉したバイトからテスト可能なまま保たれる。
決定的に重要なこととして、eutl はトラステッドリストを、自己署名アンカーへの RFC 5280 チェーンを構築し失効検証を実行することで認証するのではない。crates/pverify-eutl/src/verify.rs が文書化するとおり(リサーチ R-003)、「信頼はピン(OJEU ピン留めの LOTL 署名者、または LOTL が保証する国家署名者の同一性)によって確立される」。したがって authenticate() は次を行う。
pverify_xades::extract を介してエンベロープされた ds:Signature を抽出する(すべての XAdES 検証が用いるのと同じ Exclusive C14N エンジン bergshamra-c14n を再利用する — 第7章)。signer_certs のいずれかと DER 完全一致 することを要求する(signer_certs.iter().any(|pin| pin.as_slice() == signer_der))。ds:Reference ダイジェストを検証する(コンテンツの整合性)。pverify_core の RustCrypto プリミティブ(RSA-PSS、RSA PKCS#1 v1.5、xades_sig_alg_oid における OID 解決に従った ECDSA)を用いて、正規 SignedInfo 上の署名値を検証する。SigningCertificateV2/CertDigest のバインディングを強制する(証明書差し替え防御)。サポートされない XAdES プロファイル、正規化、または署名アルゴリズムは、そのリストにとっての検証失敗であって、チェックの暗黙のスキップでは決してない(FR-004)。そして「ここで失敗したリストからはアンカーが一切読み取られない」(ingest_one は空のアンカーとともに signature_verified: false を返す)。
t におけるステータスフィルタ(セキュリティの中核)eutl において最もセキュリティクリティカルな単一の決定は、crates/pverify-eutl/src/status.rs および verify.rs::authenticate に実装された granted-at-t フィルタである。TSL サービスは、二つの条件が成り立つ場合かつその場合に限りトラストアンカーを産出する(ingest.rs::anchor_services)。
…/Svctype/CA/QC であること(SVCTYPE_CA_QC、R-002、ServiceType のみ)。Qualifications は意図的に参照されない。TSA / OCSP / CRL サービスタイプは non_ca_qc_type で除外される。t において有効なステータスが granted であること(STATUS_GRANTED、R-001)。ステータスリゾルバ ServiceStatus::status_at(t)(status.rs)は時刻について精密である。現在のステータスに加え ServiceHistory から、時刻付きの (StatusStartingTime, ServiceStatus) の各インスタンスをすべて収集し、昇順にソートし、半開区間 [starting_time, next_starting_time) が t を含むインスタンスを選択する(最後の区間は右側が非有界である)。t ではなく「現在」で解決すると、文書が署名された後に取り下げられた CA を信頼するか、署名時には付与されていた CA を不信とするかのいずれかとなり、セキュリティ目標と §II 再現性の双方を破壊する。境界のセマンティクスは左端を含む(StatusStartingTime のちょうどその時点で新しいステータスが発効する)。これは boundary_at_exact_starting_time テストが表明するとおりである。
受け入れられる集合は granted のみである。eIDAS 以前の11個のレガシーステータス URI(undersupervision、accredited、supervisionincessation など、LEGACY_STATUS_URIS で列挙される)は認識されるため、過去の t での解決は正しい区間に着地するが、それらはすべて除外する。レガシーの「アクティブ」状態へ広げることは記録されたフォローアップであって、決して暗黙のうちに想定されない。認識されないステータス URI は「granted ではない」(除外)として解決され、暗黙のうちに受け入れられることは決してない。
検査されたがアンカー化されなかったすべてのサービスは、その理由とともに excluded[](ExcludedService / ExclusionReason::{NonCaQcType, Status})に記録されるため、§I の事実記録は、何が受け入れられ何が落とされたかの双方について誠実である。ServiceDigitalIdentity がパース可能な CA 証明書を一切伴わなかった granted な CA/QC サービスもまた、暗黙のうちに落とされるのではなく記録される(granted_but_no_parseable_ca_certificate)。
ingest_lotl は完全な LOTL(List of Trusted Lists)探索を駆動する。
各国リストは、グローバルなアンカー集合に対してではなく、LOTL がそれに対して保証する署名者の同一性(TslPointer.expected_signer_identity、ポインタの ServiceDigitalIdentity 由来)に対して認証される(FR-012)。失敗した単一の国は、記録された理由とともに SkippedList となり、実行を中断することは決してない(FR-011、R-006 部分的成功)。これは誠実な「到達したが取り込まれなかった」シグナルである。アンカーは国をまたいで SHA-256 フィンガープリントによって重複排除され、seen_in がすべての (territory, service_name) の裏書きを蓄積し(dedup_and_sort)、決定性のためフィンガープリント順で発行される。
TSL は、C14N 層が既に信頼している同じ bergshamra-xml DOM 上でパースされる(crates/pverify-eutl/src/tsl.rs、リサーチ R-004)。これは単一の XML パーサであり、第2のパーサがもたらす署名ラッピング/パーサ差異のリスクを排除する。不正形式またはルートのない XML は致命的な EutlError であって、決して暗黙の空ではない。
observed_status受け入れられた各 CA 証明書は、pverify-cli/src/trust.rs がそれらを構築するのと正確に同じく、build_anchor によって TrustAnchor に変換される。CA 証明書(BasicConstraints.cA == true)のみがアンカー化され、リーフ形状の ServiceDigitalIdentity 証明書は記録されアンカー化されない。産出されるすべてのアンカーは purpose: "eidas-qualified" と observed_status: "granted" を保持する。憲章 §I はドキュメントコメントで補強されている。observed_status は「生の TS 119 612 ステータス文字列のみを記録する」のであり、purpose = "eidas-qualified" は「出所/隔離タグであって、決して判定ではない」。pverify は「適格(qualified)」も「法的有効性」も決して業務上の判定をしない。
pverify-aatl、スライス 026)pverify-aatl は pverify-eutl の AATL 版である。Adobe の AATL 配布物(https://trustlist.adobe.com/tl12.acrobatsecuritysettings)は CMS 署名された PDF 1.6(PAdES) であり、その署名チェーンは自己署名の Adobe Root CA G2 で終端し、単一の FlateDecode の text/xml /EmbeddedFile をラップする。これは名前空間のない <SecuritySettings><TrustedIdentities><Identity>… リスト(約294個の同一性)である。クレートは ingest(distribution_bytes, adobe_root_ca_g2_der, at) という、ネットワーク I/O のない純粋関数である。
eutl と同様に、AATL 配布物はピンによって認証されるが、その機構は注目に値する。crates/pverify-aatl/src/pdf.rs::authenticate は、ピン留めされた Adobe Root CA G2 のみを含む単一アンカーストアに対して、VerificationMode::Offline で実際の pverify_core::pades::verify_pdf パイプライン(第6章)を実行する。受け入れ基準は構造的なピンベースの述語であって、ETSI Indication ではない。これは意図的である。オフラインでは、配布物の ETSI インディケーションは INDETERMINATE(失効検証ができない — §I の「断言できないことは断言しない」規律)であることが期待されるため、インディケーションを消費するとすべてのオフライン取り込みを拒否してしまう。代わりに entry_satisfies_pin は、産出されたいずれかの SignatureEntry に対して次を要求する。
chain_result.terminating_anchor_fingerprint == hex(SHA-256(pin)) — チェーンがピン留めされた Adobe Root CA G2 に到達した。Verified(アンカーステップは NotApplicableForRoot)。Verified かつ signed_attrs_digest_match == Some(true)。content_digest_check.digest_match == true — 埋め込まれた XML ペイロードが無傷である。信頼はピンであり、eutl が OJEU ピン留めの LOTL 署名者を信頼するのと正確に同じである。(verify_pdf 呼び出しは、034 の DSS/VRI 材料には &Default::default() を、032 のアルゴリズムポリシーには None を渡すため、認証は素朴なピン留めルートのチェーンチェックである。)
認証の後、extract_embedded_xml が展開された text/xml /EmbeddedFile を取り出し、parse_identities(crates/pverify-aatl/src/parse.rs)が各 <Identity> を同じ bergshamra-xml DOM 上でパースする。classify(ingest.rs)はその後、ある同一性をアンカーとして受け入れるのは、順に、次が成り立つ場合に限る。
<Certificate> 本体が base64 デコードでき かつ X.509 としてパースできる(さもなくば certificate_parse_failed)。<Trust><Root> が 1 である(さもなくば not_root)。<Trust><CertifiedDocuments> が 1 である(さもなくば not_certified_documents)。not_government_affiliated)。このヒューリスティック(crates/pverify-aatl/src/filter.rs::is_government_affiliated)は、主体者 DN を、クローズドで文書化された GOV_KEYWORDS 集合(government、gobierno、gouvernement、icp-brasil、cca india、bundesamt、ministry、federal、uae、dod など)に対して大文字小文字を区別しない部分文字列マッチで照合する。憲章 §I がこの表面を明示的に支配する。「ヒューリスティックは判定ではない…ヒューリスティックが見落とした真に政府由来の同一性は除外され記録されるのであって、決して暗黙のうちにアンカー化されることはなく、境界的な一致はレビューのために可視である」。一致したキーワードはアンカーの affiliation_match 出所フィールドに逐語的に記録されるため、人間がすべての受け入れを監査でき、キーワードリストは「監査表面である — 意図的に拡張せよ、決して暗黙のうちに拡張するな」。ユニットテストは両方向をピン留めする。政府の主体者(ICP-Brasil、Swiss Confederation、CCA India)は一致する。係争中の商用 CA(WoSign)および通常の商用ルート(DigiCert)は一致しない。
除外されたすべての同一性は、その ExclusionReason とともに ExcludedIdentity として記録されるため、和集合(受け入れアンカー ∪ 除外)は配布物の全同一性数に等しい — この不変条件は後にインベントリで表出される(§10.5)。
AATL 配布物は取得され、コミットされない(docs/trust-anchor-policy.md §2)。Adobe のキュレートされた編纂物は再配布ライセンスが不明確である(潜在的な編集著作権/データベース権)ため、pverify は一般的な OSS の慣行(tl-create、PDF4QT、pyHanko)に従い、ユーザ/CI が Adobe から直接取得するようにしている。台帳に記録されるのは、ピン留めされた Adobe Root CA G2(Adobe 自身のルート)のみである。ポリシーはまた、監査人が留意すべき具体的な罠も指摘する。官報トラストルート(SECOM RSA Root CA 2023 / OfficialStatusCA 2024)は AATL に含まれず(DER-SHA256 で別物であることを確認済み)、AATL には紛らわしい名前の類似品 SECOM Document Signing RSA Root CA 2023 が含まれているが、これを官報アンカーと混同してはならない — 官報アンカーは個別にピン留めされたままである。
AATL 台帳エントリの retrieved_from は、裸の証明書ではなく AATL 配布物(tl12.acrobatsecuritysettings、PAdES 署名された PDF)を指している — エントリがピン留めする Adobe Root CA G2 は、その PDF の CMS 署名の内側に存在する。以前はウェブビルド(build-roots.mjs)が取得バイトからピン留め証明書を直接読み取ろうとしており、台帳がキャッシュ済み DER ではなく配布物を指している場合に CLI へ誤ったバイトを渡していた — その結果 ingest-aatl は何も認証できず、本番のルート一覧ページは AATL アンカーをゼロ件しか取り込めていなかった。ビルドは現在、CLI 呼び出しの前に配布物からピン留め証明書を抽出する。すなわち PDF 内のすべての /Contents <hex…> CMS ブロブをデコードし(オペレータ提供の裸証明書のために生バイトスキャンへフォールバックし)、各々から X.509 DER の SEQUENCE をスキャンして、DER SHA-256 が台帳ピンに等しい最初のスライスを返す(extractPinnedCertByHash)。ピンにハッシュ一致するスライスのみを受理することで、スキャンはそれがバイト列のどこを指していても安全になる。ピンが唯一の完全性アンカーであり続ける(バンドルせず取得する方針は維持され、新たに何もコミットしない)。一致するスライスがなければ、ビルドは未認証の証明書を取り込むのではなく AATL レポートをスキップする。
pverify-anchor-inventory、スライス 028)pverify-anchor-inventory は、pverify のリリースが配布するすべてのチャネルを読み取り、単一の正規 Inventory 成果物を発行するホスト専用の std 集約ライブラリである。これは二つの表面によってバイト単位で同一に消費される。CLI の pverify trust-anchors dump コマンドと、トラストアンカー可視化ページを支える web の web/site/trust-anchors.json である。その構築上の保証(aggregate はクロック読み取りもランダム性もない純粋関数であり、write_json は2スペースインデント/LF/末尾改行1個の JSON を発行する)により、同じリリース入力に対する2回の実行はバイト単位で同一となる — 憲章 §II 再現性であり、リリース間のアンカードリフト差分の基礎である。
クローズドな ChannelLabel 列挙型がチャネル集合とその表示順序を固定する。pinned-ledger、fpki-bundle、eutl、aatl-trusted、aatl-excluded、pqc-interop(crates/pverify-anchor-inventory/src/inventory.rs)。集約器は各チャネル(ピン留め台帳エントリ、捕捉した eutl の IngestReport、捕捉した AATL の IngestReport)を取り込み、その後 merge_and_sort がフィンガープリントによって重複排除しつつ帰属(attribution)をマージする。同じフィンガープリントが二つのチャネルに現れる場合(例:FCPCA G2 台帳ルートでもある FPKI 証明書)、複数の EntryAttribution を伴う1個の AnchorEntry が発行され、affiliation/country/key_algorithm については先勝ち(first-non-empty-wins)となる。FPKI ルート(data_source == "fpki")は fpki-bundle へ振り分けられ、pqc-interop-lab purpose は pqc-interop へ振り分けられ、残りは pinned-ledger へ振り分けられる。
026 の取り込みが落とした AATL の同一性は、別個の型(ExcludedEntry)として別個の JSON 配列(excluded[])で読み取り専用に表出されるため、「いかなるレンダリング/フィルタ/ソート/エクスポートの経路もそれらをトラストアンカーと衝突させ得ない」(FR-007/FR-010)。web UI はこれを独立したタブ(「AATL 除外(参考)」)として、永続的で消去不可能な「informational, not a trust anchor」バナーとともに実現する。各 not_government_affiliated の除外について、インベントリは主体者 DN の傍らに完全な GOV_KEYWORDS のスナップショットと固定された平易な監査ノート(GOV_KW_AUDIT_NOTE)を埋め込んだ GovKwAudit パネルを添付する — レビュアーが §10.4.2 のヒューリスティックを盲信することなく検査によって監査できるようにする(FR-009)。
check_invariants(crates/pverify-anchor-inventory/src/aggregate.rs)はインベントリが返される前に呼び出され、いかなる違反に対してもビルドを声高に失敗させる。監査上の関心事として次がある。
entries[] と excluded[] がそれぞれの決定論的ソート順序にある。IngestReport.total_identities に等しい — §10.4 の保存則であり、ビルド時チェックとして表出される。is_inventory_anchor はフィンガープリントが真に entries[] に存在する場合にのみ設定される。crate::chain_reach::derive_next_hop_issuers は「このルートの向こう側に何があるか」(US3)に、レンダリング時の単一ホップで答える。各ルートについて、その自身の各寄与チャネル内で、発行者 DN がそのルートの主体者 DN に等しい配布済みエントリを列挙する(トリム後の RFC 4514 テキスト等価性であり、OID エンコードのマッチングよりこちらを選んだのは「人間がデータを読む方法」を反映するためである、R-4)。二つの性質が荷重を担い、監査人に関係する。
pinned-ledger のルートは、DN が一致してもなお fpki-bundle から次ホップを拾わない。チャネルをまたぐエッジが表出するには、同じエントリが両方のチャネルに帰属を寄与しなければならない。cross_channel_edges_forbidden テストがこれをピン留めする。next_hop_issuers: [] を産出し、レンダラは空配列を手がかりに明示的な「no next-hop issuers recorded in the distributed material」メッセージを印字する — 決して暗黙の空パネルではなく、決して捏造されたエッジではない(§I)。これはパス検証ではなく、いかなる信頼の主張もしない。既に配布されたバイトに対する検査の補助である。
ウェブのトラストアンカ画面(web/site/roots.js)はインベントリを出所(data_source)別にまとめ、各カードになぜそのルートが同梱されているか — どの PKI/プログラム由来か — を表示する。これにより読者はカードを展開せずとも一目でアンカーを判断できる。各カードはローカライズされた data_source タグ(SOURCE_KEYS:GPKI、GPKI 相互認証の認定認証局、商業登記、官報、FPKI、EU 信頼リスト、Adobe AATL、PQC 相互運用ラボ)と、そのエントリの国タグを表示する。未知の出所は生キーへフォールバックする(決して隠さない、§I)。ページの残りを埋もれさせるほど大きいチャネル — 実際には数百の各国リストアンカーを抱える EU LOTL/各国信頼リスト — は、既定でチャネルバナーをサマリとする <details> の背後に折りたたまれる(COLLAPSED_BY_DEFAULT)。これにより国内のピン留め台帳ルートは見えたまま、EU チャネルは意図的なクリックで開く。ピン留め台帳チャネルは「個別ピン登録のルート(手動台帳)」と表示し、取り込まれたリストではなく手動でキュレートされ個別にピン留めされた集合であることを伝える。
pverify のアンカーチャネルは、構造的に異なる二つの PKI 信頼トポロジーを体現しており、その区別は、アンカー集合において何が畳めて何が畳めないかにとって重要である。
ブリッジ/階層モデル(米国 FBCA、CertiPath、日本の GPKI BridgeCA フェデレーション)は還元可能である。すなわち、単一のアンカーに相互認証証明書のプールを加えたものが、多くのドメインにおける信頼を表現できる。なぜならブリッジがドメインルートを相互認証するからである。pverify のパスエンジンは、ブリッジのクロッシングを crates/pverify-core/src/path/bridge.rs(第9章)で明示的に扱う — detect_crossing が「外国が国内を署名する」および「国内が外国を署名する」相互認証証明書の形を認識する。
トラステッドリストモデル(eIDAS LOTL/TSL、Adobe AATL)は還元できない。これは N 個の相互に独立した自己署名ルートのフラットな集合であり、各々が列挙によって個別に信頼される。それらすべてが相互認証する単一のブリッジは存在しない。プロジェクトメモリノート「トラスト組み込み非対称性」に記録され(eutl/aatl 設計によって確認されているとおり)、ブリッジ/階層のルートは「1アンカー + 相互認証証明書プール」に畳めるが、トラステッドリストは畳めない — web/roots 集合は、構成上、独立した自己署名ルートのリストであるため、「ブリッジ CA 以外をすべて落とす」は妥当な還元ではない。これこそが、eutl と aatl が単一の取り込み「スーパーアンカー」ではなく、受け入れられたルートごとに1個の通常の TrustAnchor を発行する理由である。フラット N モデルはフラット N 個のアンカーを要求し、各々が自身の出所を保持する。
三つのクラスの証明書がアンカー集合から意図的に除外される。監査人はこれらの境界が保たれていることを検証すべきである。なぜなら、各々が設計によって閉ざされた現実の攻撃または混同を表すからである。
/DSS の /Certs は決してアンカーではない034 PAdES DSS/VRI 機能(第6章)は、PDF の文書レベルの /DSS /Certs プールを消費して署名者の証明書チェーンを構築する。これらの証明書は、中間/リーフのパス構築候補としてのみ受け入れられ、決してトラストアンカーとしてではない(crates/pverify-core/src/pades/mod.rs、Q7/FR-005)。コードは使用箇所で明示的である。解決された /Certs は intermediates 候補集合に追加される —
// crates/pverify-core/src/pades/mod.rs ~L662–677
// Append its `/Certs` to the intermediate-candidate set (never to anchors —
// Q7/FR-005/VR-2) …
for cert in &resolved.certs {
if !intermediates.iter().any(|c| c.fingerprint_hex == cert.fingerprint_hex) {
intermediates.push(cert.clone());
}
}
これは本質的に重要である。/DSS は検証対象の文書の一部であり、攻撃者が制御可能である。もし /Certs エントリがトラストアンカーになりうるなら、敵対者は自己署名ルートを埋め込み、自身の署名チェーンをそこで「終端」させることができてしまう。チェーンは依然として運用者が供給したアンカーに到達しなければならない。同じ規則が Adobe revocationInfoArchival 材料にも適用される(それは失効の証拠を供給し、signature_embedded でタグ付けされ、決してアンカーではない)。
fixtures/bundles/*/trust-anchors/ 配下の28個の合成ルートは、テストスイートのためにのみ存在する。それらはポリシーの受け入れ条件1〜3(docs/trust-anchor-policy.md §5)を満たさず、実際の文書署名用アンカーとして配布されることは決してない。
§10.2.4 で述べたとおり、EU LOTL 署名者(eidas-lotl-signer)と Adobe Root CA G2(aatl-list-signer)は取り込みソースを認証する。それらが検証トラストストアに注入されることは決してない。OS/ブラウザのトラストストアへの所属だけでも、同様に受け入れの理由とはならない — 四つのポリシー条件が決定する(docs/trust-anchor-policy.md §5)。
統一的な設計上の選択が本章全体を結びつける。すなわち、取り込みはカーネルが既に理解していないものを何も生み出さない。 すべての取り込みクレートは、pverify-cli/src/trust.rs が --trust-anchors のために生み出すのと同一の (der_bytes, SHA-256 フィンガープリント, subject_dn, validity-covers-t) の形でアンカーを構築する。したがって CLI の取り込みコマンドは、受け入れられたアンカーを PEM ファイル(アンカーごとに1個の <fingerprint>.pem、決定論的なファイル名。crates/pverify-cli/src/ingest.rs::materialize と der_to_pem)として実体化し、運用者はそれを通常の verify --trust-anchors <DIR> ウォーカーにそのまま再投入できる。CLI は ingest-trust-list(eutl、完全な探索のための --lotl と、リスト署名失敗のための終了コード 4 を伴う)、ingest-aatl(aatl)、および trust-anchors dump(インベントリ)を公開する。
これは 憲章 §I と §10.1 のアンカーモデルにおいてループを閉じる。pverify-core はいかなる取り込みクレートからも何も得ない — レポート列挙型と schema_version は凍結されたままである — なぜなら取り込みは通常の TrustAnchor 群とそれ自身の出所レポート型を発行し、カーネルが目にする唯一の信頼は、唯一の能力トレイトを通じて再投入された、フラットで、運用者が供給し、フィンガープリントでピン留めされたアンカー集合だからである。
リリース: v1.3.0 · レポートスキーマ: 1.9.0
本章では、pverify が署名の証明書パス(certification path)上の各証明書について、その失効状態をどのように確定するかを述べる。本章が扱うのは、pverify が理解する2つの失効チャネル — CRL(RFC 5280)と OCSP(RFC 6960)—、配布点からの取得・署名に埋め込まれた失効情報・ステープルされたレスポンスの間を裁定する優先順位規則、日本の認定CAエコシステムのために追加された LDAP-CRL トランスポート、鮮度/陳腐化のウィンドウ、オフラインの誠実性の規律、そして検証器が最終的に報告するクローズドな RevocationOutcome の語彙である。
失効評価は、no_std で I/O を行わない検証カーネルの内部に完全に存在する。計算の全体は crates/pverify-core/src/revocation/(mod.rs がオーケストレーション、crl.rs が CRL のパースと検証、ocsp.rs が OCSP 処理、indirect.rs が間接CRL検出)にある。カーネルはそれ自身ではネットワーク I/O を一切行わない。カーネルが消費するバイト列は RevocationFetcher トレイト(crates/pverify-core/src/traits.rs)経由で到着し、その具体的な実装は CLI のオンライン(ureq)・オフライン・from-bundle のフェッチャ(crates/pverify-cli/src/modes/)である。この分離は、失効に即して言い直した §V/§VI の境界である。すなわち、カーネルはどの URI が取得に値するか、そして返却されたバイト列をどのように解釈するかを決定し、ホストはそれらをどのように取得するかを決定する。全体アーキテクチャについては第3章を、ここで失効検証されるチェーンのステップを生成するパス検証については第9章を参照されたい。
全体を貫く統治原則は、憲章 §I「断定回避」である。すなわち、あらゆる失効結果は検証器が観測した事実である — good on CRL、revoked on OCSP、no CRL pointer、responder unreachable、not checked because offline。状態を確定できない場合、検証器は合格や失敗を捏造するのではなく、精確なサブインディケーションを伴って INDETERMINATE に縮退する。
失効検証は、証明書パスが構築されチェーン内部の署名が検証された後に適用され、かつ各オブジェクトについてそのオブジェクト自身の検証基準時刻(VRT — 第12章)において適用される。署名者チェーンは apply_revocations(crates/pverify-core/src/verify.rs:2075)を介して失効検証され、各タイムスタンプの TSA チェーンは apply_tsa_chain_revocation_for_token(verify.rs:1552)を介して失効検証される。後者はブランチ 033 で導入され、TSA証明書の失効検証を全形式にわたり一貫させたものである。両エントリポイントは単一の失効オーケストレータ check_revocation_ctx(crates/pverify-core/src/revocation/mod.rs:184)に収束する。
apply_revocations はチェーンの非アンカーのステップを反復する(verify.rs:2111)— トラストアンカーそのものはア・プリオリに信頼されるため決して失効検証されない — そして各子証明書について RevocationContext(revocation/mod.rs:76)を構築する。このコンテキストは、親CAの SPKI と主体者DN、フェッチャ、検証時刻(オブジェクトごとのVRT)、トラストアンカー、OCSPレスポンダ/発行者の参照に用いる証明書プール、埋め込みの OCSP/CRL 情報、そして検証モードを保持する。
apply_revocations の2つの細部は、監査者にとって重要である。
ocspVals を受け取るのはステップインデックス0(署名者または TSA のリーフ)のみであり、中間証明書には空の埋め込みOCSPリストが渡される(verify.rs:2126)。これは、ステープルされた OCSP レスポンスが特定のリーフ+発行者のペアに CertID で束縛されているためである。CRLValues エントリはパス上の任意の証明書に適用されうるため、埋め込みCRLのスライス全体が各ステップのコンテキストへ織り込まれる(verify.rs:2142)。pverify は、厳格で監査可能な順序で、最大3つの失効情報のソースを参照する。その規約は、確定的な事実を最初に生み出したチャネルが勝つというものであり、後続のチャネルは先行チャネルが不定であった場合にのみ参照される。この順序付けが、バイト単位の同一性の不変条件(憲章 §II)を保つものである。すなわち、CDP の CRL チャネルが成功した署名は、埋め込み情報の有無に関わらず、まったく同一のレポートを生成する。
check_revocation_ctx(revocation/mod.rs:184)はこのはしごを実装する。
CDP/直接CRL のパイプライン(run_cdp_pipeline、revocation/mod.rs:284)。証明書に記載された CRL 配布点が分類され、取得可能であれば取得・消費される。結果が確定的 — GoodOnCrl または RevokedOnCrl — であれば、それはそのまま返却され、それ以降のチャネルは参照されない(revocation/mod.rs:208)。
埋め込み CRL(try_embedded_crls、revocation/mod.rs:252)。CDP の結果が Indeterminate* の CRL バリアントのいずれかである場合にのみ到達する。各署名内CRL(CAdES/PAdES の revocation-values.crlVals、XAdES の RevocationValues/CRLValues)は、取得されたものと同じ consume_crl 検証 — 発行者DNのリンク、間接CRLの拒否、署名検証、鮮度ウィンドウ、シリアル検索 — を通る。GoodOnCrl/RevokedOnCrl を生む最初の埋め込みCRLが勝つ(revocation/mod.rs:274)。適用されないCRL(発行者が異なる、陳腐化している、間接である)はスキップされ、後続の埋め込みCRLがなお一致しうるようにする。これは LT レベルの署名にそのパスをオフラインで検証させる 019-xades-bt-blt の機構である。
OCSP パイプライン(run_ocsp_pipeline、revocation/mod.rs:620)。CDP チャネルが不定であり、埋め込みCRLが判定を生まず、かつリーフが OCSP 情報(AIA の id-ad-ocsp URI または埋め込みの ocspVals — revocation/mod.rs:228)を持つ場合にのみ到達する。OCSP パイプライン内のソース順序も再び優先順位付けされており、埋め込みの ocspVals が任意の AIA 取得より先に試される(revocation/mod.rs:637)。
フォールスルーのトリガーは明示的である。すなわち、CDP の結果が IndeterminateNoCrl、IndeterminateStaleCrl、IndeterminateIndirectCrl、IndeterminateUnsupportedProtocol、またはレガシーの IndeterminateOcspOnly(revocation/mod.rs:199)のいずれかでなければならない。確定的なCRL結果はいずれもはしごの残りをショートサーキットする。
OCSP の結果が CDP の結果を置き換える場合でも、CDP 側の証拠は返却されるレコードに保持される。cdp_entries と crl_used は finalise_ocsp_record(revocation/mod.rs:988)によって前方へコピーされ、監査者は判定を生んだ一方だけでなく、CRL の試行と OCSP の試行の両方を見ることができる。
classify_distribution_points(revocation/mod.rs:540)は、証明書の CRL 配布点と AIA OCSP ポインタを検査し、それらを次のように振り分ける。
| フィールド | 意味 |
|---|---|
cdp_entries |
構造化 CDP エントリ(Vec<CdpEntry>)。各エントリに kind(uri_http / uri_ldap / uri_other / dirname)、value(URI 文字列または DN テキスト)、fetchable(bool)を持つ。スキーマ ≤ 1.8.0 のフラットな cdp_uris フィールドを置き換える。 |
has_only_unsupported_protocol |
証明書が1つ以上の CDP URI を持つが、いずれも取得可能でない(例: ftp:// のみ) |
no_crl_pointer |
証明書がいかなる種類の CDP URI も持たない |
aia_ocsp_uris |
AIA id-ad-ocsp URI(Vec<String>)。cdp_entries と並んで監査者の視認性のために表出される(スキーマ 1.9.0 追加)。 |
has_ocsp_aia |
id-ad-ocsp AIA アクセス記述(1.3.6.1.5.5.7.48.1)が1つ以上存在する場合 true。OCSP パイプライン入口の判断を駆動する |
CdpEntry.kind 判別子はスキーマ 1.9.0(035-ocsp-client)で導入された。kind の値は次のとおりである。
kind |
条件 |
|---|---|
uri_http |
スキームが http:// または https:// |
uri_ldap |
スキームが ldap:// |
uri_other |
URI は存在するがスキームが HTTP でも LDAP でもない |
dirname |
CDP が directoryName を使用(URI なし) |
取得可能なスキームの判断は is_fetchable_scheme(revocation/mod.rs:569)にある。コアは決してソケットを開かず、どのスキームをホストフェッチャに渡す価値があるかを決定するだけである。真にサポートされないスキーム(例: ftp://)はフェッチャに到達せず、IndeterminateUnsupportedProtocol を生む。CRL ポインタを一切持たない証明書は IndeterminateNoCrl を生む — ただし、AIA OCSP ポインタを持つ場合を除く。その場合、内側の CDP 関数からレガシーの IndeterminateOcspOnly が発行され、外側のオーケストレータがそのリーフを OCSP パイプラインへ再ルーティングする(したがって v0.4 以降のバイナリは IndeterminateOcspOnly を決して表に出さない — revocation/mod.rs:302)。
取得可能な CDP は順次試行され、最初に成功した取得が勝つ(revocation/mod.rs:344)。すべての CDP 取得が失敗した場合 — ホストが到達できなかった ldap:// の CDP を含む — ループは尽き、結果は IndeterminateNoCrl となる(revocation/mod.rs:351)。一時的な取得エラーとオフラインの NotAvailableOffline のいずれも、CRL チャネル上では IndeterminateNoCrl に潰れることに注意されたい。オフラインの誠実性の区別(§11.6)は、pverify がさもなくばレスポンダ接触を捏造することになる OCSP チャネルに適用される。
parse_crl(crates/pverify-core/src/revocation/crl.rs:105)は、DER の CertificateList を ParsedCrl の射影へとデコードする。この射影は、発行者DN、thisUpdate、nextUpdate、失効エントリのリスト(シリアル+失効日+理由)、IDP の indirectCRL フラグ、および発行者DNの SHA-256 フィンガープリントを保持する。
監査者のために記録しておくに値する実装上の機微がある。pverify はローカルな CertificateListCompat 型(crl.rs:50)を宣言しており、そこでは TbsCertList.version を OPTIONAL としている。上流の x509-cert 0.2/0.3-rc の型は version を必須フィールドとしてモデル化しており、これは v1 の CRL — RFC 5280 §5.1 が許容するように version INTEGER を完全に省略するもの — のデコードを失敗させる。実世界の GPKI CRL は v2 であるためそこでは潜在化しているが、GlobalSign のポスト量子テスト階層は v1 CRL を出荷しており、これが当該欠陥を露呈させた(docs/research/fpki-brawl-pqc.md を参照)。compat 型は v1(version なし)と v2(あり)をタグによって判別する — 次のフィールドである signature は SEQUENCE(タグ 0x30)であり、INTEGER の version(タグ 0x02)と決して衝突しない — そして v1 の TBS を再エンコードする際は version を省略し、署名検証のために元の署名済みバイト列を再現する。
CRL の reason 列挙は extract_reason_string(crl.rs:198)で人間可読な文字列へマッピングされる。keyCompromise、cACompromise、superseded、cessationOfOperation、certificateHold など。
pverify は間接CRLの消費を拒否する。consume_crl(revocation/mod.rs:384)は、署名を検証する前に indirect::is_indirect(crates/pverify-core/src/revocation/indirect.rs:26)を呼び出す。CRL は次のいずれかの場合に間接として扱われ(そして IndeterminateIndirectCrl へルーティングされ)る。
IssuingDistributionPoint 拡張(2.5.29.28)が indirectCRL = TRUE を主張する、またはIssuer 名が親CAの Subject 名と一致しない(RFC 4518 最小限の DN マッチャ crate::x509::name::match_dn で比較される)。DN照合のエラーもまた疑わしいものとして扱われ拒否される — 「黙って消費するより拒否するほうがよい」(indirect.rs:34)。これは意図的な保守性である。すなわち、pverify は、その証明書の発行者へのCRL発行者のリンクを積極的に確立できないCRLを決して消費せず、これにより一群のCRL置換攻撃を封じる。この拒否は原理的にはCRL署名者の照合パスによって解除しうるが、それは現行のスコープ外である。
間接チェックの後、consume_crl(revocation/mod.rs:384)は次の順序で処理を行う。
署名検証 — verify_crl_signature(crl.rs:159)は TBSCertList を再エンコードし、signatureAlgorithm OID(RSA PKCS#1 v1.5、ECDSA P-256/P-384 など)によって crate::crypto::verify_with_alg を介してディスパッチする。検証の失敗は IndeterminateNoCrl を生む — pverify は認証できないCRLを good の証拠として扱わないが、不正なCRLによって署名を失敗させもしない。CRL を使用できないことは INDETERMINATE の事実であり、改竄の主張ではない。
鮮度(revocation/mod.rs:438):
thisUpdate > verification_time → IndeterminateStaleCrl(CRL が基準時刻より後の日付を持つ — 未来のCRLは過去を証することができない)。nextUpdate が存在しかつ nextUpdate < verification_time → IndeterminateStaleCrl(CRL が VRT に対して期限切れである)。鮮度ウィンドウは、単一のリクエスト全体に共通するクロックではなく、オブジェクトごとのVRTに対して評価される — 署名者チェーンについては vrt.signer_chain.value、TSA チェーンについてはそのトークン自身のVRTである。これが、署名時点では鮮度があったが今日では陳腐化したCRLに対して LT/LTA 署名を検証させるものである。
シリアル検索(revocation/mod.rs:464)。証明書のシリアル番号がその DER から抽出され、失効エントリに対して検索される。一致すれば失効日と理由を伴う RevokedOnCrl を生み、一致しなければ GoodOnCrl を生む。シリアル抽出が失敗した場合は空にデフォルトされる — これは決して一致しえないため、失敗モードは決して誤った revoked にならない。
監査者のために記録される CrlSummary(make_crl_summary、revocation/mod.rs:497)は、ソース URI、CRL の thisUpdate/nextUpdate、CRL 本体の SHA-256、および発行者フィンガープリントを保持する。pdf_source フィールドはここでは None のままとされる — 失効エンジンはプロビナンスに依存せず、PDF の /DSS//VRI プロビナンスは PAdES レイヤによって後付けでタグ付けされる(ブランチ 034、R-7。第6章を参照)。
日本の電子署名法-認定された認証局(AOSign、TDB、TOiNX、e-Probatio、…)の多くは、その CRL を ldap:// 上でのみ公開する。それらのCAの下で発行された証明書を検証するため、pverify はコアにおいて ldap:// を取得可能なスキームとして扱い(is_fetchable_scheme、revocation/mod.rs:569)、CDP URI をそのままホストへ渡す。コアは I/O を行わないまま留まり、LDAP トランスポートは CLI ホスト crates/pverify-cli/src/modes/ldap.rs に存在する。
LDAP クライアントは、std::net::TcpStream の上に構築された最小限で依存関係のない LDAP v3 クライアントである — 「新しいサードパーティ依存をゼロにする」ことは明確化された制約であった。これは次を実行するのにちょうど足りるだけの BER を手で組み立てる。
bind_request、ldap.rs:218 — version 3、空の名前、空の simple [0] 資格情報)、続いてsearch_request、ldap.rs:231 — wholeSubtree スコープ、neverDerefAliases、要求された属性に対する present フィルタ [7]、および属性セレクタ)。URL パーサ(parse_ldap_url、ldap.rs:83)は RFC 4516(ldap://host[:port]/baseDN[?attr[?scope...]])に従い、base DN をパーセントデコードし、ポートを 389 に、属性を certificateRevocationList;binary にデフォルトする。レスポンスリーダ(fetch_crl、ldap.rs:405)は SearchResultEntry([APPLICATION 4]、タグ 0x64)メッセージをパースし、属性タイプをベース名で照合し(任意の ;binary 転送オプションを取り除く)、DER SEQUENCE のバイト 0x30 で始まる最初の属性値を CertificateList として選択する。SearchResultDone(0x65)で停止する。
トランスポートの脅威モデルは明示的に文書化されている(ldap.rs:24)。このクライアントはポート 389 の平文 LDAP を話す — LDAPS(636)も StartTLS もない。能動的なネットワーク攻撃者は偽造された CertificateList を差し替えることができるが、そのリスクは下流で限定される。すなわち、取得されたすべての CRL は、判定に影響を与えうる前に、発行CAに対してカーネルによって署名検証される(§11.3.4)ため、改竄された CRL は信頼されるのではなく、正直なパース/検証の失敗として拒否される。残存するエクスポージャは失効データの拒否である — レスポンスをドロップまたは破損させる攻撃者は IndeterminateNoCrl を生み、決して誤った good を生まない。ソース中の運用ガイダンスは、チャネル完全性が重要な場合には CRL を --from-bundle(コンテンツアドレス指定、フィンガープリント検証済み)を介して帯域外で供給することを推奨する。ldaps:///StartTLS へのアップグレードは、CA ごとの相互運用テスト待ちで延期されている。
SSRF アドレスのネットガード(021)は、HTTP と同様に LDAP パスにも適用される。connect_with_timeout(ldap.rs:509)において、解決されたアドレスは net_guard::guard_ldap_addrs を通してフィルタされ、接続は分類上パブリックなアドレスに対してのみ開かれる(--allow-private-networks が設定されている場合を除く)。ネットガードのブロックは理由を stderr に出力し NotAvailableOffline を返す。これは、到達不能な HTTP CDP とまったく同様に IndeterminateNoCrl にマップされる。オンラインフェッチャ上では、ldap:// URI は fetch_crl(crates/pverify-cli/src/modes/online.rs:299)によってこのクライアントへルーティングされ、HTTP パスとのパリティのために FetchLogEntry が記録される。
レスポンスは HTTP フェッチャを反映して 32 MiB(MAX_RESPONSE_BYTES、ldap.rs:57)でサイズ上限が設けられており、無制限の応答をストリームするディレクトリがメモリを枯渇させることはできない。
CRL チャネルが不定でかつ OCSP 情報が存在する場合、OCSP パイプライン(run_ocsp_pipeline、revocation/mod.rs:620)に入る。すべての OCSP 計算は crates/pverify-core/src/revocation/ocsp.rs に存在する。OCSPRequest を POST し BasicOCSPResponse のバイト列を返す HTTP トランスポートは、ホストの RevocationFetcher::fetch_ocsp である。
ソース順序(revocation/mod.rs:637)は、埋め込みの ocspVals を最初に、次いで AIA 取得(オンラインまたは from-bundle)である。規約は「最初の評価可能なレスポンスが勝つ」である。すなわち、パースに失敗した埋め込みブロブは次のソースへフォールスルーするが、パースに成功しかつ確定的な Indeterminate*(unauthorised/stale/nonce-mismatch/bad-signature)を生む埋め込みレスポンスは終端である — 検証器はそのレスポンスを参照し事実を得たのであり、たとえその事実が「使用不可」であってもである。
AIA 取得については、リクエストは build_ocsp_request_bytes(ocsp.rs:118)によって構築される。すなわち、SHA-1 の下での CertID(RFC 6960 §4.1.1 の REQUIRED なデフォルト — 発行者名ハッシュ、発行者鍵ハッシュ、リーフのシリアル)と requestExtensions[Nonce] を伴う、単一証明書の RFC 6960 §4.1 OCSPRequest である。このリクエストは署名されない — pverify はリクエスタ識別子を偽装しない。ノンスに基づく相関がクライアント側の唯一の認証である。
ノンスは CSPRNG 由来である(NonceSource::generate_nonce、035-ocsp-client P1 / RFC 8954 §2.1)。NonceSource トレイトは呼び出し元が注入し、カーネルを特定の RNG に結合させることなくプラットフォーム固有のエントロピーを使用できる。
// crates/pverify-core/src/revocation/ocsp.rs
pub trait NonceSource: Send + Sync {
fn generate_nonce(&self) -> [u8; 16];
}CLI は OsNonceSource(rand::rngs::OsRng)、ブラウザ/Workers WASM は WasmNonceSource(getrandom + js feature)を使い、各リクエストに OS/プラットフォームの乱数源から 16 バイトを生成する。
nonce = CSPRNG[16] // CLI: OsRng, WASM/Workers: getrandom/js
16 オクテットは RFC 8954 §3.1 が許容する 1〜32 の範囲内である。CSPRNG nonce は真正性を提供する — 攻撃者はリクエスト前に正しいノンスを予測できない。ノンス不一致のレスポンス(Mismatched)は IndeterminateOcspUnknown → revocation_not_checked_online へマップされる(§11.5.5)。
注: 035 P1 以前は決定論的な
derive_nonce(SHA-256 ベースの擬似ノンス)が使われていた。現行コードはderive_nonceを保持しているが、online モードのリクエストビルダには使われない(CSPRNG 注入に置き換え済み)。
Workers/WASM オンラインモード(035-ocsp-client P2)。 Cloudflare Workers(wasm32-unknown-unknown + js ターゲット)で OCSP 取得が動作するようになった。WASM 向けの RevocationFetcher 実装は Web API の fetch を用い、OCSP POST リクエストをブラウザ UI が使う /ocsp Cloudflare Pages Function と同じエンドポイントにルーティングする。これにより、Workers ホストの pverify-core 呼び出しでライブ OCSP チェックができなかったギャップが解消された。
実行スコープ内 OCSP キャッシュ(035-ocsp-client P3)。 単一の検証実行中、同じ (responder_url, cert_serial) ペアに対する OCSP リクエストが重複排除される。キャッシュは VerificationRequest 実行のライフタイムにスコープされ — 実行をまたいで永続化されることはないため、後続の呼び出しで古いレスポンスが再利用されることはない。キャッシュは (responder_url: &str, issuer_name_hash: &[u8], serial: &[u8]) でキー付けされた書き込み一回・読み取り多回のマップである。同一実行内での同一キーへの最初の成功した AIA 取得レスポンスが保存され、以降の参照に再利用される。署名者と TSA が同じ OCSP レスポンダを共有する場合にネットワーク遅延を削減する。
各候補レスポンスは evaluate_ocsp_candidate(revocation/mod.rs:752)を通る。
パース — parse_basic_ocsp_response(ocsp.rs:168)。埋め込みレスポンスでの DER デコード失敗は None を返す(次のソースを試す)。取得したレスポンスでは終端である(IndeterminateOcspMalformed)。
一致する SingleResponse を CertID で特定する — find_matching_single_response(ocsp.rs:185)。この照合はハッシュアルゴリズムに依存しない。cert_id_matches(ocsp.rs:200)はレスポンダが宣言したダイジェスト — SHA-1(1.3.14.3.2.26)、SHA-256、SHA-384、SHA-512 — が何であれそれの下で発行者名/鍵ハッシュを再計算し比較する。認識されないハッシュ OID は不一致であり、IndeterminateOcspUnknown として表出する。(このハッシュ非依存のディスパッチは、実在の欠陥 — v0.4.1.x の Case 2 — を解消した。そこでは鮮度パスが SHA-1 のみの CertID に対するバイト等価でエントリを再特定しており、レスポンダが good と言っていても SHA-256 のレスポンダ CertID を黙って拒否していた。)
レスポンダ認可(RFC 6960 §4.2.2.2 / §4.2.2.2.1)— validate_responder_authorisation(revocation/mod.rs:1129)。§11.5.3 で詳述する。
鮮度 — evaluate_freshness(ocsp.rs:335)。§11.5.4 で詳述する。
ノンス — match_nonce(ocsp.rs:407)。§11.5.5 で詳述する。
署名 — verify_response_signature(ocsp.rs:455)は tbsResponseData を再エンコードし、共有の verify_with_alg ディスパッチャを介してレスポンダ証明書の SPKI に対してそれを検証する。
結果は、最初に失敗したゲートが重大度で勝つゲート順序(revocation/mod.rs:862)によって集約される。すなわち、署名 → 認可 → ノンス → 鮮度 → CertStatus。サポートされないレスポンダ署名アルゴリズムは IndeterminateOcspMalformed を生む(アルゴリズムは実行されない — INDETERMINATE、OID は responder_signature_alg にそのまま表出する)。サポートされるアルゴリズムの検証が失敗すると、失効結果のレイヤでは IndeterminateOcspResponderUnauthorised を生み、最終的な TOTAL_FAILED の ocsp_responder_signature_invalid はインディケーション集約のはしご(優先順位は第5章 §5.7、Result Model は第14章)によって決定される。すべてのゲートを通過した後にのみ CertStatus がマップされる。すなわち、good → GoodOnOcsp、revoked → RevokedOnOcsp(失効時刻+理由を伴う)、unknown → IndeterminateOcspUnknown。
validate_responder_authorisation(revocation/mod.rs:1129)は RFC 6960 のケース分岐を実装する。
ケース (a) — 発行者が署名する(is_issuer_signs、ocsp.rs:531): レスポンダ証明書のサブジェクトDNがリーフの発行者DNと等しい — CA がその自身の OCSP レスポンスに署名する。結果は IssuerSigns。
ケース (b) — 指定署名者: 3つの条件を要求する。
build_responder_chain_outcome(revocation/mod.rs:1261)は、署名者チェーンと同じ crate::path::validate_path RFC 5280 §6 エンジンを再利用し、レスポンダ自身を除く証明書プールを中間証明書とする。アンカーされないチェーン → ChainNotAnchored。id-kp-OCSPSigning EKU(1.3.6.1.5.5.7.3.9、responder_has_ocsp_signing_eku、ocsp.rs:499)を持つ。欠落 → MissingOcspSigningEku。id-pkix-ocsp-nocheck(1.3.6.1.5.5.7.48.1.5、responder_has_nocheck、ocsp.rs:512)を持つ場合、pverify はレスポンダ証明書自身の失効を再帰的にチェックしてはならない(MUST NOT)。nocheck を持たず、かつレスポンダ証明書が何らかの失効ポインタ(CRL DP または AIA OCSP)を持つ場合、pverify は際限のない OCSP-of-OCSP の再帰に入るのではなく ResponderRevocationCheckRequiredButNotSupported で拒否する(revocation/mod.rs:1196)。レスポンダ証明書は ResponderId によって特定される(locate_responder_cert、revocation/mod.rs:1211)。すなわち、ByName は候補のサブジェクトDNを照合し、ByKey は候補の SPKI subjectPublicKey の生ビットの SHA-1 を照合する。候補はまず response.certs(レスポンダ自身の埋め込み主張、最も権威あるソース)から、次いでホスト供給の証明書プールから引き出される。指定署名者のレスポンダのチェーン検証結果は OcspAttempt.responder_chain_outcome フィールドに表出し、監査者はレスポンダ自身のチェーンを見ることができる。
evaluate_freshness(ocsp.rs:335)は (producedAt, nextUpdate) × モード × ソース を OcspFreshness の結果へマップする。
| 条件 | 結果 | 注記 |
|---|---|---|
source == embedded_ocsp_vals |
ArchivalTimeFresh |
SignedData にかかる archive-TS-v3 のインプリントが完全性のアンカーであり、レスポンダのウィンドウではない(FR-076 の例外規定) |
producedAt > verification_time |
ProducedAfterVerificationTime |
レスポンダのクロックスキュー。モードに関わらず拒否される |
nextUpdate < verification_time(online/offline) |
Stale |
レスポンスが VRT に対して期限切れである |
nextUpdate < verification_time(from_bundle) |
FromBundleStorage |
バンドルストレージが完全性のアンカーである(§II 再現性) |
nextUpdate なし、online + AIA 取得、producedAt が 24 時間以内 |
Fresh |
RFC 6960 §4.2.2.1 — nextUpdate なしは「ステータスは常に最新」を意味する。ライブ取得に対して 24 時間ウィンドウを受理する(035-ocsp-client 修正) |
nextUpdate なし(その他の online/offline) |
NoNextUpdate |
FR-067 の悲観的ポリシーにより拒否される |
nextUpdate なし(from_bundle) |
FromBundleStorage |
例外規定 |
producedAt ≤ verification_time ≤ nextUpdate |
Fresh |
受理される |
3つの例外規定が強調に値する。埋め込みレスポンスは常に ArchivalTimeFresh である。なぜなら、ETSI の LT/LTA モデルでは、埋め込み失効証拠を保護するのは — レスポンダ自身の nextUpdate ではなく — SignedData にかかるアーカイブタイムスタンプだからである。nextUpdate が欠落または過去であるfrom-bundle のレスポンスは Stale ではなく FromBundleStorage である。なぜなら、バンドルのコンテンツアドレス指定ストレージが完全性のアンカーであり、レスポンダ自身のウィンドウがその後どれほど陳腐化したかに関わらず、監査者の判定はその後のあらゆる実行にわたり再現可能でなければならないからである。ライブ AIA 取得レスポンスで nextUpdate なしのもの(例: MOJ CRPKI 商業登記 OCSP レスポンダ)は、producedAt が verification time の 24 時間以内であれば RFC 6960 §4.2.2.1 に従って Fresh として受理される — 同標準は nextUpdate なしはステータスが常に最新であることを明示している。これらの例外規定以外の online および offline モードでは、陳腐化または nextUpdate なしのレスポンスは拒否される。CRL のケースと同様に、ここでの verification_time は、レスポンスがその失効を証する証明書のオブジェクトごとのVRTである(031-vrt-per-object)。
Stale、ProducedAfterVerificationTime、NoNextUpdate はいずれも失効結果 IndeterminateOcspStale(revocation/mod.rs:937)にマップされる。
match_nonce(ocsp.rs:407)は次の規律を適用する。
source == embedded_ocsp_vals → SkippedEmbedded(署名者やアーカイブ者は将来の検証器のノンスを予測できなかった)。mode == from_bundle または mode == offline → SkippedFromBundle(アーカイブ時に捕捉されたバンドルレスポンスは異なるノンスを持つ。バンドルストレージが完全性のアンカーである — 同じ規律が、pverify がノンスを生成しなかったオフラインにも適用される)。derive_nonce(...)(revocation/mod.rs:804)である。ノンス拡張のないレスポンス → NotPresentOnline、存在するが異なるノンス → Mismatched、等しいノンス → Matched。Mismatched と NotPresentOnline はいずれも IndeterminateOcspUnknown(revocation/mod.rs:925)へルーティングされる — pverify は自身のリクエストと相関できないオンラインレスポンスを拒否する。
OCSP チャネルが参照される場合、リーフごとの OcspAttempt(crates/pverify-core/src/report/mod.rs:1490)が RevocationRecord 上に表出する。これは、source、responder_uri(または文字どおりの "embedded")、順序付けられた attempted_uris、レスポンダのサブジェクトDN、producedAt(RFC 6960 のワイヤ名に合わせて camelCase を保持)、レスポンダ署名アルゴリズム OID、および freshness/nonce_match/responder_authorisation/response_status の列挙判定を、そのまま記録する。OcspResponseStatus 列挙(report/mod.rs:1445)は RFC 6960 §4.2.1 の successful/malformedRequest/internalError/tryLater/sigRequired/unauthorized を反映し、加えて pverify 内部の2つの疑似ステータス、malformed(DER デコード失敗)と unreachable(トランスポート失敗/バンドルミス)を持つ。response_sha256(016-diagnostic-data)は参照されたレスポンスバイト列をダイジェストで固定する。
035-ocsp-retry 追加(スキーマ 1.8.0): AIA 取得の response_status == "unreachable" の場合、試行レコードには追加で request_der_hex — コアがその URI 向けに構築した OCSPRequest の hex エンコード DER — が含まれる。ブラウザ JS ホストは第1パスのレポートからこのフィールドを読み取り、/ocsp Cloudflare Pages Function に POST し、プロキシ取得したレスポンスを第2パスでコアに供給する。これによりブラウザの OCSP ギャップが解消される。コアの失効パイプラインは両パスで同一に実行され、ホストはただバンドル参照と同様に事前取得した材料を提供するだけである。request_der_hex は埋め込みレスポンス・バンドル参照・取得成功ケースでは省略される — リトライパスに至らないあらゆるレポートにおいてバイト同一性が保持される。
pverify が OCSP チャネルを参照しなかった場合、この試行は完全に省略される — バイト単位の同一性の不変条件は、CRL で good なチェーンが ocsp_attempt を持たないことを意味する。
リーフが AIA OCSP URI を持ち、バンドルなしで --offline 検証された署名は、かつて捏造された OcspAttempt(response_status = unreachable、供給されたことのないバンドルに対する bundle_lookup_miss、および一度もなされなかった responder_authorisation 判定)を伴って INDETERMINATE / ocsp_responder_unreachable を報告していた。これは事実として不誠実であった。オフラインモードはソケットを開かないため、どのレスポンダも接触されなかった — 憲章 §I への直接の違反である。
修正は run_ocsp_pipeline(revocation/mod.rs:662)にある。埋め込みの ocspVals ループが実行された後(埋め込みレスポンスはネットワーク接触でなく、オフラインでもなお尊重される)、そしていかなる AIA 取得の試行の前に、パイプラインは Offline モードでショートサーキットする。
if matches!(mode, VerificationMode::Offline) {
return RevocationRecord { outcome: IndeterminateRevocationOffline,
ocsp_attempt: None, /* cdp_entries, crl_used preserved */ };
}
結果は、新しいクローズドな列挙 RevocationOutcome::IndeterminateRevocationOffline(report/mod.rs:822)を、ocsp_attempt を一切持たずに保持する — 消費者は試行が存在しないことを「参照されなかった」と読む。CDP 側の証拠(cdp_entries、crl_used)は保持され、監査者は CRL チャネルで何が試行されたかをなお見ることができる。インディケーション集約のはしごは、これを専用のサブインディケーション revocation_not_checked_offline(verify.rs:2752)へマップし、ocsp_responder_unreachable の主張と区別する。
この修正は精確にスコープされている。FromBundle(真のバンドルミスが正当に bundle_lookup_miss = true + unreachable を生む)と Online(真の一時的タイムアウトが IndeterminateOcspUnreachable を生む)は変更されない(revocation/mod.rs:674)。ゼロエグレスの保証は回帰ロックされている。すなわち、オフラインパスはソケットを開かない。これは、埋め込みCRLのフォールスルー時に、オフラインの LT 署名にそれ自身の署名内情報からなお検証させるのと同じ誠実性の規律である — オフラインが禁じるのは接触を捏造することであり、すでに手元にある情報を消費することではない。
RevocationOutcome の語彙ステップごとの失効結果は、クローズドな列挙 RevocationOutcome(crates/pverify-core/src/report/mod.rs:768)である。これは、署名者チェーン、TSA チェーン(033)、および PAdES の /DSS//VRI を出所とするチェック(034)によってそのまま再利用される単一の語彙である — ブランチ 034 は新しい結果値をゼロ追加し、プロビナンスタグのみを追加した。値を追加することは追加的な MINOR スキーマバンプである(FR-021a)。レガシーの IndeterminateOcspOnly は、保存されたレポートのデシリアライズのためにワイヤ列挙に保持されるが、現行のバイナリによって発行されることは決してない。
| 結果 | 重大度/インディケーション | サブインディケーション(集約経由、第5章 §5.7/第14章) |
|---|---|---|
GoodOnCrl |
TOTAL_PASSED への寄与 | — |
GoodOnOcsp |
TOTAL_PASSED への寄与 | — |
RevokedOnCrl |
TOTAL_FAILED | signer_certificate_revoked |
RevokedOnOcsp |
TOTAL_FAILED | signer_certificate_revoked_via_ocsp |
IndeterminateNoCrl |
INDETERMINATE | crl_stale_no_fresher |
IndeterminateStaleCrl |
INDETERMINATE | crl_stale_no_fresher |
IndeterminateIndirectCrl |
INDETERMINATE | unsupported_indirect_crl |
IndeterminateUnsupportedProtocol |
INDETERMINATE | unsupported_cdp_protocol |
IndeterminateOcspUnknown |
INDETERMINATE | ocsp_unknown_status |
IndeterminateOcspMalformed |
INDETERMINATE | ocsp_unknown_status |
IndeterminateOcspStale |
INDETERMINATE | ocsp_response_stale |
IndeterminateOcspResponderUnauthorised |
INDETERMINATE(または署名が無効なら TF) | ocsp_responder_chain_unauthorised |
IndeterminateOcspUnreachable |
INDETERMINATE | ocsp_responder_unreachable |
IndeterminateRevocationOffline |
INDETERMINATE | revocation_not_checked_offline |
IndeterminateOcspOnly(レガシー) |
保持されるが発行されない | ocsp_only_revocation_pointers |
結果から ETSI サブインディケーションへのマッピングは、aggregate_etsi/インディケーションのはしご(verify.rs、TOTAL_FAILED の revoked ケースについては 2491 行目から、INDETERMINATE クラスタについては 2680 行目から)で行われ、集約の優先順位は第5章 §5.7 に、結果として得られる Result Model は第14章にある。監査者が依拠する決定的な性質はこうである。すなわち、TOTAL_FAILED を生む結果は RevokedOnCrl と RevokedOnOcsp の2つだけであり — これは肯定的で署名検証済みの失効言明である。状態を確定できないことはすべて — CRL なし、陳腐化した CRL、到達不能なレスポンダ、認可されていないレスポンダ、オフライン — 精確な理由を伴う INDETERMINATE であり、決して捏造された失敗ではない。これは失効チャネルに適用された「断定回避」の規律である。
完全性のため、また監査者の期待の境界を定めるために、以下を記す。
apply_revocations は非アンカーのステップのみを反復する(verify.rs:2087)。id-pkix-ocsp-nocheck を欠くがそれ自身の失効ポインタを持つ指定署名者のレスポンダは ResponderRevocationCheckRequiredButNotSupported を生む(§11.5.3)。good として使用されないが、不正な CRL 署名が TOTAL_FAILED を生むこともない — それは IndeterminateNoCrl である(§11.3.4)。/DSS//VRI 情報の失効プロビナンスは PAdES レイヤによってタグ付けされるのであり、意図的にプロビナンスに依存しない失効エンジン(revocation/mod.rs:517 の pdf_source: None)によってではない。第6章を参照。リリース: v1.3.0 · レポートスキーマ: 1.9.0
本章では、pverify が RFC 3161/5816 のタイムスタンプトークンをどのように検証するか、アーカイブタイムスタンプのメッセージインプリントをどのように再計算するか、タイムスタンプ局(TSA)証明書チェーンをどのように判定するか(ブランチ 033-tsa-revocation-consistency で着地した一貫した TSA 証明書の失効検証の挙動を含む)を規定する。そして本章の中心として、pverify-core::vrt エンジンが、ブランチ 031-vrt-per-object で導入された JNSA §4 の再帰的な外側カバー規則を通じて、署名者チェーン・署名者署名・各署名タイムスタンプ・各アーカイブタイムスタンプに対して、オブジェクトごとの「検証基準時刻(VRT)」(Validation Reference Time)をどのように導出するかを規定する。
ここで扱う内容は、pverify が生成するすべての長期(B-LT / B-LTA)判定の時間的な背骨である。パス検証機構(第9章を参照)と失効検証機構(第11章を参照)は「時刻でパラメータ化」されている。すなわち、これらは単一の OffsetDateTime を受け取り、その時刻における証明書の有効期間と失効の鮮度を判定する。VRT エンジンは、各オブジェクトを「どの」時刻で判定するかを決定するコンポーネントであり、タイムスタンプ検証器は、そのエンジンが消費する信頼できる時刻を生成するコンポーネントである。両者は密に結合しているため、まとめて文書化する。
以下の主張はすべて、crates/pverify-core/src/timestamp.rs、crates/pverify-core/src/vrt/(mod.rs、coverage.rs、promotion.rs)、crates/pverify-core/src/report/vrt.rs、crates/pverify-core/src/verify.rs 内の統合箇所、ならびに specs/031-vrt-per-object/ と specs/033-tsa-revocation-consistency/ 配下の仕様に基づく。
pverify のタイムスタンプ面は以下を実装する。
| 標準 | 条項 | pverify が行うこと |
|---|---|---|
| RFC 3161 | §2.4.1, §2.4.2 | TimeStampToken を CMS の SignedData として解析する。eContentType == id-ct-TSTInfo(1.2.840.113549.1.9.16.1.4)を要求する。TSTInfo をデコードする |
| RFC 3161 | §2.4.2 | TSA の CMS signedAttrs 署名と、TSTInfo コンテンツに対する messageDigest 属性を検証する |
| RFC 3161 | §2.4.1 | messageImprint.hashAlgorithm の下で hash(message_imprint_input) を再計算し、hashedMessage と比較する |
| RFC 5816 | (ESS 証明書バインディング) | 共有 CMS signed-attrs パス(第5章)を通じて ESS signing-certificate/v2 バインディングを尊重する |
| RFC 5652 / EN 319 122-1 §6.3 | CAdES archive-time-stamp-v3 |
§6.3.4 の正規インプリント入力を再計算して比較する(第5章) |
| EN 319 142-1 §5.4 / ISO 32000-2 §12.8.5 | PAdES /DocTimeStamp |
リビジョンの ByteRange に対するアーカイブタイムスタンプとして扱う |
| ETSI EN 319 102-1 | timestamp サブプロセス, REVOKED_NO_POE |
失効が確認された TSA 証明書は INDETERMINATE / revoked_no_poe に繰り下げる(033) |
| ETSI TS 101 903 v1.4.2 Annex A.1.5 | XAdES ArchiveTimeStamp インプリント |
ds:Signature の exc-C14N から現在・後続 ATS ノードセットを除く(036) |
| JNSA デジタル署名検証ガイドライン 第 1.1 版 §3.2 / §4 | 再帰的な外側カバー | オブジェクトごとの VRT エンジン(031) |
オブジェクトごとの VRT 規律は、JNSA ガイドラインのギャップレビュー — 長期署名の検証基準時刻が完全には適用されていない — に対処する(specs/031-vrt-per-object/spec.md の "Background")。一貫した TSA 証明書の失効検証の挙動は、JNSA ガイドラインのギャップレビューに対処する(specs/033-tsa-revocation-consistency/)。
本章が扱わないもの: §6.3.4 の archive-time-stamp-v3 正規入力の構築そのもの(第5章)、OCSP/CRL 評価の内部(第11章)、および RFC 5280 パス検証アルゴリズム(第9章)。本章はこれらをプリミティブとして消費し、時間的ロジックに集中する。
verify_timestamp_tokencrates/pverify-core/src/timestamp.rs::verify_timestamp_token は、トークンごとのエントリポイントである。すべての形式ハンドラ — CAdES、PAdES、XAdES、JAdES、ASiC 内部 — は、各 RFC 3161 トークンをこの単一の関数に通し、その結果を crate::report::TimestampToken(report/mod.rs:998)へ射影する。
pub fn verify_timestamp_token(
tst_bytes: &[u8],
message_imprint_input: Option<&[u8]>,
role: TimestampRole,
intermediates: &[Certificate],
anchors: &[TrustAnchor],
verification_time: OffsetDateTime,
) -> Result<TimestampToken, Error>これらのパラメータは以下の契約を担う(timestamp.rs:124)。
tst_bytes — 外側の ContentInfo(CMS 形状)。この関数は、encapContentInfo.eContentType が id-ct-TSTInfo(TST_INFO_OID、timestamp.rs:67)でない任意の入力を拒否し、RFC 3161 §2.4.2 に従って Error::Parse を返す。message_imprint_input — トークンがコミットすると主張するバイト列。Some(input) はインプリントの再計算を引き起こす。None は「構造表面」モードであり、正規インプリント入力を再構築していない(またはできない)アーカイブ呼び出し元、および最初の PAdES /DocTimeStamp パスで使用される。§12.3 を参照。role — TimestampRole::{SignatureTimestamp, ContentTimestamp, ArchiveTimestamp}(report/mod.rs:988)。これは、スタンプごとの imprint_record が設定されるか否かを駆動する(ArchiveTimestamp の場合のみ)。verification_time — TSA チェーンが判定される時刻。これがオブジェクトごとの VRT である。 このパラメータが vrt ではなく汎用名 verification_time を保持しているのは、まさにこの関数が形式非依存だからである。すなわち、渡される値がカバーする TS の GenTime であるか生の要求時刻であるかは呼び出し元が決定する(timestamp.rs:113-123)。これが 031 が差し込まれる継ぎ目である。verify_timestamp_token は、順に以下を実行する(timestamp.rs:132-319)。
SignedDataParser::parse が外側の CMS をデコードする。eContentType が id-ct-TSTInfo と照合される。不一致はハードな解析エラーである。TSTInfo デコード。 parse_tst_info(timestamp.rs:334)は、BER ウォーカーを介して TSTInfo SEQUENCE を位置的にたどり、messageImprint(hashAlgorithm OID + hashedMessage OCTET STRING)と genTime を抽出する。少なくとも最初の 5 フィールド(version、policy、messageImprint、serialNumber、genTime)を要求する。policy、serialNumber、accuracy、ordering、nonce、tsa、extensions はレポートに表出されない。parse_generalized_time(timestamp.rs:409)は YYYYMMDDhhmmssZ 形式(15+ ASCII バイト)を受理し、存在する場合は小数秒サフィックスをトリミングし、末尾の Z を欠く任意の値を拒否する(すなわち、ローカル時刻またはオフセット GeneralizedTime は拒否される — RFC 3161 TSA は UTC Z を発行する)。これは意図的に厳格なデコーダである。reconstitute_certificate_der、timestamp.rs:324)、解析される。TSA リーフは最初の非 CA 証明書(basic_constraints.is_none_or(|bc| !bc.ca))であり、これが見つからない場合は最初の証明書にフォールバックする。残りの埋め込み証明書は TSA 中間証明書となる(timestamp.rs:170-181)。解析可能な証明書を持たないトークンは解析エラーである。verify_signed_attrs は TSA の signedAttrs に対する署名を確認し、verify_content_digest は messageDigest 属性が TSTInfo eContent のダイジェストと等しいことを確認する。2 つのブール値 signature_ok と message_digest_ok が最終的な message_imprint_match フラグを制御する。両方を検証することで、signedAttrs が誤った eContent を主張する TSA を排除する(timestamp.rs:183-195)。message_imprint_input == Some(_) の場合のみ)。§12.3 を参照。validate_path(第9章)が、TSA リーフ + 中間証明書を verification_time においてアンカーに対して実行する。このとき空の user-initial-policy-set(&[] = {anyPolicy})を用いる。RFC 3161 §2.4 は TSA の受理を署名者のポリシー姿勢に結び付けないため、TSA チェーンは --required-policy とは独立に検証される(timestamp.rs:267-277)。crate::verify::wire_chain_step_signatures が、各チェーンステップの証明書署名を RustCrypto で検証し、各ステップを Verified / NotApplicableForRoot とマークする(timestamp.rs:284)。このステップがなければ、TSA チェーンステップは validate_path からの構造的なプレースホルダーのみを担うことになる。message_imprint_match のセマンティクス最終的なワイヤフィールド message_imprint_match は imprint_check && signature_ok && message_digest_ok に設定される(timestamp.rs:313)。これは単なるインプリント比較ではなく、3 つの事実の連言である。
Some(input) の呼び出し元では、imprint_check は再計算ダイジェストと宣言ダイジェストの比較である。None の呼び出し元(構造表面)では、imprint_check はデフォルトで true となる(timestamp.rs:258)ため、このフィールドは TSA の自己一貫性(署名 + messageDigest)のみを反映する。フィールド名はこの点について誠実である。すなわち、TSA 署名が検証された None モードのトークンは「内部的に一貫しており、埋め込まれた TSA によって署名されている」ものであり、これはまさに下流の VRT エンジンが token_internally_valid として扱うものである(§12.5)。TSA 署名が失敗したトークンは、たとえインプリントが一致していても message_imprint_match == false を報告する。なぜなら、署名が失敗した TST は何も確立していないからである(timestamp.rs:305-312)。下流のサブインディケーション層(第5章 §5.7)は false を「この TST は INDETERMINATE」として扱う。
断定回避に関する注記。
verify_timestamp_tokenはResultを返す。不正な形式のトークン(Err)は、失敗したトークンを捏造するのではなく、呼び出し元によって破棄される。破棄されたトークン、またはfalseフラグが付いたトークンから流れる判定は INDETERMINATE であり、偽造トークンによる TOTAL_FAILED ではない。これがタイムスタンプに適用された 憲章 §I である。
message_imprint_input == Some(input) の場合、検証器は messageImprint.hashAlgorithm に宣言されたアルゴリズムの下で hash(input) を再計算し、hashedMessage とバイト単位で比較する(timestamp.rs:211-259)。
imprint_alg = HashAlg::from_oid_bytes(tstInfo.hashAlgorithm)
match imprint_alg {
Some(alg) => matched = (compute_digest(alg, input) == tstInfo.hashedMessage)
None => matched = false // unsupported algorithm — cannot recompute
}
サポートされるダイジェストのカタログは SHA-1 / SHA-256 / SHA-384 / SHA-512 である(HashAlg::from_oid_bytes)。未知の OID は matched = false を生じ、ArchiveTimestamp の呼び出し元に対しては ImprintOutcome::UnsupportedAlgorithm { oid } レコードを生じる。すなわち pverify は、誤った不一致ではなく、再計算できなかったことを報告する。
role == ArchiveTimestamp の場合、比較はトークンに埋め込まれた構造化された ArchiveTimestampImprintRecord(report/mod.rs:1323)に記録される。
| フィールド | 意味 |
|---|---|
imprint_algorithm_oid |
インプリントハッシュアルゴリズムのドット区切り OID |
declared_digest |
tstInfo.hashedMessage の16進表現 |
recomputed_digest |
compute_digest(alg, input) の16進表現(アルゴリズムが非サポートの場合は空) |
outcome |
ImprintOutcome::{Match, Mismatch, UnsupportedAlgorithm{oid}} |
signature_timestamp および content_timestamp ロールは、設計上インプリントレコードを持たない(timestamp.rs:232、242-254)。これらのカバー対象は署名値であって再計算された正規入力ではない。スタンプごとのレコードは、アーカイブ TS のインプリント再計算(CAdES-LTA における §6.3.4 入力、第5章)のために予約されている。集約された LongTermArchivalIndication(report/mod.rs:1051)は、CAdES ハンドラによってスタンプごとの ImprintOutcome から畳み込まれる(verify.rs:913-934)。いずれかが不一致 → ArchiveTimestampImprintMismatch、そうでなければいずれかが非サポート → ArchiveTimestampImprintUnsupportedAlgorithm、そうでなければ ArchiveTimestampVerified。アーカイブのない署名は NoArchiveTimestamp である。
Mismatchは、アーカイブタイムスタンプが、それがカバーすると主張する成果物をカバーしていないことの直接的な証拠である。CAdES ハンドラは、aggregate_etsiにおけるarchive_timestamp_imprint_mismatchの優先順位を通じて、これを TOTAL_FAILED へとエスカレートする(第5章 §5.7)。UnsupportedAlgorithmは INDETERMINATE である(断定回避)。
ブランチ 036-xades-blta(PR #125)は、ETSI TS 101 903 v1.4.2 Annex A.1.5 で規定された xades:ArchiveTimeStamp のインプリント検証サポートを追加した。XAdES ATS のインプリントは CAdES の archive-time-stamp-v3 入力と構造的に異なる。
インプリント入力。 インプリントは ds:Signature 要素全体の Exclusive C14N(exc-C14N、RFC 3741)であり、特定のノードセット減算を伴う。文書順において現在の ATS より後に現れる xades:ArchiveTimeStamp 要素はすべて正規化前のノードセットから除外される。この「非循環」構造により、各 ATS は、その ATS が追加される直前の署名状態にコミットし、その時点でまだ存在しなかった後続の ATS エントリを含まない。
imprint_input = exc_c14n(ds:Signature ノードセット \ {ATS[k], ATS[k+1], …, ATS[n]})
^^^^^^ 現在の ATS インデックス k および後続のすべての ATS
これは crates/pverify-xades/src/blta.rs において bergshamra-c14n クレートの NodeSet subtract API を用いて実装されている — XAdES が既に使用するもの以外の新規依存なし。
共有データ構造。 ArchiveTimestampImprintRecord(§12.3)と LongTermArchivalIndication(§12.5 / report/mod.rs:1051)は CAdES と XAdES の間で共有される。XAdES ハンドラ(verify_xades)は各 xades:ArchiveTimeStamp トークンに同じ imprint_record を設定し、スタンプごとの ImprintOutcome を CAdES と同一の LongTermArchivalIndication 値へ畳み込む。いずれかが不一致 → ArchiveTimestampImprintMismatch、そうでなければいずれかが非サポート → ArchiveTimestampImprintUnsupportedAlgorithm、そうでなければ ArchiveTimestampVerified。
036 のスコープ内。 単一の xades:ArchiveTimeStamp、または各 ATS が現在・後続の ATS を減算する非循環チェーン。フィクスチャ: 合成の build_blta / build_blta_tampered(fixtures/ 下)、2 つの xades_blta_e2e テストが PASS。
スコープ外。 ArchiveTimestampV3(CAdES 連結方式)、複数 ATS の連鎖インプリントループ、JAdES arcTst、および DSS XAdES-LTA フィクスチャ(ATS 署名データ内に XPath Transform を適用しており、pverify の exc-C14N インプリント再計算では再現できないため expectations.json から除外)。
ブランチ 031 以前は、VerificationRequest の下流のすべてのチェックが同じ request.verification_time を受け取っていた — 署名者パス検証、アーカイブ TS 検証、verify_timestamp_token 内の TSA チェーン、および PAdES/XAdES/JAdES の類似物(specs/031-vrt-per-object/spec.md の "Current pverify behaviour" に列挙)。これは長期署名にとって誤りである。
JNSA の典型的な失敗を考える(US1、spec.md 41-53 行)。署名者証明書が 2023-06-01 … 2025-06-01 の間有効で、2024-03-01 に発行された署名タイムスタンプの下で署名され、本日 --at = 2026-06-20 で検証される CAdES-LTA を想定する。証明書は要求時刻には失効(有効期限切れ)しているが、信頼できるタイムスタンプが署名の存在を証明する時刻には有効だった。署名者チェーンを --at で判定すると INDETERMINATE / signing_certificate_expired を報告する。署名タイムスタンプの GenTime で判定すると正しく TOTAL_PASSED を報告する。JNSA デジタル署名検証ガイドライン 第 1.1 版 §3.2 / §4 は、各オブジェクトがそのオブジェクトをカバーするタイムスタンプ由来の時刻で判定されることを義務付けている。
VRT エンジンは、1 つの署名に対して 4 つの(系統の)基準時刻を計算する。
各オブジェクトの VRT は、再帰的に次の外側のカバーするタイムスタンプの GenTime である。最も外側のオブジェクトは request.verification_time にフォールバックする。署名者チェーンと署名者署名は最も内側のカバーされるオブジェクトである。署名タイムスタンプがそれらをカバーする。アーカイブタイムスタンプが署名タイムスタンプをカバーする。連鎖したアーカイブタイムスタンプは互いをカバーする。エンジンは、選択されたすべての時刻とそれを選択した規則を signatures[i].vrt に表出する。これにより検証者(信頼当事者)は、ソースを読まずに「どの時刻が何を判定したか」を監査できる(憲章 §I)。
vrt/coverage.rs)エンジンは、解析されたタイムスタンプトークンを内部の CoveringGraph(vrt/coverage.rs:140)— outer_cover エッジを持つ CoveringNode のツリー状 DAG — に射影する。これらの型はいずれもワイヤを越えない。シリアライズされるのは crate::report::vrt のみである。
CoveringKind(coverage.rs:38)が各ノードにタグを付ける。
| 種別 | 出所 |
|---|---|
SignerSignature |
署名者署名値(最も内側のカバーされるオブジェクト)。署名ごとに1つ、インデックス0の擬似ノード |
SignatureTimestamp |
CAdES signature-time-stamp / xades:SignatureTimeStamp / JAdES sigTst |
CadesAtsv3 |
CAdES archive-time-stamp-v3 |
PadesDocTimestamp |
PAdES /DocTimeStamp |
XadesArchiveTimestamp |
xades:ArchiveTimeStamp |
JadesArchiveTimestamp |
JAdES arcTst |
各 CoveringNode(coverage.rs:93)は、繰り上げゲートが必要とするデータを担い、そのすべてが VRT 非依存である。ノードフラグは、既に検証済みの TimestampToken から読み取られ、決して再導出されない。
gen_time — TST の GenTime(署名者擬似ノードの場合はセンチネル UNIX_EPOCH)。token_internally_valid — ts.message_imprint_match(§12.2.3)から射影される。TSA 署名 + messageDigest +(該当する場合)インプリントがすべて一貫していたこと。imprint_status: Option<bool> — アーカイブインプリントレコードからの Some(true/false)(ImprintOutcome::Match)。インプリントレコードを持たない署名 TS / 署名者ノードでは None。tsa_chain_anchored — chain_is_anchored(&ts.tsa_chain_outcome)(coverage.rs:440)。チェーンが空でなく、すべてのステップの署名が Verified または NotApplicableForRoot であること。これは aggregate_etsi が TSA 繰り上げに用いる述語と一致する。from_timestamps)5 形式すべてが 1 つのビルダー from_timestamps(coverage.rs:338)を共有し、出所タグとアーカイブノード種別によってパラメータ化される。形式ごとのコンストラクタ from_cades / from_pades / from_xades / from_jades(および ASiC。これは内部署名が CAdES の場合は CAdES 形状にルーティングされる — 内部 XAdES は verify_xades を通じて検証され、これは自身の出所を Xades とタグ付けする、mod.rs:160-170)は、アーカイブノードがどの CoveringKind を担うかにおいてのみ異なる。
ビルダーはカバーエッジを構築する(coverage.rs:346-429)。
SignerSignature 擬似ノードである。archive_timestamps[0] にパッチされ、存在しない場合は None となる。archive[i] の外側カバーは archive[i+1] である(後のトークンほど早いものをカバーする)。最も外側のアーカイブ TS は outer_cover == None を持つ。signature_timestamp[0]、なければ存在すれば archive_timestamp[0]、それもなければ None である。「最も外側を最後にする」順序は抽出器に対する契約である。CAdES は archive-time-stamp-v3 未署名属性をネスト順に発行する。PAdES /DocTimeStamp は ByteRange 順であり、後のものほどすべての早い PDF リビジョン(早い /Sig および /DocTimeStamp エントリを含む)をカバーする — coverage.rs:245-271。
PAdES のカバー形状は CAdES と構造的に同一である。唯一の違いはアーカイブノード種別であり、これがワイヤ形式の kind: pades_doc_timestamp 判別子と doc_timestamp 導出タグを駆動する。JAdES-B-B は退化したケースである。空の etsiU 配列は署名者擬似ノードのみを含むグラフを生じるため、署名者 VRT は request_at にフォールバックする(coverage.rs:302-325)。
vrt/promotion.rs)derive_promotion_time(promotion.rs:147)は、1 つのノードに対して (time, derivation, demotion_reason) を計算する。連鎖した ATSv3 のウォークが O(N) に保たれるよう BTreeMap キャッシュ上でメモ化されており、訪問済み集合上のサイクル検出とともに深さ 8 で上限が設けられている(COVERING_DEPTH_BUDGET、promotion.rs:40)。深さ上限とサイクル検出は、明示的な anti-DoS / anti-forgery 措置である。すなわち「整形式の入力で深さ予算を使い果たす唯一の方法は偽造されたサイクルである」(promotion.rs:37-54)ため、両方の使い果たし経路は VrtDemotionReason::CoveringCycleDetected にマップされる。
is_valid_for_promotion(promotion.rs:71)は、外側のノードが内側のノードの時刻を繰り上げてよいかを制御する。あるノードが有効な繰り上げ元であるのは、以下を満たす場合かつその場合に限る(promotion.rs:85-102)。
Ok(false) を返す)。gen_time <= request_at(TS が検証要求より後の日付でないこと。これは未来日付の TS が判定を遡及日付化することを防ぐ — リスク R-2)。token_internally_valid が真である(TSA 署名 + messageDigest OK)こと、かつ imprint_status != Some(false)(アーカイブインプリントが、再計算された場合、一致した)こと。tsa_chain_anchored が真であること。FR-002a の条項 4 および 5 — TSA 証明書が次の外側カバー時刻において有効であること、および TSA チェーンの失効がその時刻において鮮度を保っていること — は、意図的にエンジン内では評価されない(promotion.rs:22-27)。エンジンは純粋計算であり、いかなる証明書ストアからも自由である。条項 4-5 は呼び出し箇所で実現される。そこでは validate_path と apply_revocations が、このエンジンが渡す VRT において TSA チェーンを再判定する(§12.8)。この分離が、VRT エンジンを決定論的で、I/O を行わず、WASM クリーンに保つものである。
derive_inner(promotion.rs:167)がウォークを実装する。
derive(node):
if cached(node): return cached
if depth == 0 or node already seen: return (request_at, fallback_invalid, CoveringCycleDetected)
outer = outer_cover_of(node)
if outer is None: # outermost
return (request_at, RequestAt, None)
if is_valid_for_promotion(outer):
result = (outer.gen_time, derivation_for_kind(outer.kind), None)
else:
result = (request_at, fallback_invalid, diagnose_ineligible(outer))
# recurse into outer's OWN vrt — clause-4 input for the call-site gate
outer_vrt = derive(outer)
if outer_vrt demoted as cycle and result was clean:
result = (request_at, fallback_invalid, CoveringCycleDetected) # propagate
return result
PKI 監査者が留意すべき 2 つの微妙な点がある。
promotion.rs:263-276): node の直接の外側が適格である場合でも、エンジンは外側自身の VRT へ再帰する。その再帰がサイクルを表面化させた場合、繰り上げは撤回される。内側のオブジェクトは、自身の時刻がそれ自体サイクルにより破綻しているカバー時刻への繰り上げを信頼できない。promotion.rs:235-240): SignerSignature 種別の外側から Ok(false) が来る唯一の方法は、署名者が TS をカバーすると主張する不正な形式の入力である。これは CoveringCycleDetected として刻印される。候補となるカバー TS が不適格な場合、diagnose_ineligible(promotion.rs:109)は外側のフラグから、優先順位に従って理由を合成する: FutureGentime → ImprintMismatch → TokenParseFailed → TsaChainUnanchored →(最終手段)TsaCertInvalidAtOuterCover。完全なクローズドな列挙(閉じた語彙)VrtDemotionReason(report/vrt.rs:247)は以下のとおり。
| バリアント | ワイヤ文字列 | FR-002a 条項 |
|---|---|---|
FutureGentime |
future_gentime |
条項 1 |
ImprintMismatch |
imprint_mismatch |
条項 2 |
TokenParseFailed |
token_parse_failed |
条項 2 |
TsaChainUnanchored |
tsa_chain_unanchored |
条項 3 |
TsaCertInvalidAtOuterCover |
tsa_cert_invalid_at_outer_cover |
条項 4 |
TsaRevocationStaleAtOuterCover |
tsa_revocation_stale_at_outer_cover |
条項 5 |
CoveringCycleDetected |
covering_cycle_detected |
リスク R-2 の緩和 |
derivation_for_kind マップ(promotion.rs:283)はワイヤの derivation タグを選択する: SignatureTimestamp → signature_timestamp、CadesAtsv3 / XadesArchiveTimestamp / JadesArchiveTimestamp → covering_archive_timestamp、PadesDocTimestamp → doc_timestamp。covering_archive_timestamp と doc_timestamp の分離により、消費側は、出所となる TS エントリの種別を検査することなく、CMS 未署名属性の時間アンカと PDF リビジョンの時間アンカを区別できる。
derive_vrt エントリポイントとワイヤ形状derive_vrt(vrt/mod.rs:84)は、公開された純粋計算のエントリである。これは SignatureInputs の借用(mod.rs:45)— 出所タグ、request_at、および並行する timestamps[] インデックス付きの署名/アーカイブトークンスライス — を受け取り、crate::report::vrt::Vrt ブロックを返す。I/O を行わず、クロックを読まず、トラストストアを参照しない。FR-011a は request_at が inputs[].at とバイト単位で等しいことを保証する(mod.rs:18-21、82-83)ため、同じ入力は常に同じ VRT レコードを生じる(憲章 §II 再現性)。ユニットテスト derive_vrt_is_byte_deterministic(mod.rs:231)がこれを固定している。
エンジンは署名者の繰り上げ時刻を一度導出し、それを signer_chain と signer_signature の両方にコピーする(両者は最も内側のカバーを共有する、mod.rs:99-106)。次に、すべての非署名者ノードをウォークして signature_timestamp[] と archive_timestamp[] 配列を埋める(mod.rs:111-145)。1 つの形式間の非対称性に注意されたい。XAdES と JAdES のアーカイブ TS ノードは、ワイヤ形式の archive_timestamp[] 配列からスキップされる(mod.rs:135-142)。それらの VRT は derivation フィールドを介して、形式固有のカバーレコードに表出され、ワイヤの kind 判別子を cades_atsv3 / pades_doc_timestamp の 2 値に閉じたまま保つ(Q1、FR-010)。
Vrt ブロック(report/vrt.rs)Vrt(report/vrt.rs:31)はすべての SignatureEntry に存在する。内側の配列は、カバーするタイムスタンプがない / 失効の証拠が適用されない場合に空となる(FR-005)。
signatures[i].vrt = {
signer_chain: { value, derivation },
signer_signature: { value, derivation },
signature_timestamp: [ { value, derivation, timestamp_index, timestamp_role,
demotion_reason?, predates_request_at } ],
archive_timestamp: [ { ...as above..., kind: cades_atsv3 | pades_doc_timestamp } ],
revocation_evidence: [ { value, derivation, source_kind, source_index, attests } ]
}
| フィールド | 型 | 備考 |
|---|---|---|
value |
RFC 3339 時刻 | 選択された基準時刻 |
derivation |
VrtDerivation |
request_at / signature_timestamp / covering_archive_timestamp / doc_timestamp / request_at_fallback_invalid_timestamp |
timestamp_index |
usize | signatures[i].timestamps[] への逆参照。検証者(信頼当事者)が VRT エントリと、それが判定するトークンを相関付けられるようにする |
timestamp_role |
TimestampRole |
grep しやすいフィルタリングのために複製される |
demotion_reason |
Option<VrtDemotionReason> |
候補のカバー TS が繰り下げられた場合のみ存在する。成功時には省略される |
predates_request_at |
bool | FR-008: gen_time > request_at(TS エントリに設定される、mod.rs:117) |
kind |
ArchiveTimestampKind |
アーカイブエントリのみ |
VrtDerivation(report/vrt.rs:207)はクローズドな列挙である。値の追加は追加的な MINOR のスキーマバンプである。serde のワイヤ文字列はラウンドトリップテスト(report/vrt.rs:291)によって固定されている。このブロックは schema_version 1.2.0 → 1.3.0(031)で導入され、スキーマはその後 1.9.0(036)まで進んだ。Vrt::request_at_fallback() のデシリアライズデフォルト(report/vrt.rs:54)は、格納された 1.3.0 以前のレポートが現行バイナリで再生されるとき、UNIX_EPOCH センチネルを report.verification_time で代替するため、古いレポートは 2 度目のクロック読み取りなしに誠実にラウンドトリップする。
VRT エンジンは時刻を計算するのみである。呼び出し箇所がそれを適用する。CAdES を参照(verify.rs:748-901)として取ると、そのフローは 2 パス設計である。
すべての TST は request.verification_time で一度検証される(アーカイブ TS については verify.rs:733-746、署名 TS については類似の早いループ)。決定的な性質は、エンジンのカバーグラフ入力 — gen_time と imprint_record — がVRT 非依存であることである。したがって第1パスは、TSA チェーンに使用される検証時刻にかかわらず、それらを正しく生成する(verify.rs:748-754)。
derive_vrt は source = CoveringSource::Cades、request_at = request.verification_time、およびインデックス付きの設定済みトークンスライスとともに呼び出される(verify.rs:757-764)。PAdES は CoveringSource::Pades で同じことを行う(pades/mod.rs:899-910)。
verify.rs:775-807): vrt.signer_chain.value != request.verification_time の場合、署名者パスは VRT で再検証される — validate_path → wire_chain_step_signatures → 弱アルゴリズムのフラグ付け → apply_revocations、すべて VRT において。等しいときにスキップするガードはバイト単位の同一性の最適化である。同じ時刻で再検証しても同一の出力を生じるであろうし、既存のチェーン結果は既に層化されたフラグを担っている。verify.rs:816-833)と各アーカイブ TS(verify.rs:834-857): その VRT が request_at と異なる場合、トークンは VRT(FR-002a 条項 4 に従い、その次の外側カバー TS の GenTime)で再検証され、第1パスのトークンを置き換える。これが、条項 4(外側カバーにおける TSA 証明書の有効性)が実際にチェックされる箇所である — 再検証の内部の validate_path によって。pades/mod.rs のパスは構造的に同一である(pades/mod.rs:911-919 の、vrt_block.signer_chain.value != verification_time でガードされた署名者再検証)。
この 2 パス設計 — 一度導出し、各オブジェクトをそれ自身の時刻で再判定する — は、証明書ストアに依存する条項 4-5 を、ストアとフェッチャを所有する呼び出し箇所へ移しつつ、VRT エンジンを純粋に保つ。決定的に重要なこととして、失効/アルゴリズム層はカバーグラフや任意の VRT を決して再導出または変更しない(FR-005a)。VRT は一度読み取られ、下流へスレッドされる。
ブランチ 033 以前は、署名者チェーンは失効検証されていたが、タイムスタンプ内の TSA チェーンは構造的に表出されるのみだった — verify_timestamp_token 自体は失効に一切触れない(timestamp.rs:41-50)。XAdES は既に TSA チェーンの失効検証を適用していたが、CAdES、PAdES、JAdES、ASiC は適用しておらず、これが JNSA ガイドラインのギャップレビューが指摘した不整合だった。ブランチ 033 は 1 つの共有ヘルパー apply_tsa_chain_revocation_for_token(verify.rs:1552)を導入し、これをすべての形式ハンドラが各タイムスタンプオブジェクト自身の VRTで呼び出す。
apply_tsa_chain_revocation_for_token(verify.rs:1552-1603):
token.tsa_subject_dn_text に対する RFC 4514 のサブジェクト DN 照合により、埋め込み証明書の中から TSA リーフを特定する。残りの埋め込み証明書が中間証明書である。リーフが見つからない場合、関数は変更なしに戻る — プレースホルダーのチェーンがそのまま残り、判定不能(undeterminable)として表出される(FR-006「TSA リーフの欠落」)。vrt_time)で再検証し、ステップ署名を配線し、次に署名者チェーンが使用するのと同じ (embedded_ocsp_der, embedded_crls_der, mode) チャネルで apply_revocations を呼び出す(FR-006a — TSA チェーンは各検証モードの下で署名者チェーンと同一に扱われる)。ChainResult で token.tsa_chain_outcome を上書きする。これはタイムスタンプ自身のチェーンのみに触れる — 署名者の判定にも、カバーグラフにも、任意の VRT にも決して触れない(verify.rs:1545-1547)。CAdES の統合は、このヘルパーをすべてのタイムスタンプに対して無条件に適用する(verify.rs:867-901)。これは第2パスの VRT 繰り上げの continue によってゲートされない。なぜなら第1パスの verify_timestamp_token は失効を一切適用しなかったからである — TSA チェーンステップは依然として validate_path のプレースホルダーを担っており、すべてのトークンについて埋められなければならない。各トークンの vrt_time は vrt_block.signature_timestamp[i] / archive_timestamp[i] から読み取られ、request.verification_time にフォールバックする。
オフラインモードでは、ヘルパーは埋め込み CMS 失効情報のみを消費し、レスポンダ接触を決して捏造しない。「期待されるが取得不能」のステータスは判定不能の RevocationOutcome へ繰り下がり、署名者チェーンのオフラインポリシーを反映する(憲章 §VII オフラインの誠実性)。PAdES(ブランチ 034)では、埋め込みチャネルがさらに /DSS//VRI で解決された CRL/OCSP DER を運ぶ — 第6章 §6.5 を参照。CAdES の呼び出しは、意図的に埋め込み CMS の値のみを渡し、PDF /DSS//VRI を決して渡さない(FR-004)。
revoked_no_poeapply_tsa_revocation_indication(verify.rs:1622-1653)は、インディケーション合成のはしごの最後の層である(aggregate_etsi → 署名者バインディング → コンテンツタイプ → アルゴリズム妥当性の後、verify.rs:992-998)。これは:
TOTAL_FAILED を決してマスクしない — 既に TOTAL_FAILED であればベースを変更せずに返す(verify.rs:1627)。RevokedOnCrl / RevokedOnOcsp であれば、インディケーションを INDETERMINATE / SubIndication::TsaCertificateRevokedNoPoe(ワイヤ文字列 revoked_no_poe、etsi.rs:424)へ繰り下げ、failing_locus を最初の失効タイムスタンプのロカス、例えば tsa_revocation.signature_timestamp[1] や tsa_revocation.archive_timestamp[0] に設定する(verify.rs:1640-1648)。これは、タイムスタンプサブプロセスに対する ETSI EN 319 102-1 REVOKED_NO_POE(失効済み・存在証明なし)を反映する。すなわち、TSA 証明書が失効しており、失効前の存在証明(PoE)がないため、タイムスタンプはもはや存在証明として信頼できない。これは決して TOTAL_FAILED ではない — 失効した TSA 証明書は「タイムスタンプを断言できない」のであって、署名偽造が証明されたわけではない(憲章 §I)。(確認された失効ではなく)判定不能の TSA 結果は、それ自体では繰り下げを行わない(FR-006)。オフラインの「期待されるが取得不能」は、TSA チェーンに適用された署名者チェーンポリシーによって既に対称的に処理されている(FR-006a)ため、追加の繰り下げは導入されない。
TsaCertificateRevokedNoPoe は schema_version 1.4.0 → 1.5.0 で導入された追加的なクローズドな列挙値である。これは既存の RevocationOutcome の語彙を再利用し、新しい失効結果を追加しない。
VRT エンジンと TSA 失効ヘルパーは 5 形式すべてで共有されるため、検証者(信頼当事者)は CAdES 対 PAdES 対 XAdES 対 JAdES 対 ASiC を特別扱いする必要が決してない(spec.md US3)。レビュアーが実装に対して保持すべき横断的な不変条件は以下のとおり。
derive_vrt は時刻ソースとして request_at のみを読む。決して Clock::now() を呼ばず、証明書ストアに触れず、I/O を実行しない(vrt/mod.rs:18-21)。FR-002a 条項 4-5 は設計上、呼び出し箇所に委ねられる。request_at == inputs[].at。 VRT 導出時に 2 度目のクロック読み取りはない(FR-011a)。CLI は --at が供給されない限り、起動時に一度検証時刻を捕捉する(憲章 §II)。tsa_chain_outcome のみに触れる。demotion_reason とともに request_at_fallback_invalid_timestamp へ繰り下げる — pverify はなぜ繰り上げできなかったかを報告し、判定を黙って回復することは決してない(spec.md US2 シナリオ 2)。promotion.rs:37-54)。request_at と等しいオブジェクトの再判定はスキップされる。それは同一の出力を生じるためである。これは、一般的なカバー TS なしのケースに対してバイト単位で同一のレポートを保つ(verify.rs:775、pades/mod.rs:911)。pverify-core カーネルの一部であるため、ネイティブ CLI とブラウザの WASM ホストで同一に実行される。同一の入力はバイト単位で同一の VRT ブロックを生じる。本章を US1 フィクスチャ(spec.md 41-53 行)で結びつける。署名者証明書は 2023-06-01 … 2025-06-01 の間有効。署名 TS の GenTime は 2024-03-01。アーカイブ TS の GenTime はそれより後。--at = 2026-06-20。
gen_time と imprint_record が捕捉される。署名者チェーンを --at で検証すると INDETERMINATE / signing_certificate_expired を報告するであろう。2024-03-01)は --at 以下であり、内部的に有効で、その TSA チェーンはアンカーされるため、適格な繰り上げ元である。derive_vrt。 vrt.signer_chain.derivation == signature_timestamp、vrt.signer_chain.value == 2024-03-01。署名 TS 自身の VRT はアーカイブ TS の GenTime(covering_archive_timestamp)である。アーカイブ TS(最も外側)は request_at にフォールバックする。2024-03-01 — 証明書の有効期間内 — で再検証され、TOTAL_PASSED を生じる。各 TSA チェーンは apply_tsa_chain_revocation_for_token を介してそれ自身の VRT で失効検証される。signatures[0].etsi_indication.indication == "TOTAL_PASSED"、そして signatures[0].vrt が、監査のために選択されたすべての時刻とその導出タグを表出する。US2 シナリオ 2(spec.md 68 行)と対比する。アーカイブ TS を取り除き、署名 TS 自身が最も外側になるようにする。両方の TSA 証明書がその後失効(有効期限切れ)している場合、署名 TS チェーンの VRT は request_at にフォールバックし、繰り下げが誠実に表面化し、インディケーションは INDETERMINATE へ繰り下がる — 黙った回復はない。その誠実な繰り下げの挙動こそが、時刻とともに導出タグと繰り下げ理由を表出することの眼目である。
本章では、pverify の アルゴリズム妥当性(algorithm-validity)判定を規定する。これはオプトインの、オブジェクトごとの、VRT スコープのチェックであり、各暗号オブジェクトのダイジェストアルゴリズム、署名アルゴリズムファミリおよび鍵長を、そのオブジェクトの検証基準時刻(VRT)時点で有効なバージョン管理されたポリシーと照合し、アルゴリズムがサンセットを迎えており、かつそのサンセット以前の存在証明が存在しない場合に ETSI インディケーションを INDETERMINATE / crypto_constraints_failure_no_poe へと格下げする。本機能はブランチ 032-algorithm-validity(JNSA ガイドラインのギャップレビュー)で導入され、アルゴリズム妥当性を フラグのみ(weak_algorithms[]、変更なく保持)から 判定に影響するものへと移行させた。
このチェックは ETSI EN 319 102-1(ETSI 欧州標準)の CRYPTO_CONSTRAINTS_FAILURE_NO_POE サブインディケーションに支配され、そのデフォルト閾値を CRYPTREC(電子政府推奨暗号リスト)から取得し、NIST SP 800-57 Part 1 / SP 800-131A を副次的な参照とし、専用の憲法条項(constitution v1.2.0 で批准された「Algorithm-policy enforcement scope」条項)によって制約される。本機能はブランチ 031(第12章)のオブジェクトごとの検証基準時刻エンジンの直上に構築される。すなわちアルゴリズム妥当性エンジンはクロックを一切読み取らず、VRT を再導出することも一切なく、signatures[i].vrt を逐語的に消費する。
本章の正典となるソースファイルは、crates/pverify-core/src/algorithm_policy/{mod.rs,policy.rs,extract.rs}(純粋計算エンジン、ポリシー入力型、および OID/鍵抽出器)、crates/pverify-core/src/report/algorithm_validity.rs(ワイヤ型)、crates/pverify-core/src/verify.rs(オブジェクトごとの組み立てとインディケーションの階層化)、および docs/algorithm-validity-policy.md(公開された人間可読のポリシー参照。コード定数とバイト単位で同一であることが表明されている)である。
アルゴリズム妥当性チェックは、素朴な「弱い暗号を拒否する」ゲートとは一線を画す、4 つの譲れない設計原則によって形作られている。それぞれはコード上に実装され、テストによって表明されている。本節ではそれらを冒頭で名指しする。これらが本章の残りのあらゆる機微を説明するからである。
1. オプトイン、デフォルトオフのバイト単位の同一性。 このチェックはホストがポリシーを供給したときにのみ発火する。ポリシーが存在しない場合、レポート本体は schema_version 文字列(ブランチ 032 で 1.3.0 → 1.4.0 に進み、ワークスペースは現在 1.9.0、crates/pverify-core/src/report/schema.rs:line を参照)を除いて 032 以前のレポートとバイト単位で同一である。このメカニズムは構造的である。SignatureEntry.algorithm_validity は skip-when-None を伴う Option<AlgorithmValidityCheck> であるため、None は null としてシリアライズされるのではなく JSON から 不在 となり、ポリシーが None のときはインディケーション階層化の呼び出しが完全にスキップされる(crates/pverify-core/src/verify.rs:970)。これは再現性不変条件(憲法 §II)を満たす。追加的な機能は、無効化されているときにレポート本体を撹乱してはならない。
2. 決して TOTAL_FAILED にしない — 「断定回避」の姿勢(憲法 §I)。 アルゴリズムチェックの失敗は 決して TOTAL_FAILED を生まない。pverify は SHA-1 署名が 偽造された ものであると断言することはできない。アルゴリズムが何らかの証明された存在時点よりも前にサンセットを迎えたために署名の妥当性を断言できない、と述べることができるのみである。したがって誠実な判定は、サブインディケーション crypto_constraints_failure_no_poe(EN 319 102-1 の CRYPTO_CONSTRAINTS_FAILURE_NO_POE 値)を伴う INDETERMINATE である。暗号学的に 破綻した 署名 — messageDigest 不一致、署名検証の失敗 — はより深刻かつより根本的な事実であり、コアパイプラインによって TOTAL_FAILED として報告される。アルゴリズム層はそれを決してマスクしない(原則 4 を参照)。
3. ウォールクロックスコープではなく VRT スコープ(長期署名の成果)。 各オブジェクトは、そのオブジェクト自身の VRT 時点で有効な ポリシーと照合して判定され、その VRT は 031 の VRT ブロックから逐語的に読み取られる。VRT が 2012-06-01 である SHA-1 の署名者署名(有効なカバーするタイムスタンプが 2014 年のサンセットに先行するため)は 受容可能 である。これはまさに ETSI が意図する長期署名のセマンティクスである。同じ SHA-1 署名で、リクエスト時刻フォールバックの VRT が 2026-06-22 である場合(カバーするタイムスタンプがなく、サンセット以前の存在証明がない)は 失敗 する。エンジンはクロックを一切読み取らない。リクエストから vrt_value を消費し、カバーグラフを再計算することは決してない(crates/pverify-core/src/algorithm_policy/mod.rs モジュールドキュメント、R-4)。
4. コア判定の後に階層化し、決して TOTAL_FAILED をマスクしない。 アルゴリズム妥当性インディケーションは、基底の aggregate_etsi 判定の 後、および署名者バインディング層とコンテンツタイプ層の後に、インディケーションラダーの 1 つの層として適用される。基底が既に TOTAL_FAILED である場合、この層はそれを変更せずに返す(apply_algorithm_validity_indication、crates/pverify-core/src/verify.rs:1488)。
5. 認識できないものは決して暗黙に合格しない(032 が閉じるまさにそのギャップ)。 pverify が認識しないアルゴリズム OID、または回復できない鍵長は、合格として通り抜けることはない。デフォルトの unrecognised_treatment = indeterminate の下では INDETERMINATE / crypto_constraints_failure_no_poe へと格下げされる。これがこのスライスの中心的な正当性特性である。未知のアルゴリズムを暗黙に受容する検証器は安全でない。
支配的なポリシーは、バージョン管理された JSON シリアライズ可能な構造体 AlgorithmPolicy(crates/pverify-core/src/algorithm_policy/policy.rs:170)である。その同一性は (source, version) の組であり、両文字列はすべてのレポートで逐語的に表面化される(policy_source / policy_version)ため、レポートは どの ポリシーが各オブジェクトを決定したかを記録する。
pub struct AlgorithmPolicy {
pub source: String, // authority of record
pub version: String, // policy version string
pub digest_rules: Vec<DigestRule>,
pub signature_rules: Vec<SignatureFamilyRule>,
pub unrecognised_treatment: UnrecognisedTreatment,
}ポリシーは pverify が名指しできるアルゴリズムのみを制約できる。ダイジェストおよび署名ファミリの値空間はクローズドな Rust enum であり、ワイヤ形式へ 1:1 でミラーリングされる。
| 列挙 | バリアント(ワイヤ形式) | ファイル |
|---|---|---|
DigestId |
sha1, sha256, sha384, sha512, md5 |
policy.rs:99 |
SignatureFamily |
rsa, ecdsa, ed25519, mldsa |
policy.rs:110 |
UnrecognisedTreatment |
indeterminate(デフォルト), fact_only |
policy.rs:125 |
SignatureFamily::MlDsa は、批准されたデフォルトポリシーテーブルと mldsa-ok ルール ID スタイルに合わせるため、意図的にスネークケースの ml_dsa ではなくコンパクトな mldsa としてシリアライズされる(policy.rs:117)。DigestId::Md5 は常に受容不可のマーカーである。これはデフォルトポリシーが MD5 を「認識できない」ままにするのではなく「決して受容不可」(CRYPTREC 除外)に固定できるようにするためにのみ存在する。
pub struct DigestRule {
pub digest: DigestId,
pub rule_id: String, // e.g. "sha1-sunset-2014"
pub acceptable_before: Option<PolicyDate>, // None = always; Some(D) = ok iff VRT.date < D
pub authority: String, // citation, surfaced in the report
}
pub struct SignatureFamilyRule {
pub family: SignatureFamily,
pub rule_id: String,
pub min_key_bits: Option<u32>, // None = no length floor (Ed25519/ML-DSA)
pub min_effective: Option<PolicyDate>, // None = always enforced; Some(D) = enforced iff VRT.date >= D
pub authority: String,
}PolicyDate は日付のみの値(YYYY-MM-DD、RFC 3339 full-date)であり、位置 4 および 7 にダッシュを持つ厳密な 10 文字形式を強制し月日の範囲を検証するカスタムの Serialize/Deserialize を備える(policy.rs:56–policy.rs:89)。日粒度はサンセットセマンティクスにとって十分であり、ポリシーを手作業で監査可能に保つ。定数 PolicyDate::UNIX_EPOCH(1970-01-01)は「決して受容不可」のマーカーである。現実的な VRT はすべてそれ以降であるため、acceptable_before = 1970-01-01 に固定されたダイジェストは常に失敗する。
比較セマンティクスは 2 種類のルールの間で意図的に非対称であり、いずれもヘルパ vrt_on_or_after(vrt, date)(mod.rs:118、vrt.date() >= date を返す)を鍵とする。
VRT.date < acceptable_before の場合に限り合格する。アルゴリズムはサンセット日 まで(ただしその日は含まない) 信頼できた。min_effective が None であるか、または VRT.date >= min_effective の場合に限り 強制 される。有効日より前ではこの閾値はまだ効力を持たない(フロア以前の短い鍵は受容可能)。有効日以降は、min_key_bits より短い鍵は失敗する。不正な形式のポリシー日付(実際には到達不能 — 日付は deserialize 時に検証される)は、mod.rs:122 の保守的なコメントに従い 有効(フェイルクローズド)として扱われる。
すべてのポリシー入力構造体は #[serde(deny_unknown_fields)] を備える(policy.rs:138, :151, :169)。余分なキーや綴り誤りのキーを持つポリシーファイルは パース時に拒否 され、決して暗黙に無視されることはなく、実行はデフォルトへフォールバックするのではなく非ゼロ終了と明確なエラーで中断する(crates/pverify-cli/src/main.rs:319 load_algorithm_policy)。これは FR-008 である。テスト deny_unknown_fields_fails_closed(policy.rs:306)がこの挙動を固定する。
ホストがファイルを供給せずに強制を有効化した場合、エンジンは AlgorithmPolicy::default_policy()(policy.rs:186)を使用する。その全内容はコード定数として定義され、憲法上のサーフェスである。すなわちその値は憲法の「Algorithm-policy enforcement scope」条項(.specify/memory/constitution.md、v1.2.0)によって批准されており、定数は specs/032-algorithm-validity/contracts/default-policy.md(テスト C-3)の機械可読な規範形式とバイト単位で同一であることが表明される。したがってアルゴリズムの再分類は 統治された変更(憲法改正)であり、暗黙のコード編集ではない。
同一性。
| フィールド | 値 |
|---|---|
source |
CRYPTREC 電子政府推奨暗号リスト (primary) + NIST SP 800-57 Part 1 / SP 800-131A (secondary) |
version |
2026-06-default |
unrecognised_treatment |
indeterminate |
ダイジェストルール(policy.rs:199–:230):
| ダイジェスト | rule_id |
acceptable_before |
権威 |
|---|---|---|---|
| MD5 | md5-never |
1970-01-01(never) |
CRYPTREC excluded |
| SHA-1 | sha1-sunset-2014 |
2014-01-01 |
NIST SP 800-131A; CRYPTREC 危殆化 |
| SHA-256 | sha256-ok |
null(always) |
CRYPTREC 推奨 |
| SHA-384 | sha384-ok |
null(always) |
CRYPTREC 推奨 |
| SHA-512 | sha512-ok |
null(always) |
CRYPTREC 推奨 |
署名ファミリルール(policy.rs:231–:260):
| ファミリ | rule_id |
min_key_bits |
min_effective |
権威 |
|---|---|---|---|---|
| RSA | rsa-min-2048-2014 |
2048 | 2014-01-01 |
NIST SP 800-57; CRYPTREC 推奨 |
| ECDSA | ecdsa-p256-min |
256 | null(always) |
CRYPTREC 推奨 |
| Ed25519 | ed25519-ok |
null(フロアなし) |
null |
recognised strong |
| ML-DSA | mldsa-ok |
null(フロアなし) |
null |
PQC, no sunset |
デフォルトの根拠は権威の優先順位について明示的である。CRYPTREC が記録上の国内権威であり(pverify は JNSA デジタル署名検証ガイドラインを追従する日本政府の GPKI 検証ツールである)、NIST SP 800-131A / SP 800-57 が CRYPTREC が暗黙のままにしている移行日 — 特に SHA-1 と RSA-2048 の 2014 年の切り替え — を供給する。ECDSA について観測される「鍵ビット数」は曲線の体サイズ(P-256 → 256、P-384 → 384)であるため、256 のフロアは認識される両曲線を許容しつつ、それより弱いものを除外する。Ed25519 と ML-DSA は鍵長フロアを持たない。これらはサンセットのない認識される強アルゴリズムである。
エンジンは crates/pverify-core/src/algorithm_policy/mod.rs に存在し、no_std + alloc、ゼロ I/O、ゼロクロックかつ WASM クリーンである(憲法 §VI)。その公開サーフェスは 2 つの関数である。
pub fn evaluate(objects: &[ObjectUnderEvaluation], policy: &AlgorithmPolicy)
-> Vec<AlgorithmValidityResult>; // one result per object, input order
pub fn overall_outcome(results: &[AlgorithmValidityResult]) -> AvOutcome;これは事前に組み立てられた &[ObjectUnderEvaluation](verify パイプラインが既にパース済みの VRT ブロックとアルゴリズム識別子から構築したもの)を消費し、入力順に 1 オブジェクトあたり 1 つの AlgorithmValidityResult を返す — 決定論的であり、したがってバイト単位で安定である(憲法 §II)。この決定論性は determinism_byte_identical(mod.rs:456)によって固定される。
各オブジェクトは、そのローカス、その VRT(値 + 導出、031 ブロックから逐語的にミラーリング)、および観測されたアルゴリズムデータを保持する。
pub struct ObjectUnderEvaluation {
pub locus: ObjectLocus, // SignerChain | SignerSignature | Signature/ArchiveTimestamp{index}
pub vrt_value: OffsetDateTime, // R-4: consumed verbatim, never recomputed
pub vrt_derivation: VrtDerivation, // mirrored for the report (FR-012)
pub digest: Option<DigestId>, // None if undeterminable/unrecognised
pub signature_family: Option<SignatureFamily>,
pub signature_alg_oid: String, // always recorded, even if unrecognised
pub key_bits: Option<u32>, // None if undeterminable
}evaluate_one(mod.rs:264)は 2 つの独立したサブチェックを実行し、それらを worst-of で合成する。
ダイジェストサブチェック(check_digest、mod.rs:130):
digest = None)→ ルールなしの Pass。これはそれ自体としては失敗ではない。たとえばタイムスタンプオブジェクトはインプリントダイジェストのみを保持する場合があり、ダイジェストが内部的であるファミリは別個のダイジェストを保持しない。署名/鍵サブチェックが判定を担う。unrecognised_treatment を経由(決して暗黙の合格ではない)。acceptable_before = Some(D) かつ VRT.date >= D の場合に限り失敗、それ以外は合格。マッチしたルールはいずれの場合も記録される。署名/鍵サブチェック(check_signature、mod.rs:161):
signature_family = None かつ signature_alg_oid が空 → Pass(このオブジェクトはチェックすべき署名コンポーネントを保持しない。たとえばインプリントダイジェストのみで判定されるタイムスタンプ)。signature_family = None だが signature_alg_oid が非空 → 認識できない OID が観測された → unrecognised_treatment を経由(R-2)。エンジンは「チェックすべきものがない」(空の OID)と「認識しない OID を見た」を慎重に区別する — 後者のみが格下げする。min_key_bits = None のファミリ(Ed25519 / ML-DSA)→ Pass、ルールを記録。min_key_bits フロアを持つファミリ: フロアは min_effective 日付に従って強制される。強制される場合、key_bits >= min は合格、key_bits < min は失敗、key_bits = None(強制されたフロアに対して判定不能)は Indeterminate(「断定回避」)へ格下げする。合成(mod.rs:272): オブジェクトの結果は、重大度順序 Fail (2) > Indeterminate (1) > Pass (0)(severity、mod.rs:101)による 2 つのサブチェックの worst である。同点の場合は 署名/鍵 ルールが勝つため、合格するオブジェクトはダイジェスト判定ではなくより具体的な署名判定を記録する(監査性、FR-012)。matched_rule と reference_vrt は合格・失敗いずれにも記録されるため、読み手はレポート単独から判定を再計算できる。
「断定できない」をインディケーションへ変換する唯一のつまみは unrecognised_outcome(mod.rs:218)である。Indeterminate(デフォルト)はインディケーションを格下げする。FactOnly は格下げせずに事実を記録する(寛容なデプロイ向け — 明示的にデフォルトではない)。これはポリシー作成者の選択であり、デフォルトは安全な方である。
エンジンはバイトを一切パースしない。ホスト側の extract モジュール(crates/pverify-core/src/algorithm_policy/extract.rs)が、既にパースされたアルゴリズム識別子と証明書 SPKI バイトをクローズドなポリシー列挙へマップする。それが回復するすべてのデータは、署名検証 / 弱フラグ付けのために verify パス内の別の場所で既にパースされている(リサーチ R-5)。本モジュールはそれらを再パースするのではなく収集し、新規依存を一切追加しない(FR-015)。RSA 鍵ビット数は既存の rsa クレート経由で、ECDSA 曲線は spki / x509-cert 経由で得る。
| 関数 | マップ | 認識する対象 |
|---|---|---|
digest_id_from_oid |
ダイジェスト OID → DigestId |
SHA-1(1.3.14.3.2.26)、SHA-256/384/512、MD5(1.2.840.113549.2.5) |
signature_family_from_oid |
sig-alg OID → SignatureFamily |
単独 + 結合 RSA OID(RSASSA-PSS …1.1.10 を含む)、単独 + 結合 ECDSA OID、Ed25519(1.3.101.112)、ML-DSA-44/65/87 |
digest_from_signature_oid |
結合 sig-alg OID → 埋め込まれた DigestId |
<digest>With<family> PKCS#1 v1.5 / ECDSA-with-SHA OID |
key_bits_from_cert_der |
cert DER + ファミリ → 鍵ビット数 | RSA 法ビット数、ECDSA 曲線体サイズ(P-256→256、P-384→384) |
key_bits_from_cert_der(extract.rs:94)は証明書 DER から SubjectPublicKeyInfo を再エンコードし、RSA については RsaPublicKey::size() * 8 を計算し、ECDSA については namedCurve OID(1.2.840.10045.3.1.7 → 256、1.3.132.0.34 → 384)を読み取る。Ed25519 / ML-DSA については 設計上 None を返す — これらのファミリは鍵長フロアを持たないため、min_key_bits = None ルールに対する None は合格する。あらゆるパース失敗も None を返す。強制された フロア(min_effective 日付が効力を持つ RSA/ECDSA)に対しては、None は断定できないこと → Indeterminate として扱われる。認識される OID/曲線の集合は crypto.rs が署名検証に使用するものと同一であるため、エンジンが署名の検証方法を「知っている」のに、その後それをポリシーのために認識できないということは起こり得ない。
各形式別検証器は、VRT ブロック、パース済みの署名者証明書、トークンごとのレコードがすべて手元にある継ぎ目でオブジェクトインベントリを組み立て、その後 evaluate を呼び出してインディケーションを階層化する。CMS 形状の形式(CAdES、PAdES 埋め込み CMS、ASiC ラップ CAdES)は単一のアセンブラを共有する。XAdES と JAdES は同じ ObjectUnderEvaluation 形状を構築する類似物を持つ。
assemble_and_evaluate_cms_objects(crates/pverify-core/src/verify.rs:1671)は、決定論的な順序でオブジェクト集合を構築する。
signer_chain — 署名者(リーフ)証明書自身の signatureAlgorithm(発行者 がリーフへ署名するために用いたダイジェスト+ファミリ)に基づいて判定される。鍵長は 発行者の 公開鍵 — 実際に証明書署名を生成した鍵である。発行者は中間証明書の中での subject/issuer DN 照合によって特定される(find_issuer_cert、verify.rs:1779)。発行者証明書が利用不可の場合(たとえば自己発行のリーフ、または表面化されていないアンカー)、鍵長は署名者鍵へフォールバックする。これは一般的な GPKI の形状である。signer_signature — 署名された属性に対する署名者の署名。ダイジェスト = 署名属性のダイジェストアルゴリズム、ファミリ = 署名者鍵のファミリ(signer_spki_family、verify.rs:1818 によってリーフ SPKI から回復)、鍵長 = 署名者鍵。報告される signature_alg_oid は、判定可能な場合は 結合 ダイジェスト+ファミリ OID(signer_signature_oid、verify.rs:1832)であり、レポートが実際のアルゴリズムを示し、ポリシーのファミリルールがマッチするようにする。signature_timestamp[i] — 署名タイムスタンプごとに 1 つ、そのメッセージインプリントダイジェストに基づいて判定される。signature_family = None かつ signature_alg_oid が空であるため、インプリントダイジェストのみがチェックされる(timestamp_object、verify.rs:1797)。archive_timestamp[i] — アーカイブタイムスタンプごとに 1 つ、同様に判定される。各オブジェクトの VRT は、インデックスによってマッチする 031 VRT エントリから読み取られる。エントリが見つからない場合(防御的)、署名者署名 VRT へフォールバックする(verify.rs:1735–:1764)。
スコープ注記(032): タイムスタンプオブジェクトは インプリントダイジェスト のみを判定する。TSA の 署名 アルゴリズムおよび CRL/OCSP レスポンダの署名アルゴリズムはブランチ 032 のスコープ外である(後続の延期事項、
docs/algorithm-validity-policy.md§ Scope)。タイムスタンプの 妥当性 それ自体は 031 VRT エンジン(第12章)と 033 TSA 失効層(第11章)の関心事のままである。
同一のチェックが 5 つすべてのファミリにわたって実行される。アセンブラは CMS 形状の形式について共有される。XAdES と JAdES は各自の署名メソッドからインベントリを構築する。
| 形式 | 呼び出しサイト | 注記 |
|---|---|---|
| CAdES | verify.rs:972(assemble_and_evaluate_cms_objects) |
参照実装 |
| PAdES | crates/pverify-core/src/pades/mod.rs:1077 |
埋め込み CMS、CMS アセンブラを再利用 |
| XAdES | crates/pverify-core/src/xades.rs:653(evaluate_xades_algorithm_validity) |
signature method → ファミリ/ダイジェスト |
| JAdES | crates/pverify-core/src/jades.rs:350 |
現スライスでは B-B のみ |
| ASiC | 委譲された内側の CAdES/XAdES パイプライン経由 | 内側形式のチェックを継承 |
すべての呼び出しサイトは algorithm_policy.is_some() / request.algorithm_policy.as_ref().map(...) でガードするため、ポリシーが不在のときはフィールドは None のままでインディケーションは手付かずとなる — オフパスのバイト単位の同一性を保持する(§13.1、原則 1)。
apply_algorithm_validity_indication(crates/pverify-core/src/verify.rs:1480)は、アルゴリズムチェックが判定に影響を与える唯一の地点である。これはマスクしないインディケーションラダー(第5章 §5.7、結果モデル 第14章)の 1 つの層として実行される。
aggregate_etsi (base)
→ apply_signer_binding_indication
→ apply_content_type_indication
→ apply_algorithm_validity_indication ← 本章
→ apply_tsa_revocation_indication
そのロジックは意図的に小さい。
pub(crate) fn apply_algorithm_validity_indication(
base: EtsiIndication,
results: &[AlgorithmValidityResult],
) -> EtsiIndication {
if base.indication == Indication::TotalFailed { return base; } // never mask (R-6)
// most-severe failing object: Fail > Indeterminate; earliest on a tie
let worst = results.iter()
.filter(|r| matches!(r.outcome, Fail | Indeterminate))
.fold(None, /* keep earliest on severity tie */);
match worst {
Some(r) => EtsiIndication::indeterminate(
SubIndication::CryptoConstraintsFailureNoPoe,
Some(r.locus.locus_string())), // e.g. "algorithm_validity.signer_signature"
None => base, // all pass → base unchanged (FR-010)
}
}主要な特性:
TOTAL_FAILED をマスクしない(verify.rs:1488 の早期リターン、R-6)。暗号学的に破綻した署名は TOTAL_FAILED のままである。アルゴリズムの事実はより根本的でない方である。algorithm_validity ブロックを除いて、ポリシーオフの同一署名とバイト単位で同一である。Fail は Indeterminate を上回る。同等の中では 入力順で最先 のものが勝つ(fold は >= 重大度比較で現在のものを保持する、verify.rs:1507)ため、failing_locus は決定論的である。failing_locus は正確なオブジェクト文字列である — AvObjectLocus::locus_string(crates/pverify-core/src/report/algorithm_validity.rs:101)は algorithm_validity.signer_chain、algorithm_validity.signer_signature、algorithm_validity.signature_timestamp[1] などをレンダリングする。したがって監査者は どの オブジェクトが、どの VRT で判定を格下げしたかを見ることができる。結果は常に crypto_constraints_failure_no_poe を伴う INDETERMINATE であり、決して TOTAL_FAILED ではない — Fail(サンセット、PoE なし)と Indeterminate(認識不能 / 判定不能)の両ケースについてそうである。2 つのケースはインディケーションではなく、オブジェクトごとの outcome フィールドで区別される。
強制がオンのとき、各 SignatureEntry は algorithm_validity ブロック(crates/pverify-core/src/report/algorithm_validity.rs:38)を保持する。
AlgorithmValidityCheck
├─ policy_source : String — *アクティブな* ポリシーの記録上の権威
├─ policy_version: String — アクティブなポリシーのバージョン
├─ results : [AlgorithmValidityResult]
└─ overall : AvOutcome — results 全体の worst(すべて合格の場合に限り pass)
AlgorithmValidityResult
├─ locus : { kind, index? } — signer_chain | signer_signature
│ | signature_timestamp | archive_timestamp
├─ reference_vrt : { value, derivation } — このオブジェクトが判定された VRT、
│ 031 の time_fmt + VrtDerivation を再利用
├─ observed_digest : String|null — ダイジェスト OID、判定不能なら null
├─ observed_signature_alg : String — sig-alg OID、常に記録
├─ observed_key_bits : u32|null — 鍵ビット数、判定不能なら null
├─ matched_rule : MatchedRule|null — 合格・失敗いずれにも記録(FR-012)
└─ outcome : "pass"|"fail"|"indeterminate"
MatchedRule
├─ rule_id : String — 安定した id、例 "sha1-sunset-2014"
├─ kind : "digest_sunset" | "key_length"
├─ effective_date : String|null — YYYY-MM-DD、常時適用の場合は null
└─ authority : String — ルールからコピーされた引用
すべての結果は 自己記述的であり、レポート単独から再計算可能 である(FR-012)。検証者(信頼当事者)は判定を手作業で再導出できる — サンセットルールについては、reference_vrt.value の日付が effective_date 以上であり、かつアルゴリズム/鍵が弱いものである場合に限り失敗 となる。reference_vrt は正典の crate::report::time_fmt シリアライザと 031 の VrtDerivation enum を逐語的に再利用するため、時刻形式と導出語彙は重複しない(algorithm_validity.rs:170)。policy_source / policy_version は常に アクティブな ポリシー(デフォルトまたは供給されたもの)を名指しするため、保存されたレポートはどの権威が各オブジェクトを決定したかについて曖昧でない。
weak_algorithms[] との関係既存の weak_algorithms[] の 事実(ヘッダレベルの WeakAlgorithmFlag、crates/pverify-core/src/report/mod.rs:250)は、ブランチ 032 によって 変更なく保持 される。これらはフラグのみのインベントリである — ステップごとのすべての SHA-1 観測を役割別(signed_attrs_digest、signer_signature、certificate_signature、crl_signature、tsa_message_imprint、tsa_signature、ess_signing_certificate_digest)に記録する — そしてそれらにフラグを立てることはインディケーションを 決して 格下げしない。032 の algorithm_validity ブロックは直交する、判定に影響する、オプトインの、VRT スコープの層であり、事実を置き換えるものではない。したがってポリシーオンのレポートは 両方 を保持する。生の弱アルゴリズム観測と、それらを各オブジェクトの VRT に照らして文脈づけるポリシー判定である。
ポリシーはホスト側でロードされる。コアはファイルを一切読み取らない(憲法 §V)。
CLI(crates/pverify-cli/src/main.rs): フラグは --algorithm-policy [FILE] であり、Option<Option<PathBuf>>(main.rs:271)としてモデル化される。不在 → None(オフ)。パスなしで存在 → 組み込みの default_policy()。パス付きで存在 → load_algorithm_policy(main.rs:319)が JSON を読み、UTF-8 検証し、パースし、あらゆるエラーでフェイルクローズドする(main.rs:763)。パースされたポリシーは VerificationRequest.algorithm_policy(verify.rs:157)へスレッドされる。
WASM(web/pverify-wasm/src/lib.rs:66): バイト単位で同一の等価物。入力は AlgorithmPolicyInput enum — Default(組み込み)または Custom(完全にパースされた AlgorithmPolicy)— を保持し、同一の Option<AlgorithmPolicy>(lib.rs:380)へ解決される。両ホストが同一の構造体を同一の verify_with カーネルへ供給するため、CLIとWASMのパリティ不変条件が成り立つ。同一の入力(同一のポリシーを含む)はバイト単位で同一のレポートを生む。
これらはエンジンテスト(mod.rs:352–:488)と公開された参照(docs/algorithm-validity-policy.md)から引いたものであり、すべてデフォルトポリシーに対するものである。
| オブジェクト | VRT | 観測値 | マッチしたルール | 結果 | インディケーションへの寄与 |
|---|---|---|---|---|---|
| SHA-1 / RSA-2048 署名者署名 | 2013-12-31 |
sha1, rsa, 2048 | sha1-sunset-2014 |
pass(2013 < 2014) |
なし |
| SHA-1 / RSA-2048 署名者署名 | 2014-01-01 |
sha1, rsa, 2048 | sha1-sunset-2014 |
fail(2014 ≥ 2014) |
INDETERMINATE / crypto_constraints_failure_no_poe |
| RSA-1024 / SHA-256 署名者 | 2026-06-22 |
sha256, rsa, 1024 | rsa-min-2048-2014 |
fail(2026 ≥ 2014、1024 < 2048) |
INDETERMINATE / …no_poe |
| RSA-2048 / SHA-256 署名者 | 任意 | sha256, rsa, 2048 | rsa-min-2048-2014 |
pass | なし |
| ECDSA P-256 / SHA-256 | 任意 | sha256, ecdsa, 256 | ecdsa-p256-min |
pass | なし |
| Ed25519 | 任意 | —, ed25519, — | ed25519-ok |
pass | なし |
| ML-DSA-65 | 任意 | —, mldsa, — | mldsa-ok |
pass | なし |
未知の sig OID 1.2.3.4.5 |
任意 | —, none, — | (なし) | indeterminate | INDETERMINATE / …no_poe |
| RSA ファミリ、鍵ビット数が回復不能 | 2026-06-22 |
sha256, rsa, null | rsa-min-2048-2014 |
indeterminate | INDETERMINATE / …no_poe |
最初の 2 行は長期署名の成果が実際に働いているところである。同じ SHA-1 署名は、カバーするタイムスタンプがその存在を 2014 年サンセット以前に証明するときには断言可能であり、それがないときには断言不能である。末尾から 2 番目の行は 032 が閉じるギャップである — 未知のアルゴリズムは暗黙に合格しない。
--algorithm-policy FILE は、組み込みのデフォルトを同じ形状の JSON ポリシーで置き換える。デプロイは、次のようなファイルを供給することでデフォルトより 厳格 にできる(たとえば RSA-2048 のサンセットを 2020 年発効とし、RSA-3072 を要求する)。
{
"source": "ACME strict deployment policy",
"version": "strict-2020",
"digest_rules": [
{ "digest": "sha256", "rule_id": "sha256-ok", "acceptable_before": null, "authority": "CRYPTREC" }
],
"signature_rules": [
{ "family": "rsa", "rule_id": "rsa-min-3072-2020", "min_key_bits": 3072,
"min_effective": "2020-01-01", "authority": "ACME" }
],
"unrecognised_treatment": "indeterminate"
}出力される policy_source / policy_version は この ポリシーを名指しするため、レポートはどのポリシーが各オブジェクトを決定したかを記録する。不正な形式または余分なフィールドを持つファイルは実行を中断させる(#[serde(deny_unknown_fields)]、§13.2.3) — 決してデフォルトへの暗黙のフォールバックではない。
crates/pverify-core/src/report/algorithm_validity.rs のワイヤ型は、specs/032-algorithm-validity/contracts/report-schema-algorithm-validity-delta.md によって固定されたスキーマ差分の 1:1 ミラーであり、それは順にルートの report-schema.json へミラーリングされる。algorithm_validity ブロック、AvOutcome / RuleKind / AvObjectLocus のクローズドな列挙、および crypto_constraints_failure_no_poe サブインディケーションの導入はすべて 追加的 であった(新しいオプショナルフィールド、新しいクローズドな列挙値)。したがって追加的な MINOR バンプである(ブランチ 032 で 1.3.0 → 1.4.0。レポート形状は現在 SCHEMA_VERSION = "1.10.0"(v1.3.0 時点)、crates/pverify-core/src/report/schema.rs:line)。crypto_constraints_failure_no_poe は単一のクローズドな SubIndication enum(crates/pverify-core/src/report/etsi.rs:399)の値であり、etsi.rs:698 でラウンドトリップテストされている。
追加的な機能としては珍しく、デフォルトポリシーテーブルの 値 は憲法上のサーフェスである。憲法の「Algorithm-policy enforcement scope」条項は実質的に拡張され、デフォルトテーブルはバージョン 1.2.0 で批准された(.specify/memory/constitution.md 行 4、183、193)。その意図は統治である。pverify はその判定が法的重みを持ちうる政府検証ツールであるため、ある日付時点で何を弱いアルゴリズムとみなすか の変更は、意図的で文書化された改正でなければならず、暗黙のコード編集であってはならない。C-3 契約テストは、コード定数、contracts/default-policy.md の規範形式、および公開された docs/algorithm-validity-policy.md の間の歩調を強制する。
スコープ内(032): CAdES、PAdES、XAdES、JAdES、および ASiC ラップ CAdES/XAdES にわたる、各オブジェクトの VRT におけるダイジェスト / 署名ファミリ / 鍵長のチェック。
スコープ外(延期): タイムスタンプの 妥当性 の再判定(031 VRT エンジンの関心事、第12章)。TSA の 署名 アルゴリズムまたは CRL/OCSP レスポンダの署名アルゴリズムのチェック(タイムスタンプオブジェクトはインプリントダイジェストのみを判定する、§13.6.1)。ネットワーク取得または自動更新されるポリシー(ポリシーは静的で決定論的な入力として供給される)。およびあらゆる 法的 有効性の主張(pverify は事実を報告し、法的結論は報告しない — 憲法 §I)。アルゴリズム妥当性層は、ブランチ 033 の TSA 失効層(第11章)と合成されるが、それを包含しない。当該層は TSA 証明書がその VRT で失効しているときに独立して INDETERMINATE / revoked_no_poe へ格下げする。
本章では、pverify が出力する JSON 形式の事実報告(fact report)——検証実行において外部から消費される唯一の成果物——の形状、意味論、およびガバナンスを規定する。本章は、ETSI EN 319 102-1 のインディケーションおよびサブインディケーションの値空間(§14.2–§14.4)、validation_objects[] の資料インベントリと、ブランチ 034 で追加された PAdES /DSS および /VRI を含む出所(プロビナンス)モデル(§14.5)、ブランチ 035 で追加された全発見事項並列報告 findings[](§14.2)、各署名エントリに付随する診断データ(§14.6)、report-schema.json の契約とその追加的MINORバージョニングの規律(§14.7–§14.8)、そして CLI の終了コードの意味論と検証者(信頼当事者)向けの判定マップ(§14.9)を定義する。
全体を通じて支配する原則は、憲章 §I、すなわち事実報告/「断定回避」である。レポートは観測した事実を述べるものであり、決して業務上の判定を行わない。事実が確立できない場合、レポートは TOTAL_FAILED や TOTAL_PASSED を捏造するのではなく、正確なクローズドな列挙(閉じた語彙)のサブインディケーションを伴って INDETERMINATE に縮退する。インディケーションを生成するパイプラインは第3章(システムアーキテクチャ)で記述する。レポートが表出するオブジェクトごとの検証基準時刻(VRT)の機構は第12章で記述する。本章が関心を寄せるのは、レポートが何を述べるかであって、それがどのように計算されるかではない。
レポート型は、crates/pverify-core/src/report/mod.rs の Rust 構造体 Report である。そのモジュールドキュメントは束縛的な契約を明示的に述べている。すなわち、Rust 型は specs/001-verify-cades-pades/contracts/report-schema.json の 1:1 のミラーであり、いかなる乖離もスタイル上の選択ではなくバグである。3つの決定性に関する制約(憲章 §II、再現性)が型レベルで強制されており、すべてのフィールドに対して規範的である。
BTreeMap フィールド(NameConstraintsCheck.cumulated_permitted / cumulated_excluded、report/mod.rs)は、Ord を導出した GeneralNameForm 列挙をキーとしており、これにより決定論的なキー順序が与えられる。f64 を持たないこと。 すべての数値フィールドは整数または真偽値であり、浮動小数点のフォーマットに起因する非決定性を排除している。Vec<u8> は直列化フィールドとして禁止されている。すべてのバイナリ資料は、レポート層に到達する前にホストによって16進文字列へ事前エンコードされる(例: subject_fingerprint、body_sha256)。時刻には time::serde::rfc3339 を使用し、これは RFC 3339 を正確にラウンドトリップし、秒未満の精度を保持する。カーネルが no_std + alloc かつ I/O を行わない(第3章)ため、レポートはすべてメモリ上のモデル構造から構築される。ホスト(ネイティブでは pverify-cli/src/render.rs、ブラウザでは web/pverify-wasm/src/lib.rs)は、返された Report を JSON へ直列化するにすぎない。ネイティブパスと WASM パスは同一の verify_with カーネルを実行するため、同一入力が与えられればその出力はバイト単位で同一である(CLIとWASMのパリティ不変条件、第3章)。
Report(crates/pverify-core/src/report/mod.rs)は、文書が運ぶ署名の数にかかわらず、verify の呼び出しごとに1度だけ出力される(1文書 → 1レポート → N 個の signatures[] エントリ)。必須フィールドの順序はスキーマの required 配列によって固定されており、header_serialises_in_schema_required_order テストによってアサートされる。
┌─ Report ─────────────────────────────────────────────────────────────┐
│ pverify_version バイナリのバージョン(env CARGO_PKG_VERSION) │
│ schema_version "1.10.0" — レポート形状の SemVer(§14.8) │
│ path_validation_phase "v0.6-bridge-acceptance"(能力サーフェス) │
│ bridge_ca_supported true │
│ dn_match_method "rfc4518-minimal-v0.1" │
│ verification_time RFC 3339 — 取得された単一のクロック時点 │
│ mode online | offline | from_bundle │
│ inputs[] InputHash — 消費したファイル/ポリシーOIDごと │
│ fetch_log[] FetchLogEntry — 送信HTTP、オンライン時のみ │
│ weak_algorithms[] WeakAlgorithmFlag — ステップ別フラグの和集合 │
│ signatures[] SignatureEntry — 署名ごとに1つ(§14.6) │
│ validation_objects[] ValidationObject — 資料インベントリ(§14.5) │
└────────────────────────────────────────────────────────────────────────┘
最初の5つのフィールドは憲法上の不変条件(constitutional invariants)(crates/pverify-core/src/report/schema.rs)であり、レポートを生成したバイナリに関するコンパイル時の事実であって、決して構成(コンフィギュレーション)から読み込まれるものではない。schema_version、path_validation_phase、bridge_ca_supported、dn_match_method は、schema.rs 内で const の文字列/真偽値として、また report-schema.json の24–27行目で "const" の JSON-Schema 制約として固定されている。header_emits_constitutional_invariants テストがこのラウンドトリップを強制する。新たな不変条件の定数を追加することは MINOR スキーマバンプであり、既存のものの値を変更することは MAJOR である(§14.8)。
mode はクローズド列挙型 Mode { Online, Offline, FromBundle }(ワイヤ上では "online"/"offline"/"from_bundle")である。これは失効情報の取得を制御し、ひいては §14.5 の出所導出ヒューリスティックを制御する。verification_time は CLI 起動時に1度だけ取得される(または --at 経由で供給される)単一の時点である。同一の (inputs, anchors, time, revocation-material) のタプルは、いつどこで実行されたかにかかわらずバイト単位で同一のレポートを生成する(憲章 §II)。
ヘッダはまた、必須順序において最後に追加された validation_objects[] を運ぶ。この配置は意図的である。これはブランチ 016 において新たな必須トップレベルフィールドとして追加されたものであり、最後に追加することによって、それ以前のすべてのフィールドが 016 以前のレポートとバイト単位で同一に保たれる(report/mod.rs、Report.validation_objects のドキュメントコメント、SC-004)。
Report::from_json(report/mod.rs)は、より古い保存済みレポートを読み取るための契約サーフェスである。031 以前のレポート(スキーマ 1.2.0)は signatures[].vrt ブロックを完全に省略している。serde は #[serde(default = "vrt::Vrt::request_at_fallback")] を介して欠落フィールドを埋め、これは時刻が OffsetDateTime::UNIX_EPOCH のセンチネルであるブロックを合成する。その後 normalise_stored_vrt_defaults が、すべてのセンチネル値に対して report.verification_time を代入する(FR-014)。serde_json::from_str を直接使用する呼び出し元は生のセンチネルを得る。代入を行うのは from_json の境界だけである。これは、閉じたスキーマがどのように MAJOR な破壊なしに古いレポート本体を吸収するかの正準的な例である(§14.8)。
トップレベルの判定はクローズド列挙型 Indication(crates/pverify-core/src/report/etsi.rs)であり、ETSI EN 319 102-1 §5 をミラーしている。
| Rust variant | ワイヤ文字列 | 意味(EN 319 102-1) |
|---|---|---|
TotalPassed |
"TOTAL_PASSED" |
pverify が実行したすべてのチェックが肯定的に返った。 |
Indeterminate |
"INDETERMINATE" |
利用可能な情報が判断を下すには不十分であった。 |
TotalFailed |
"TOTAL_FAILED" |
pverify が実行したチェックのうち少なくとも1つが否定的に返った。 |
インディケーションは、署名ごとに EtsiIndication 本体(report/etsi.rs)として運ばれる。
pub struct EtsiIndication {
pub indication: Indication,
pub sub_indication: Option<SubIndication>, // None only on TOTAL_PASSED
pub failing_locus: Option<String>, // free-text artefact/step id
}failing_locus は、失敗が観測されたアーティファクト/ステップを意図的に自由記述の識別子で示すものである(例: "step[1]"、未対応 OID に対する "1.3.101.112"、"archive_time_stamp_v3"、"tsa_revocation.signature_timestamp[1]")。これはインディケーション内で唯一の自由記述フィールドである。sub_indication の値空間は厳密にクローズドである(§14.4)。TOTAL_PASSED のインディケーションは sub_indication: null と failing_locus: null を運ぶ(total_passed_emits_null_optionals テストがこれを固定する)。
インディケーションは、aggregate_etsi(crates/pverify-core/src/verify.rs:2343)と、それに重ねられた小さな精緻化の積層によって組み立てられる。aggregate_etsi は、TOTAL_FAILED > INDETERMINATE > TOTAL_PASSED という重大度順序付けされた基底と、「支配的な重大度クラスにおける最初の発見が勝つ。同点の場合は最も早い情報源を優先し、レポートが位置的に安定するようにする」という規則を確立する。具体的には、aggregate_etsi 内部の優先順位の梯子は、順に次をチェックする。パス検証の構造的発見(これはすでに chain_constraints_failure を TOTAL_FAILED 重大度でエンコードしている)、次に archive-time-stamp-v3 のインプリント不一致(verify.rs:2386、TOTAL_FAILED)、次に OCSP レスポンダの検証して失敗(verify.rs:2403、TOTAL_FAILED)、次に signedAttrs/コンテンツダイジェストのゲート、そして失効の結果である。
基底の後に、4つの積層パスが実行される。各パスは証明済みの TOTAL_FAILED をマスクすることを禁じられており、失敗していない基底を縮退させることしかできない(第5章 §5.7)。
apply_signer_binding_indication(verify.rs:1324)— ESS 署名証明書バインディング。apply_content_type_indication(verify.rs:1441)— RFC 5652 §5.3 のコンテンツタイプの一致。apply_algorithm_validity_indication(verify.rs:1480)— オプトインの、VRT スコープのアルゴリズムポリシー。verify.rs:1488 のガードは、基底がすでに TOTAL_FAILED である場合に基底をそのまま返す。失敗しているオブジェクトの中から、最も重大なロカス(Fail > Indeterminate、同点の場合は最も早いもの)を選ぶ。apply_tsa_revocation_indication(verify.rs:1622)— TSA チェーンの失効、033 のクロスフォーマット一貫性レイヤ。この積層は、判定レベルにおいて断定回避の規律を実現する。すなわち、非推奨のアルゴリズム、確認不能なタイムスタンプ、あるいは失効した TSA 証明書は判定を INDETERMINATE へ縮退させるが、暗号学的に破綻した署名というより硬い事実を決して上書きしない。
SubIndication(crates/pverify-core/src/report/etsi.rs)は単一のクローズドな Rust 列挙であり、report-schema.json(964–1031行目)の sub_indication の enum へ値ごとにミラーされている。自由形式のサブインディケーションは禁止されている——モジュールコメントは、意外な文字列が消費者に重大度を誤読させうると指摘している(憲章 §I)。値の追加は追加的な MINOR スキーマバンプであり、削除または再利用は MAJOR である(§14.8)。いくつかの非推奨バリアント(bridge_ca_required_unsupported、chain_constraints_failure、policy_processing_inconclusive、ocsp_only_revocation_pointers、not_yet_supported_in_v0.1)は、保存済みレポートのデシリアライズのためにワイヤ列挙の中に保持されているが、現行のバイナリではもはや出力されない。出力は aggregate_etsi / validate_path で抑制される。
ワイヤ文字列は逐語的に取られ、正確にラウンドトリップしなければならない。2つは意図的に不規則であり、専用のテストによって固定されている。すなわち、camelCase 化された messageDigest_mismatch(sub_indication_camel_case_preserved)と、not_yet_supported_in_v0.1 におけるリテラルなバージョンのドット(sub_indication_version_dot_preserved)である。
以下の表は、現在出力されているすべてのサブインディケーションを、重大度クラスと、それを発生させるチェックごとにグループ化したものである。クラスは、aggregate_etsi とその各レイヤが値をどのようにルーティングするかの性質であって、列挙そのものの性質ではない。
| ワイヤ文字列 | 発生元 |
|---|---|
chain_signature_failed |
チェーンステップの証明書署名を検証して失敗した。 |
signed_attrs_signature_failed |
署名者の signedAttrs/コンテンツ署名を検証して失敗した。 |
messageDigest_mismatch |
RFC 5652 §11.2 の messageDigest ≠ 再計算したコンテンツダイジェスト。 |
signer_certificate_revoked |
CRL が署名者リーフを失効と報告した(RevokedOnCrl)。 |
signer_certificate_revoked_via_ocsp |
署名者リーフに対する OCSP CertStatus = revoked(RevokedOnOcsp)。 |
ocsp_responder_signature_invalid |
OCSP レスポンスの署名を検証して失敗した(改ざんと等価)。 |
signer_certificate_not_found |
SignerInfo.sid が埋め込み証明書のいずれにも一致しなかった(署名者すり替え)。 |
signing_certificate_digest_mismatch |
ESS signing-certificate(v2) の certHash ≠ 署名者証明書(RFC 5035)。 |
signing_certificate_issuer_serial_mismatch |
ESS issuerSerial が存在するが ≠ 署名者証明書の発行者+シリアル。 |
content_type_mismatch |
content-type 署名属性 OID ≠ eContentType(RFC 5652 §5.3)。 |
content_type_missing |
signedAttrs は存在するが content-type 属性が欠如している。 |
archive_timestamp_imprint_mismatch |
archive-time-stamp-v3 インプリント再計算 ≠ 宣言値(EN 319 122-1 §6.3.4)。 |
xades_reference_digest_mismatch |
ある ds:Reference のダイジェスト ≠ 再計算した正規化ノードセット。 |
xades_signing_certificate_mismatch |
XAdES SigningCertificateV2 の CertDigest ≠ 検証に用いる証明書。 |
jades_payload_digest_mismatch |
JAdES JWS 署名 ≠ 計算した署名入力。 |
jades_signing_certificate_mismatch |
JAdES x5t#S256 / x5t#o ダイジェスト ≠ 署名者証明書。 |
asic_data_object_digest_mismatch |
ASiC-E CAdES マニフェストの DigestValue ≠ 再計算した ZIP エントリダイジェスト。 |
pdf_byte_range_does_not_cover_eof |
PAdES /ByteRange が最後の %%EOF の後のバイトを未署名のまま残している。 |
pdf_byte_range_malformed |
PAdES /ByteRange が構造的に不正である。 |
key_usage_missing_required_bit |
必要な KeyUsage ビットが欠如している。 |
signer_certificate_expired_at_time / signer_certificate_not_yet_valid_at_time / anchor_expired_at_time |
該当する基準時刻における有効期間の失敗。 |
| ワイヤ文字列 | 発生元 |
|---|---|
signature_algorithm_unsupported |
認識されない署名 OID または未対応の ECDSA 曲線パラメータ(実行しなかった)。 |
public_key_malformed |
アルゴリズムは認識されたが、SPKI 鍵オクテットを検証鍵へデコードできない。 |
crypto_constraints_failure_no_poe |
オプトインのアルゴリズムポリシー: アルゴリズム/鍵がオブジェクトの VRT において許容されなくなった。 |
revoked_no_poe |
あるタイムスタンプの TSA チェーンが、そのオブジェクトの VRT において失効と確認された(033)。 |
signer_identifier_malformed |
SignerInfo.sid が不正/欠如——署名者を識別できない。 |
signing_certificate_digest_not_evaluable |
ESS certHash のハッシュアルゴリズムが計算不能。 |
content_type_not_evaluable |
content-type は存在するがパース不能、または eContentType が利用不能。 |
revocation_not_checked_offline |
--offline かつ使用可能な埋め込み/CDP の失効情報がない。ソケットは開かれない。 |
ocsp_response_stale |
OCSP nextUpdate が過去、欠如(オンライン/オフライン)、または producedAt が未来。 |
ocsp_responder_chain_unauthorised |
RFC 6960 §4.2.2.2 の指定署名者の認可が失敗した。 |
ocsp_unknown_status |
OCSP unknown、ノンス不一致、不正な DER、または非 successful のステータス。 |
ocsp_responder_unreachable |
すべての OCSP 試行が一過的に失敗した(オンライン)、またはバンドルミス(from_bundle)。 |
crl_stale_no_fresher |
CRL nextUpdate が過去であり、より新しい CRL が利用不能。 |
unsupported_indirect_crl / unsupported_cdp_protocol |
間接CRL/非HTTP の CDP スキームが未対応。 |
indeterminate_named_constraint |
名前制約の状態機械がチェーンを拒否した(RFC 5280 §4.2.1.10)。 |
name_constraints_unsupported_form / name_constraints_malformed |
未対応の GeneralName 形式/不正な拡張。 |
indeterminate_policy_rejected |
ポリシー処理がチェーンを拒否した(RFC 5280 §6.1.5)。 |
policy_mappings_malformed |
policyMappings が不正(例: anyPolicy をマップする)。 |
archive_timestamp_imprint_unsupported_algorithm |
archive-TS-v3 インプリントハッシュ OID が対応カタログ外。 |
xades_unsupported / xades_unsupported_profile / xades_unsupported_canonicalization / xades_signer_certificate_unavailable / xades_timestamp_imprint_mismatch |
XAdES のスコープ外パッケージング/C14N/証明書/タイムスタンプインプリント。 |
jades_unsupported_profile / jades_unsupported_serialization / jades_signer_certificate_unavailable |
JAdES のスコープ外ベースライン/シリアライゼーション/証明書。 |
asic_data_object_missing / asic_unsupported_container |
ASiC のカバー対象オブジェクトが欠如/認識されない ZIP コンテナ。 |
cms_multi_signer_unsupported |
CMS SignedData が複数の SignerInfo を運んだ——判定を拒否した。 |
監査上の微妙な点が1つある。signature_algorithm_unsupported と *_signature_failed の値は意図的に区別されている。前者は「検証を実行しなかった」(INDETERMINATE)を意味し、後者は「実行して失敗した」(TOTAL_FAILED)を意味する。ブランチ 029 はこの境界を堅牢化した。SignedAttrsSignature::Failed は今や、失敗箇所で刻印された型付きの cause(SignedAttrsFailureKind、report/mod.rs)を運び、aggregate_etsi は人間可読の reason 文字列を再パースするのではなく型付きの値でルーティングする——これにより、未対応アルゴリズムを偽造の主張へ密かに転倒させうる脆い結合が閉じられた。cause は #[serde(skip)] であるため、ワイヤ形式は 029 以前のレポートとバイト単位で同一である。
validation_objects[] — 資料インベントリとその出所validation_objects[](ブランチ 016)は、実行が実際に参照したすべての生資料——証明書、CRL、OCSP レスポンス、タイムスタンプトークン、および署名されたコンテンツ——の、重複排除され決定論的にソートされたインベントリである。各資料は SHA-256 によって固定され、ValidationObjectKind でタグ付けされ、バイトの出所を記録する origin[] 配列を運ぶ。これは derive_validation_objects(crates/pverify-core/src/report/validation_objects.rs)によって、すでに組み立てられた Report ツリーに対する純粋な導出パスとして構築される。これが出力するすべてのダイジェストは、レポート上の他所にすでに存在するフィールドであるため、FR-007(「ハッシュの乖離なし」)は構成上成立し、導出は &Report のみを読む——生バイトの副入力なし、ネットワークなし、新たな暗号なし。
pub struct ValidationObject {
pub sha256: String, // 64 lowercase hex, primary dedup key
pub kind: ValidationObjectKind,
pub origin: Vec<ValidationObjectOrigin>, // ≥1, sorted by decl order, deduped
pub usage: Vec<ValidationObjectUsage>, // omitted when empty
pub parsed: bool, // omitted when true (the common case)
}ValidationObjectKind(クローズド列挙): certificate、crl、ocsp_response、timestamp_token、signed_content。
決定性は構造的である(憲章 §II、SC-003)。導出は HashMap を一切使用しない。ウォーク中に暫定的なオブジェクトを収集し、64桁16進のダイジェストによって BTreeMap で重複排除し(dedup_and_sort)、各オブジェクトの origin[] と usage[] を和集合化してソートし、parsed フラグを寄与者全体で AND し、最後にベクタ全体を (kind, sha256) でソートする。したがって、同一入力に対する2回の実行はバイト単位で同一の配列を生成する。ダイジェストはそのバイトを一意に識別するため、kind は衝突をまたいで不変であり、最初のものが勝つ(dedup_and_sort コメント)。スキーマは sha256 を ^[0-9a-f]{64}$ に、origin を minItems: 1 に制約する(report-schema.json 1044–1055行目)ため、オブジェクトが空の出所集合を持つことは決してない(origin_is_never_empty テスト)。
ValidationObjectOrigin と 034 の PAdES 追加ValidationObjectOrigin(validation_objects.rs)はクローズド列挙であり、その宣言順が origin[] 配列のソート順である。
| バリアント | ワイヤ文字列 | 意味 |
|---|---|---|
SignatureEmbedded |
signature_embedded |
署名の内部に運ばれたバイト(CMS の証明書/CRL/OCSP、eContent、ByteRange、Adobe revocationInfoArchival)。 |
Bundle |
bundle |
--from-bundle ディレクトリから読み込まれた。 |
FetchedOnline |
fetched_online |
ネットワーク越しにライブで取得された(オンラインモード)。 |
TrustAnchor |
trust_anchor |
終端アンカー(ホストがロード。決して /DSS 証明書ではない)。 |
SuppliedInput |
supplied_input |
供給されたデタッチドコンテンツ。 |
PdfDss |
pdf_dss |
034: PDF 文書レベルの /DSS プール、DSS-global フォールバック。 |
PdfVri |
pdf_vri |
034: 署名の /Contents の SHA-1 をキーとする /VRI エントリ経由で到達。 |
2つの PAdES 出所は、宣言順——したがって origin[] のソート順——が 034 以前のあらゆる配列をバイト単位で安定に保つよう、まさに SuppliedInput の後に追加された(FR-008/Q4)。これらは、並行する追加的な出所フィールド CrlSummary.pdf_source と OcspAttempt.pdf_source(report/mod.rs)を通じてインベントリへ射影される。各々は Option<PdfRevocationSource>(pdf_dss / pdf_vri)であり、存在しないときは省略される。これらのフィールドは、取得された、バンドルされた、または CMS 埋め込みのレコードのすべてにおいて存在しないため、/DSS//VRI/revocationInfoArchival を持たない PDF は、schema_version を除いて 034 以前のレポートとバイト単位で同一である。
失効情報の出所導出は微妙な部分である(validation_objects.rs の collect_revocation)。PDF 文書の出所タグは、モードベース/OcspSource のヒューリスティックに優先する(R-7)。すなわち、CRL または OCSP レスポンスが pdf_source: PdfDss/PdfVri を運ぶ場合、その出所は実行モードにかかわらず PdfDss / PdfVri である。これが重要なのは、DSS/VRI 由来の CRL/OCSP が内部的に埋め込み失効チャネルに乗るためである(さもなくばヒューリスティックに signature_embedded とタグ付けされてしまう)。pdf_source が存在しない場合にのみ、導出は実行モードのヒューリスティックにフォールバックする。すなわち、from_bundle ⇒ bundle、source_uri を持たない CRL ⇒ signature_embedded、それ以外 ⇒ fetched_online、そして OCSP については OcspSource 列挙を介して同様に行う。これはレポートにおいて出所が直接既知ではなく推論される唯一の箇所であり、推論はフィールドごとに文書化されている。
DSS 用語集の定義と整合する、意図的なスコープ境界に1点注意せよ。DSS /Certs は中間/リーフ証明書のみであり、トラストアンカーへ昇格されることは決してない(Q7/FR-005)。TrustAnchor 出所は、chain_result.terminating_anchor_fingerprint によって名指されるホストロードのアンカーのために予約されている。
ValidationObjectUsage の相互参照存在する場合、usage[] は資料が果たした役割を記録する(ブランチ 016 US2)。これは kind 上で内部タグ付けされた列挙である(ValidationObjectUsage、validation_objects.rs)。
{"kind":"certificate_role","role": signer|ca|trust_anchor} — リーフ(チェーンステップ 0)が署名者、終端アンカーがトラストアンカー、それ以外のすべてのステップが CA である。{"kind":"revocation_target","evaluated_subject_fingerprint": "<64hex>"} — CRL/OCSP が評価対象としたリーフ。{"kind":"timestamp","timestamp_role": signature_timestamp|content_timestamp|archive_timestamp}。usage[] は空のとき省略され、US1 のみの消費者はバイト単位で同一のオブジェクトを見る。本スライスでは意図的に OcspResponder の証明書役割は存在しない(C1)。OCSP レスポンダ証明書は今日のレポートで SHA-256 を運ばないため、役割を付加すべき certificate オブジェクトが存在しない——これは延期された追加的拡張である。
各 SignatureEntry(crates/pverify-core/src/report/mod.rs)は、1つの署名についての監査可能な、事実レベルの記録である。非オプショナルの背骨は次のとおりである。
| フィールド | 目的 |
|---|---|
format |
SignatureFormat — 検出された AdES レベル(§14.6.1)。 |
pdf_byte_range / pdf_covers_eof |
PAdES 専用。非 PDF では null。 |
content_source |
ContentSourceTag — embedded または detached。ダイジェストがどのバイトソースに対して実行されたか。 |
chain_result |
構築された RFC 5280 パス: steps[]、terminating_anchor_fingerprint、bridge_attempts[]。 |
signed_attrs_check |
signedAttrs の存在、ダイジェストアルゴリズム OID、ダイジェスト一致、署名結果。 |
content_digest_check |
宣言された vs 計算されたコンテンツダイジェスト、加えて covered_content_sha256(016)。 |
timestamps[] |
TimestampToken — 各 signature/content/archive の TST とその TSA チェーン。 |
embedded_validation_data |
LT/LTA: 埋め込み証明書、CRL、アーカイブタイムスタンプ、アーカイブインディケーション。 |
vrt |
オブジェクトごとの検証基準時刻(031;第12章)。 |
etsi_indication |
署名ごとの判定(§14.3)。 |
いくつかのフィールドは、存在しないときにスキップされる追加的オプショナルであり、それらを生成しないフォーマットのバイト同一性を保持する。すなわち、container / asic_manifest_checks[] / asic_conformance_deviations[](ASiC、ブランチ 020)、signer_identifier_check / signing_certificate_binding(CMS 署名者バインディング、ブランチ 025)、content_type_check(RFC 5652 §5.3、ブランチ 026)、および algorithm_validity(オプトインポリシー、ブランチ 032。--algorithm-policy のときのみ Some(..) となるため、デフォルトオフのレポートは schema_version を除いて 1.3.0 本体とバイト単位で同一である)。
ブランチ 016 はまた、derive_validation_objects が &Report のみを読めるようにする、3つの「以前は欠けていた」SHA-256 フィールドを追加した。すなわち、ContentDigestCheck.covered_content_sha256(signed_content オブジェクトのダイジェスト——カバーされた生バイトであり、署名アルゴリズムの computed_digest とは別物)、TimestampToken.token_sha256、および OcspAttempt.response_sha256 である。各々は空/none の場合 skip_serializing_if され、それらを欠いていた保存済みレポートのバイト同一性を保持する。
format に表出される AdES レベルの語彙SignatureFormat(report/mod.rs)は誠実なスコープ宣言である。出力される値と、各々が pverify が実際に何を検証したかについて何を含意するかは、次のとおりである。
CAdES-BES / CAdES-T(signature-time-stamp)/ CAdES-LT / CAdES-LTA(§6.3.4 インプリント再計算を伴う archive-time-stamp-v3)。/DocTimeStamp に対する PAdES-B / PAdES-B-T / PAdES-B-LT / PAdES-B-LTA。ブランチ 034 以降、LT/LTA レベルは文書レベルの /DSS//VRI と Adobe revocationInfoArchival を用いてオフラインで確定的である。XAdES-B-B(enveloped、Exclusive C14N)、XAdES-B-T、XAdES-B-LT、XAdES-B-LTA(ETSI TS 101 903 v1.4.2 Annex A.1.5 に基づく ArchiveTimeStamp インプリント検証、ブランチ 036。インプリント = 後続 ArchiveTS node-set を subtract した ds:Signature の exc-C14N)。デタッチド/エンベローピングのパッケージングまたは非排他 C14N は、保持された XAdES-unsupported 値と対応するサブインディケーションを介して拒否される。JAdES-B-B / JAdES-B-T / JAdES-B-LT / JAdES-B-LTA(JWS JSON Serialization; ブランチ 037)。sigD デタッチド、JWS Compact、および rfsTst/tstVd は JAdES-unsupported を介して拒否される。ASiC(EN 319 162)は format に対して直交している。内側の署名は入れ子の CAdES/XAdES 値を保持し、container フィールドが ASiC-S / ASiC-E を記録する——ASiC×format の結合バリアントは存在しない。
各チェーンステップ上の RevocationRecord(report/mod.rs)は、クローズドな RevocationOutcome 列挙、CDP の URI、オプショナルな CrlSummary、オプショナルな失効時刻/理由、およびオプショナルな OcspAttempt を運ぶ。RevocationOutcome の語彙(GoodOnCrl、RevokedOnCrl、GoodOnOcsp、RevokedOnOcsp、IndeterminateRevocationOffline を含む Indeterminate* ファミリ)は、署名者チェーン、TSA チェーン(033)、および DSS 由来(034)のチェックによって逐語的に再利用される。すなわち、DSS 由来の CRL/OCSP は新たな値なしに既存の結果語彙を生む(Q2/FR-007)。誠実なオフライン結果 IndeterminateRevocationOffline は、オフラインモードにおける以前の不誠実な IndeterminateOcspUnreachable の出力を置き換えた(憲章 §VII / §I、オフラインの誠実性)。
OcspAttempt(report/mod.rs)は最も深い診断レコードである。これは RFC 6960 の事実を逐語的に表出する。すなわち、source(OcspSource: ステープルされた埋め込み値 vs AIA フェッチ online/from-bundle)、レスポンダ URI と順序付けされた attempted_uris[]、responder_subject_dn、producedAt(camelCase のワイヤ名は意図的であり、§I に従ってそのまま表出される)、responder_signature_alg OID、および4つのクローズド列挙の結果——OcspFreshness、OcspNonceMatch、OcspResponderAuthorisation(RFC 6960 §4.2.2.2 の指定署名者の認可)、および OcspResponseStatus(その値 0..6 は RFC 6960 §4.2.1 の ASN.1 列挙をミラーし、加えてパーサ/トランスポート障害のための pverify 内部の malformed/unreachable 擬似ステータスを持つ)である。鮮度とノンス一致における from_bundle/embedded の例外規定は、文書化された再現性の例外を明示的に名指す。
TimestampToken は、TSA のサブジェクト DN、入れ子の tsa_chain_outcome(TSA チェーンのための完全な ChainResult)、gen_time、メッセージインプリントの一致、token_sha256、そして——アーカイブタイムスタンプの場合のみ——再計算/宣言されたダイジェストとスタンプごとの ImprintOutcome(match/mismatch/unsupported_algorithm)を伴うオプショナルな ArchiveTimestampImprintRecord を運ぶ。
report-schema.json の契約規範的なワイヤ契約は specs/001-verify-cades-pades/contracts/report-schema.json である——これは、すべてのクローズド列挙と Rust 型の必須フィールド集合を値ごとに固定する JSON Schema である。憲法上の不変条件は "const" 制約としてエンコードされており(24–27行目)、schema_version、path_validation_phase、bridge_ca_supported、dn_match_method が逸脱したレポートはスキーマ検証に失敗する。EtsiIndication 定義(955–1037行目)は indication、sub_indication、failing_locus の3つすべてを要求し、sub_indication と failing_locus は oneOf [null, …] として型付けされる。sub_indication の enum(969–1030行目)は権威ある一覧であり、保持されているが出力されない非推奨値を含む。ValidationObject(1039–1062行目)は sha256/kind/origin を要求し、sha256 を64桁16進に、origin を pdf_dss/pdf_vri を含む7値の出所列挙のソートされた部分集合に制約する。
スキーマと Rust 型の関係は、維持される 1:1 のミラーである。規律(etsi.rs、schema.rs、report/mod.rs に文書化されている)は、いかなるリネーム、追加的値、または新フィールドも、Rust の列挙/構造体とスキーマの両方をともに更新しなければならないというものである。乖離はリリースを妨げるバグである。etsi.rs と report/mod.rs のいくつかのテスト(round_trip_sub、バリアントごとの *_round_trips テスト、validation_objects_roundtrip_and_enum_wire_strings)が正確なワイヤ文字列を固定するため、偶発的なリネームは CI で失敗する。
SCHEMA_VERSION はレポート形状の SemVer である(crates/pverify-core/src/report/schema.rs、現在 "1.10.0")。バージョニング規則は厳格であり、プロジェクトの中心的な後方互換性のレバーである。
SubIndication、RevocationOutcome、ValidationObjectOrigin、…への)、またはオプショナル/存在しないときスキップされるフィールドの追加、または最後に追加される新たな必須トップレベルフィールドの追加。const の値の変更。したがって、非推奨値が削除されることは決してない——それは保存済みレポートのデシリアライズのために保持され、単にもはや出力されないだけであり、これにより変更が MINOR に保たれる(bridge_ca_required_unsupported、IndeterminateOcspOnly、NotYetSupportedInV01 などにわたって見られる非推奨パターン)。
schema.rs の SCHEMA_VERSION のドキュメントコメントから読み取れる 1.x の系譜は次のとおりである。
| バージョン | ブランチ | 追加的変更 |
|---|---|---|
| 1.0.0 | v0.1 | 初期レポート形状。 |
| 1.1.0 | v0.5 | bridge_attempt → bridge_attempts[](構造的フィールドリネーム、結合バンプとして扱う)。 |
| 1.2.0 | 016 | 新たな必須トップレベル validation_objects[] + *_sha256 診断フィールド。 |
| 1.3.0 | 031 | 新たな必須署名レベル vrt ブロック。 |
| 1.4.0 | 032 | 追加的オプショナル algorithm_validity フィールド + crypto_constraints_failure_no_poe 値。 |
| 1.5.0 | 033 | 新たな値 revoked_no_poe。ステップごとの TSA 失効フィールドがポピュレートされた(形状ではなく内容)。 |
| 1.6.0 | 034 | 2つの新たな ValidationObjectOrigin 値(pdf_dss/pdf_vri)+ CrlSummary/OcspAttempt の pdf_source(存在しないとき省略)。 |
| 1.7.0 | 035 | 新たなオプショナル EtsiIndication.findings[](FindingEntry オブジェクト)— 独立して観察された全ての問題を並列に表出。単一発見時はバイト同一性を保つため存在しないとき省略。 |
| 1.8.0 | 035-ocsp-retry | 追加的オプショナル OcspAttempt.request_der_hex(hex エンコードされた OCSPRequest DER、レスポンダが到達不能のときのみ存在。JS ホストがこれを /ocsp プロキシ経由で POST してリトライできるようにする)。埋め込み応答・バンドル参照・取得成功ケースでは省略——1.8.0 以前の全レポートに対してバイト同一性を保持。 |
| 1.9.0 | v1.2.0(GPKI DirName CDP + CdpEntry) | フィールドリネームを伴う破壊的変更:RevocationRecord.cdp_uris: Vec<String> を cdp_entries: Vec<CdpEntry>(kind・value・fetchable: bool を持つオブジェクト配列)に置き換え。DirectoryName 形式の CDP エントリを fetchable:false で表出(従来は無音に省略)。aia_ocsp_uris: Vec<String> を RevocationRecord に追加。cdp_uris のリネームのため MINOR バンプ(互換性注記:未知フィールドを無視する消費者は無影響、厳格な消費者は cdp_uris→cdp_entries への移行が必要)。注: v1.3.0(036-xades-blta)では SignatureFormat::XadesBLta("XAdES-B-LTA")と VerificationRequest.pdf_last_revision_end(LGPKI2 PAdES 修正)が追加されたが、スキーマバンプなし——いずれも追加的・後方互換。 |
| 1.10.0 | 037(ETSI JAdES B-T / B-LT / B-LTA) | SignatureFormat に3つの新値(JAdES-B-T、JAdES-B-LT、JAdES-B-LTA)、ならびに従来スキーマ未収録の XAdES-B-LTA および全 LTV-JWS バリアントを追加。037 以前の ETSI JAdES B-T+ 署名を含まないレポートは schema_version 文字列を除いて 1.9.0 本体とバイト同一(追加的MINOR)。 |
これらのバンプの規定的特性は、オフ/不在の機能における判定のバイト同一性である(憲章 §II)。追加的機能は、その入力が不在であるか無効化されている場合、schema_version 文字列を除いてレポート本体をバイト単位で同一に保たねばならない。header_emits_constitutional_invariants と validation_objects_roundtrip_and_enum_wire_strings テストは schema_version == "1.10.0" をアサートする。report-schema.json ミラーは24行目で同じ値を固定する。schema_version 以外の4つの憲法上の不変条件(path_validation_phase、bridge_ca_supported、dn_match_method)はブランチ 016、031–037 によってローテートされなかったことに注意せよ——これらは RFC 5280 §6 の能力サーフェスを追跡しており、それは v0.6 以来変化していない。
CLI 終了コード(crates/pverify-cli/src/main.rs、モジュール exit)は、意図的に検証判定から切り離されている。
| コード | 定数 | 意味 |
|---|---|---|
| 0 | SUCCESS |
検証が完了まで実行された。判定はレポートの中にある。 |
| 2 | INVOCATION_ERROR |
ファイルの欠如/読み取り不能、フラグの競合、不正な --at、バンドル違反。 |
| 3 | RUNTIME_ERROR |
完全なレポートの生成を妨げた実行途中の I/O 障害。 |
| 4 | INTERNAL_ERROR |
レポート直列化器が表現不能な形状を生成した(リリースを妨げるバグ)。 |
決定的な契約(FR-023、exit::SUCCESS に文書化されている)は、CLI が TOTAL_FAILED と INDETERMINATE を含む報告されたあらゆる判定に対して 0 で終了することである。非合格のインディケーションが非ゼロの終了へ縮退させられることは決してない。非ゼロのコードは呼び出しおよび実行時のエラーのために予約されている。verify ディスパッチがこれを確認する。すなわち、XAdES/ASiC/JAdES 入力が誠実な *_unsupported 事実に着地した場合でも、または署名が TOTAL_FAILED である場合でも、パスはレポートをレンダリングし exit::SUCCESS を返す(main.rs、~707–734行目のディスパッチコメントと799行目の最終的な render::render(...) → SUCCESS)。したがって:
自動化処理は終了コード
0を署名の有効性として扱ってはならない。 判定は JSON 出力のsignatures[*].etsi_indication.indicationにある。
これは JNSA 適合性ステートメント(docs/jnsa-conformance-statement.md、Exit-code caveat)に逐語的に再記述されている。
JNSA デジタル署名検証ガイドラインは VALID / INVALID / INDETERMINATE で語る。pverify は ETSI EN 319 102-1 のインディケーションを報告する。docs/jnsa-conformance-statement.md から取られたマッピングは次のとおりである。
| JNSA 用語 | pverify indication |
検証者(信頼当事者)の読み取り |
|---|---|---|
VALID |
TOTAL_PASSED |
pverify が実行したすべてのチェックが肯定的に返った——実装上の制約の範囲内で。入力クラスに対していずれかの必須(Mandatory)チェックが未実装である場合、JNSA VALID とは等価でない。 |
INVALID |
TOTAL_FAILED |
pverify が実行したチェックのうち少なくとも1つが否定的に返った(検証して失敗、失効証明書、…)。 |
INDETERMINATE |
INDETERMINATE |
利用可能な情報が判断を下すには不十分であった(オフライン + 埋め込み失効情報なし、未対応アルゴリズム、失効した TSA、…)。 |
適合性ステートメントの TOTAL_PASSED のもとの注意書きは、調達にとって荷重を負うものである。TOTAL_PASSED は、pverify が実行したチェックの範囲においてのみ肯定的な結果であり、その文書の M/E/O 適合性マトリクスに列挙されている。検証者はインディケーションとマトリクスの両方を読まねばならない——例えば、署名者証明書の失効は P(部分的: オフライン/from-bundle は誠実に INDETERMINATE へ縮退する)として記録され、アルゴリズム妥当性は P(--algorithm-policy 経由のオプトイン)である。レポート自身の path_validation_phase、weak_algorithms[]、vrt[]、algorithm_validity、および validation_objects[] フィールドは、まさにこの「実際に何がチェックされたか」という問いがソースコードを読むことなく JSON から回答可能であるために存在する。
終了コードを判定に結合させることは、3値の ETSI 語彙をプロセス境界において2値の合否へ崩壊させることをバイナリに強制することになり、これはまさに 憲章 §I がツールに行うことを禁じている業務上の判定にほかならない。INDETERMINATE の「失効した TSA 証明書」(revoked_no_poe)や「オフライン、失効未検証」(revocation_not_checked_offline)は、検証者の知識状態についての事実であって、署名が不良であるという主張ではない。追加的な帯域外の証拠を持つ検証者は、それを正当に許容可能と扱ってよい。判定を構造化されたレポートにおいてのみ表出し——終了コードを「ツールが実行されたか」のために予約する——ことは、そのポリシー上の決定を検証者の手に保つものであり、これこそが事実報告型の検証器の眼目のすべてである。
本章では、pverify が 信頼できない、攻撃者の影響下にある 入力を処理する際に維持するセキュリティ態勢について述べる。本ツールの中核的なユースケース、すなわち公開された AdES 文書を GPKI/JNSA および eIDAS/ETSI のコーパスに対してオンラインで検証することは、設計上敵対的である。すなわち、運用者は第三者から手渡されたファイルに対して pverify を実行し、検証器はそのファイルの作成者が完全に制御している URI をたどり、バイト構造を解析することが期待される。したがって、脅威モデルは、検証対象の署名、すべての埋め込み証明書、すべての配布 URI、およびすべてのコンテナエンベロープを、別段の証明がなされるまで敵対的なものとして扱う。
本章は、相互に連動する 5 つの制御を扱う。
これらの制御が依拠するアーキテクチャ上の基盤、すなわち I/O を行わない no_std カーネル、ホスト/カーネルのケイパビリティ境界、および「断言できないことは断言しない(cannot affirm)」の規律については、第3章(アーキテクチャ概観)および第11章(失効検証)で述べる。本章は、それらと同じ境界に対する 敵対的 な観点を扱う。
pverify の設計は、ホストクレート(pverify-cli、web/pverify-wasm、および取り込みクレート pverify-eutl / pverify-aatl / pverify-asic / pverify-xades / pverify-jades)と、I/O を行わない検証カーネル pverify-core との間に、単一の明確な信頼境界を引く。カーネルは すべての 暗号処理とポリシー評価を実行するが、ソケットを開いたり、ファイルを読んだり、クロックを読んだりすることは決してない。カーネルが参照する唯一のホストケイパビリティは、Clock、RevocationFetcher、TrustAnchorStore トレイト(crates/pverify-core/src/traits.rs)を経由する。これが、「敵対的な URL をフェッチする」あるいは「敵対的なコンテナを解析する」攻撃面が完全にホストクレート内に存在し、そこでゲートされる構造的な理由である。
敵対者が制御する入力と、各制御が保護する資産は以下のとおりである。
| 敵対者が制御する入力 | 脅威 | 制御 | 節 |
|---|---|---|---|
| 署名の証明書内の CDP / OCSP / LDAP URI | 内部サービス、ループバック、クラウドメタデータへの SSRF | アドレスネットガード(解決後ピン留め) | §15.2 |
公開 CDP ホストからの HTTP リダイレクト(Location) |
オープンリダイレクトによる内部アドレスへの SSRF バイパス | ホップごとのガード、手動リダイレクト追従 | §15.2.4 |
| ディスク上の署名/デタッチドコンテンツ/トラステッドリスト XML ファイルのサイズ | ローカルメモリ枯渇 DoS | read_bounded の上限 |
§15.3.1 |
| フェッチした CRL / OCSP レスポンス本体のサイズ | リモートメモリ枯渇 DoS | フェッチ本体の上限(take(cap+1)) |
§15.3.2 |
| ASiC ZIP コンテナ(エントリ数、展開後サイズ、エントリ名) | Zip 爆弾 DoS、パストラバーサル書き込み | エントリ/サイズ予算、enclosed_name による拒否 |
§15.3.3 |
XAdES 文書内の ds:Signature の数 |
多数の署名に対する C14N による CPU/メモリ DoS | 文書あたりの署名数上限 | §15.3.4 |
| BER/DER 構造のネスト深さ/ノード数/長さオクテット | スタック枯渇、ヒープ膨張、整数オーバーフロー | BER パース予算 | §15.4 |
| 署名バイトそのもの | 偽造された「Good」判定 | コアにおける RFC 5280/5652/6960 検証。第5章〜第11章を参照 | — |
脅威モデルは、各制御が具現化する横断的な不変条件の小さな集合によって強制される。ここでは敵対的な形式で述べる。
カーネルに I/O を実行させることはできない。 pverify-core は #![no_std] + #![forbid(unsafe_code)] であり、ネットワーク、ファイルシステム、PDF、XML、ZIP、JSON のいずれのライブラリもリンクしない。scripts/cargo-tree-gate.sh は、lopdf、zip、flate2、miniz_oxide、ureq、pverify-eutl、pverify-aatl などが cargo tree -e normal -p pverify-core のグラフに決して入らないことを経験的に表明する。したがって、カーネルに到達する SSRF や zip 爆弾は構造上不可能である。カーネルは、ホストがすでに精査し境界付けたモデルに依存しないバイト構造しか見ることがない。
ブロックまたは拒否された操作は決して判定にならない。 すべての制御は、TOTAL_PASSED や TOTAL_FAILED をでっち上げるのではなく、誠実な INDETERMINATE へと縮退する(憲法 §I、「断言できないことは断言しない」)。ブロックされた SSRF フェッチは、到達不能な任意のエンドポイントと同じフェッチログの形(http_status: 0)で記録される。上限を超えるファイル読み込みや予算超過の BER パースは 起動エラー(ツールは診断とともに停止する)であって、サイレントな切り詰めの後に切り詰めたバイトを検証することはない。
安全な振る舞いがデフォルトである。 振る舞いを変えるスイッチは正確に 1 つ(--allow-private-networks)のみであり、デフォルトでオフであって、本番経路から cfg(test) による分岐は存在しない。pverify 自身のループバックテストハーネスも、運用者が用いるのと同じフラグを通じてオプトインする(crates/pverify-cli/src/net_guard.rs、モジュールドキュメント SC-004)。
レポートのバイト安定性が保たれる。 発火しないセキュリティ制御は、JSON レポート、およびその schema_version を、制御導入前の出力とバイト単位で同一のまま残す。021 ネットガードは CLI ホストクレートのみに触れた(schema_version は不変)。ブロック理由は stderr にのみ送られ、機械可読なレポートには決して送られない(docs/network-egress-and-ssrf.md、仕様 FR-009)。
本章の各制御は、2026-06-19 のペネトレーションテストパス(包括 issue #81)への対応として着地し、2026-06-20 のセキュリティレビュー(docs/security-review-2026-06-20.md、docs/review-remediation-2026-06-20.md)で再検証された。ペンテストの所見はスライスに以下のように対応する。
bounded_io.rs + フェッチ上限(§15.3)。pverify-core/src/ber/mod.rs(§15.4)。2026-06-20 のレビューはさらに 4 つの検証正当性に関する修正(PAdES --required-policy の強制、複数 SignerInfo の誠実な拒否、不正な ESS と ESS 不在との区別、および型付き ETSI 失敗分類)を駆動した。これらはリソース/送出面ではなく判定の完全性に関わるものであり、それぞれ属すべき箇所、すなわち第5章(CAdES)、第6章(PAdES)、第9章(パス検証/ポリシー)、および §5.7 のインディケーション集約と結果モデル(第14章)で述べる。
2026-06-23 のセキュリティレビュー(docs/security-review-2026-06-23.md、issue #120)はブラウザデプロイメントに特化した 4 件の所見を特定した。
TOTAL_PASSED を生成するソーシャルエンジニアリングベクターが成立していた。修正: ユーザーが「カスタムトラスト」トグルを明示的に有効にした場合のみ追加するよう変更。有効時は判定結果に警告バナーを表示(§15.7)。algorithm_policy: "default" を送信しておらず、本番サイトで CRYPTREC 一次ポリシが無効のままになっていた。修正: web/site/app.js の buildUploadRequest() でこのフィールドを常時送信するよう変更。/crl エッジプロキシが redirect: "follow" を使用しており、許可リスト済み CA ホストにオープンリダイレクトがあると許可リスト外にエスケープできた。修正: 手動リダイレクト追跡とホップごとのスキーム/ホスト再検証。LDAP ポートを {389, 636} に制限(§15.5)。web/site/_headers に CSP・Referrer-Policy・X-Content-Type-Options・Permissions-Policy を追加。全 HTML から Google Fonts リンクを削除しシステムフォントに切り替え(§15.7)。オンラインモード(デフォルト。--offline でも --from-bundle でもない)において、pverify verify は 検証対象の信頼できないアーティファクトから取り出した URI、すなわち証明書に埋め込まれた CRL 配布点(CDP)URI と OCSP / LDAP 機関情報アクセス(AIA)URI を逆参照することで失効検証を実行する。これらの URI は完全に攻撃者の制御下にある。細工された証明書は、その CDP を http://169.254.169.254/...(AWS/GCP IMDS のクラウドメタデータエンドポイント)、http://127.0.0.1:8080/(ループバックの管理サービス)、あるいは http://10.1.2.3/internal(RFC 1918 ホスト)に向けることができる。これらを盲目的にたどることは、運用者のマシンを SSRF の踏み台に変える。これは最高評価のペンテスト所見であった(#71、P1)。
スキーム許可リストだけ(021 以前の制御)では不十分である。それは file:// や gopher:// を止めるが、http://10.0.0.1/ は喜んでたどってしまう。CLI に対しては ホスト 許可リストも却下される。pverify は任意の CA からの署名を検証する汎用検証器であるため、信頼できる CDP ホストの固定リストは正当な検証を壊してしまう(仕様 021、コンテキスト)。したがって、CLI の制御は アドレスベース である。すなわち、ターゲットが非公開 IP アドレスに解決されるあらゆるフェッチを拒否する。(小さな固定のトラストアンカー集合を提供する web エッジプロキシは、ホスト許可リストを 使用できる — §15.5 を参照。)
crates/pverify-cli/src/net_guard.rs::classify(addr: IpAddr) -> Option<BlockReason> は純粋な判定関数である。None はグローバルにルーティング可能な公開ユニキャストアドレスを許可し、Some(reason) はそれ以外のすべてをブロックする。これは std::net の述語のみに基づいて構築されており、新規依存はなく、また依然として不安定な IpAddr::is_global を意図的に 使わない(仕様 FR-010)。ブロックされる範囲と、各々が由来する標準は以下のとおりである。
| 範囲/アドレス | 分類 | 標準 |
|---|---|---|
127.0.0.0/8, ::1 |
Loopback |
— |
10/8, 172.16/12, 192.168/16 |
Private |
RFC 1918 |
fc00::/7(fd00:ec2::254 IPv6 IMDS を含む) |
Private |
RFC 4193 |
100.64.0.0/10(CGNAT) |
Private |
RFC 6598 |
198.18.0.0/15(ベンチマーキング) |
Private |
RFC 2544 |
169.254.0.0/16(169.254.169.254 IMDS を含む), fe80::/10 |
LinkLocal |
RFC 3927 / RFC 4291 |
0.0.0.0, 0.0.0.0/8, :: |
Unspecified |
RFC 1122 / RFC 4291 |
224.0.0.0/4, 255.255.255.255, ff00::/8 |
Multicast |
RFC 5771 / RFC 4291 |
192.0.2/24, 198.51.100/24, 203.0.113/24, 2001:db8::/32 |
Reserved(ドキュメント用/TEST-NET) |
RFC 5737 / RFC 3849 |
240.0.0.0/4(将来用予約) |
Reserved |
RFC 1112 §4 |
コードが明示的に扱い、監査者が確認すべき微妙な点が 2 つある。
IPv4 マップド IPv6 は分類前にアンラップされる。 classify は Ipv6Addr::to_ipv4_mapped() を呼び出し、一致した場合は埋め込まれた IPv4 形式を分類する。これにより、::ffff:169.254.169.254 や ::ffff:10.0.0.1 が v6 ルールをすり抜けてブロックされる v4 アドレスを密輸することを防ぐ(net_guard.rs、classify と ipv4_mapped_unwraps_to_ipv4_class テスト)。
標準ライブラリに述語が存在しない範囲は手動でチェックされる。 CGNAT(100.64/10)、ベンチマーキング(198.18/15)、0.0.0.0/8、240.0.0.0/4、IPv6 ユニークローカル(fc00::/7)、IPv6 リンクローカル(fe80::/10)、および IPv6 ドキュメント用(2001:db8::/32)は、is_reserved、is_global などが不安定であるため、明示的なオクテット/セグメントマスク(classify_v4 / classify_v6)で判定される。網羅的な must_block_all_non_public および must_permit_all_public ユニットテストは、2 つのクラウドメタデータアドレスを含むあらゆる代表的なリテラルにわたって分類器の振る舞いをピン留めする。
BlockReason は シリアライズされない。これは stderr 診断(reason_str)とユニットテストのみを駆動する。ブロックされたフェッチは既存の到達不能フェッチログの形を再利用するため、レポートとその schema_version はバイト安定のまま保たれる(FR-005 / FR-009)。
あらゆるアドレスチェックにおける TOCTOU の懸念は DNS リバインディングである。すなわち、チェック時 には公開アドレスに解決されるが、接続時 には非公開アドレスに解決されるホスト名である。pverify が選択した耐性レベルは 解決後ピン留め(resolve-then-pin) である(仕様 021 の明確化)。ホストを解決し、解決されたすべてのアドレスを分類し、再解決するのではなく、まさに分類されたアドレスに接続する。
HTTP と OCSP については、これはカスタム ureq::Resolver である GuardResolver をエージェントにインストールすることで強制される(net_guard.rs。crates/pverify-cli/src/modes/online.rs::HttpFetcher::new で AgentBuilder::resolver(...) を介して配線される)。GuardResolver::resolve は次のとおりである。
to_socket_addrs() で解決する。空の結果は フェイルクローズ である。すなわち、フェッチはブロックされ(BlockReason::Unresolvable)、io::Error が返される。SocketAddr の いずれか が非公開であれば、netloc 全体 が拒否される。公開アドレスと内部アドレスの混合に解決されるホストは、内部アドレスを漏洩させることを許してはならない(仕様エッジケース)。SocketAddr に接続する。チェックされたアドレス こそが 接続されるアドレスである。LDAP(crates/pverify-cli/src/modes/ldap.rs の手書き std::net クライアント)については、同じ規律が guard_ldap_addrs によって適用される。これは解決された集合を公開部分集合にフィルタリングし、connect_with_timeout がそれらのみにダイヤルする。意図的な非対称性に注意されたい。HTTP リゾルバはいずれかのアドレスが非公開であれば netloc 全体をブロックするが、LDAP フィルタは公開部分集合を保持してそれにダイヤルする。これは、LDAP クライアントがアドレスを順番に試行し、公開アドレスにピン留めすることで SSRF を止めるのに十分だからである(非公開アドレスには決してダイヤルされない)。この振る舞いは guard_ldap_addrs のドキュメントコメントに記載され、guard_ldap_addrs_drops_non_public によってピン留めされる。
最初の URL のみをチェックするガードは容易に打ち破られる。攻撃者は 302 Location: http://127.0.0.1/secret を返す 公開 サーバ上で CDP をホストする。pverify は ureq の自動リダイレクト追従を無効化(HttpFetcher::new 内の AgentBuilder::redirects(0))し、自身の制御下で手動でリダイレクトをたどる(online.rs::follow_redirects、上限 MAX_REDIRECTS = 5)ことで、これを打ち破る。
follow_redirects は各 Location についてリクエストを再発行するため、GuardResolver(およびスキームチェック)は、いかなる接続の前にも各ホップで新たに実行される。リダイレクト判定ロジックは純粋な next_redirect(current, status, location) として抽出されており、ソケットなしでユニットテスト可能である。監査者が留意すべき 2 つの性質がある。
next_redirect_refuses_non_http_location_scheme は、file://、ldap://、gopher:// へのリダイレクトが 決して 追従されないことを確認する。http/https のリダイレクト先のみがチェーンを継続する。リダイレクトは、最初の許可リストが拒否したスキームを再び開くことはできない。redirected_from フィールドに記録されるため、レビュアーはフェッチがたどった全経路を見ることができる。最初のスキーム許可リストは正確に {http, https, ldap} のままである(online.rs は、いかなる DNS 解決の前にも、それ以外を non_http_cdp_uri / non_http_ocsp_uri で拒否する。仕様 FR-011)。
--allow-private-networks(デフォルトでオフ)は、1 回の実行に対して 3 つのガードポイントすべてを「すべて許可」へとショートサーキットする。これはセキュリティ上の含意とともに --help に記載されており、信頼できるラボ/CI または内部 PKI のデプロイメントのみを意図している。決定的に重要なのは、これが 唯一 の振る舞いスイッチであるという点である。cfg(test) によるバイパスは存在せず、pverify 自身のループバックベースの失効ハーネスも、運用者が用いるのと正確に同じこのフラグを通じてオプトインする。これは、安全なデフォルトが本当にループバックをブロックすることの最も誠実な実証である(net_guard.rs モジュールドキュメント、docs/network-egress-and-ssrf.md)。
文書化され受容された残存リスク(docs/network-egress-and-ssrf.md)は以下のとおりである。
HTTP_PROXY / HTTPS_PROXY が設定されている場合、ureq は プロキシ ホストをガードを通じて解決する(したがってループバック/非公開のプロキシ自体は、フラグが設定されない限りブロックされる)が、ガードは、プロキシが pverify に代わって接続するアドレスを見ることができない。プロキシ自体の送出を制限することは運用者の仕事である。std のみであり、カーネルレベルのコネクタを持たない。HTTP/OCSP は ureq を すでに解決された アドレスにピン留めするため、その経路に対して競合する 2 度目の解決は存在しない。OS リゾルバを制御する病的な敵対者は、原理的には再解決を行うレイヤでリバインディングの競合を起こしうるが、現実的な細工 URI とオープンリダイレクトのケースは閉じられている。INDETERMINATE)であって、決して偽の「Good」ではない。--offline および --from-bundle の下では、ガードは何もしない。これらのモードはソケットを開かず、その出力は 021 以前とバイト単位で同一である。
pverify は、いくつかのクラスの信頼できないまたは大きな入力を取り込み、そのいずれもがメモリを枯渇させてローカル(CLI)またはリモート(フェッチしたレスポンス)のサービス拒否を引き起こしうる。これらの上限は意図的に余裕を持たせてある。すべての正当な署名、PDF、トラステッドリストはそれらを大きく下回るため、誠実な検証の結果は変わらない(ペンテスト #72。docs/security-review-2026-06-20.md、「Positive findings」)。
bounded_io.rs)crates/pverify-cli/src/bounded_io.rs::read_bounded(path, cap) は、すべての信頼できないローカル入力に対して、むき出しの fs::read を置き換える。これは 2 段階のガードを使用する。
metadata().len() の事前チェックが、いかなる割り当ての前にも、過大な 通常 ファイルを拒否する。File::take(cap.saturating_add(1)).read_to_end(...) の後に続く buf.len() > cap チェックである。+1 のセンチネルバイトは「ちょうど 1 バイト超過」を TooLarge エラーに変え、take の上限は stat が過小報告した場合でもバッファを境界付ける。stat と read の間に大きくなるファイル(TOCTOU)や、サイズが信頼できないパイプ/特殊ファイルでも、上限を超えて割り当てることはできない。デフォルトの上限は以下のとおりである。
| 入力クラス | 上限 | 定数/オーバーライド |
|---|---|---|
| 署名、デタッチドコンテンツ、トラステッドリスト XML | 256 MiB | DEFAULT_MAX_INPUT_BYTES。--max-input-mb でオーバーライド可能 |
| トラストアンカー/署名者証明書ファイル | 1 MiB | MAX_ANCHOR_BYTES |
バンドルマニフェスト(manifest.json) |
16 MiB | MAX_MANIFEST_BYTES |
上限の超過は BoundedReadError、すなわち 起動エラー であって、検証判定ではない。pverify は、その後に検証する入力をサイレントに切り詰めることは決してない(憲法 §I、事実報告)。エラーメッセージは、まれな過大だが正当なアーティファクトに対して、運用者が署名/コンテンツの上限を引き上げる方法(--max-input-mb)を伝える。
フェッチした失効情報は、crates/pverify-cli/src/modes/online.rs において同じ take(cap+1) パターンで境界付けられる。v1.3.0(2026-06-25 セキュリティレビュー)以降、すべての fetch_url 呼び出しサイトで take(cap+1) 読み込みの直後に body_too_large チェックが明示的に追加された。buf.len() > cap の場合、そのレスポンスはサイレントに切り詰めた本体をそのまま解析に渡すのではなく、到達不能なエンドポイントと同様に扱われる。パターンは次のとおりである。
let buf = resp.take(cap + 1).read_to_end()?;
if buf.len() > cap { return Err(FetchError::BodyTooLarge); }これにより、部分的な読み込みが本体が完全であるかのようにパーサへ転送される境界が閉じられた。
| フェッチ | 上限 | 定数 |
|---|---|---|
| HTTP/HTTPS 経由の CRL | 64 MiB | MAX_CRL_BYTES |
| HTTP/HTTPS 経由の OCSP レスポンス | 1 MiB | MAX_OCSP_BYTES |
上限を超えるレスポンスはフェッチログに誠実に記録され、失効の結果は、他のあらゆる失敗したフェッチと正確に同様に INDETERMINATE へと縮退する。敵対的なレスポンダは、無制限の本体をストリーミングすることで検証器を OOM にすることはできない。
OCSP リレーノンス転送(v1.3.0)。 Cloudflare Workers OCSP リレー(web/functions/ocsp.js)は、フロントエンドから受け取った OCSP の request_der_hex 全体を上流の TSA OCSP レスポンダへ転送するようになり、リレーレスポンスを WASM クライアントへ転送する前に RFC 8954 の 16 バイトノンスを検証するようになった。これにより、ノンスフィールドを無視するリレーが古いまたは再生されたOCSPレスポンスを転送しうるノンスストリッピングの窓が閉じられた。
ASiC コンテナ(EN 319 162)は ZIP アーカイブである。crates/pverify-asic/src/zip.rs::ZipArchive::parse のパーサは、zip 爆弾とパストラバーサルに対する多層防御の予算を強制する(ペンテスト #72。FR-017 / FR-018)。
MAX_ENTRIES = 4096。これより多くのエントリを宣言するコンテナは、いかなる展開の前にも ResourceBoundExceeded で拒否される。MAX_ENTRY_DECOMPRESSED = 128 MiB。宣言された file.size() がまずチェックされ、その後に実際の読み込みが境界付けられる。MAX_TOTAL_DECOMPRESSED = 256 MiB。各エントリは take(remaining_budget + 1) で読み込まれるため、虚偽の中央ディレクトリサイズ はメモリを枯渇させることができない。宣言されたサイズが小さくても、実際の読み込みは進行中の予算で上限が設けられ、オーバーフローすれば拒否される。ZipFile::enclosed_name().is_none()(zip クレートのエスケープ検出器)でチェックされ、さらに 念のため先頭の //\ および任意の .. パスコンポーネントを明示的に拒否する。アーカイブ全体はメモリにデコードされ、いかなるエントリもファイルシステムに書き込まれることはない(FR-002、「一時ファイルなし」)ため、トラバーサル名はライブの書き込みプリミティブではなく多層防御である。zip 依存はピュア Rust の deflate-flate2 / miniz_oxide バックエンドを使用し、pverify-asic を WASM ビルド可能かつネイティブ FFI フリーに保つ(zip.rs モジュールドキュメント、R-4)。
XAdES 文書内の各 ds:Signature は Exclusive C14N と参照処理を駆動する。これは、攻撃者が数千の署名を宣言する文書で増幅しうる作業である。crates/pverify-xades/src/parse.rs は、ds:Signature の数が MAX_SIGNATURES_PER_DOCUMENT = 32 を超える文書を、いかなる署名ごとの正規化の 前に 拒否し、XadesError::TooManySignatures を返す(ペンテスト #79)。32 はあらゆる正当な数を大きく上回りつつ、C14N の作業を境界付ける。
CAdES および PAdES の埋め込み CMS ペイロードは、crates/pverify-core/src/ber/mod.rs の寛容な手書きの BER/TLV ウォーカによって解析される(pverify-core の SignedData パーサがそれを通って降下する)。このコードは完全に攻撃者が制御するバイトにわたって no_std カーネル内で実行されるため、予算のない再帰降下は古典的なリソース枯渇のターゲットである。ネストされた構成型タグのチェーンはコールスタックを吹き飛ばしうるし、フラットまたはワイドなペイロードは無制限の Vec 成長を強制しうる。予算(ペンテスト #73)は両方を閉じる。
parse_ber_tlv は BerBudget { depth, nodes } を播種し、それを再帰に通す。
MAX_BER_DEPTH = 32 は構成型タグのネストに上限を設ける。実際の CMS — EncapContentInfo / SignerInfos / 証明書セットをラップする SignedData — は 10 レベルを十分に下回る深さで底をつく。この上限は、細工されたネストチェーンが コールスタック を枯渇させるのを止めるためだけに存在する。depth は降下時にデクリメントされ、リターン時に復元される ため、具体的にスタック深さを境界付ける。MAX_BER_NODES = 100_000 は、単一の入力から解析される TLV ノードの総数に上限を設ける。各ノードは Vec<u8> の値と Vec<BerTlv> の子ベクタを所有するため、これはフラット/ワイドなペイロードに対する ヒープ割り当て を境界付ける。nodes は、内部パースエラーが飲み込まれるサブツリーを含めて ツリー全体にわたって単調に消費される ため、総作業量は構造に関わらず境界付けられる。予算は各割り当ての 前に チェックされる。微妙だが重要な振る舞いがある。構成型ビットがセットされた TLV の子の解析が失敗したとき(実際の CMS のすべての構成型ラッパー — 例えば不透明なバイトをラップする OCTET STRING — がそのレイヤで整形式の子を持つわけではない)、内部エラーは 意図的に飲み込まれ、children は空のまま残されるが、飲み込まれたサブツリーが消費したノード予算は 払い戻されない。これは、飲み込まれたサブツリー内に高コストの構造を埋め込む敵対者に対してすら総作業量を境界付けつつ、パーサを実世界の CMS に対して寛容に保つ(parse_ber_tlv_budgeted、構成型タグ分岐のコメント)。
いずれかの上限を超えると Error::Ber("BER nesting too deep") / Error::Ber("BER node budget exceeded") を返す。エラー型は意図的に &'static str のみである。上流の civ ソース由来の動的な「exceeds remaining data N」の詳細は、パースのホットパスでの割り当てを避け、バイトオフセットがログ面に漏洩するのを防ぐために、意図的に落とされた(ber/mod.rs モジュールドキュメント、R-001)。
ヘッダ長ヘルパは、エンコーディングレイヤで予算を補強する。parse_tlv_total_length と skip_tlv_header は、0x83(3 長さオクテット ⇒ 16 MiB)までの確定形式の長さオクテットのみをサポートする。これより長い長さエンコーディング(0x84+)は、複数ギガバイトの値を割り当てようとするのではなく、None / Error::Ber("invalid TLV length") を返す。encode_length は対称な相方であり、同じ 16 MiB の範囲に境界付けられる。再帰的な parse_ber_tlv 自体は任意幅のロングフォーム長さをサポートするが、すべての値で i + len > data.len() をチェックするため、バッファをオーバーランする長さは、境界外読み取りではなく、クリーンな Error::Ber("TLV value exceeds remaining data") となる。
ユニットテストは敵対的なケースを直接ピン留めする。parse_ber_tlv_rejects_nesting_beyond_depth_cap は MAX_BER_DEPTH + 2 のネストされた SEQUENCE を構築し、実現されたツリー深さが上限を決して超えないこと(スタックオーバーフローがないこと)を表明する。そして parse_ber_tlv_rejects_too_many_nodes は MAX_BER_NODES + 1 のフラットなプリミティブを与え、それらがすべて割り当てられる前にノード予算が作動することを表明する。
CLI は意図的にホスト許可リストを却下する(任意の公開 CA を検証しなければならない — §15.2.1)。web エッジプロキシ はそれを使用でき、実際に使用する。ブラウザデプロイメントが小さく固定されたトラストアンカー集合を提供するからである。
web/functions/crl.js は、ブラウザフロントエンドのために CRL/OCSP フェッチをプロキシする Cloudflare Pages Function である(CORS ヘッダを送らない配布点に対して CORS を終端するために存在する)。これは web/revocation-hosts.json にリストされていないホストを拒否する。これは web/site/roots/ で出荷されるルートのための CRL/OCSP 配布ホストのキュレートされたリストである。
repo1.secomtrust.net
repository.secomtrust.net
ldap.aosign.com
dir.tdb.ne.jp
repository.toinx.net
ldap.e-probatio.com
build-roots.mjs はこのファイルを web/site/revocation-hosts.json にコピーし、エッジ Function とブラウザフロントエンドが同じ許可リストを読むようにする。そして許可リストは出荷されるトラストアンカーと歩調を合わせて保たれる。新しいルートが追加されると、その配布ホストがここに追加される。これは CLI のアドレスガードよりも厳格な制御である — web デプロイメントが汎用ツールではなく固定トラスト集合の製品であるため適切である — が、カーネル の判定はどちらのデプロイメントでも変わらない。WASM ホストは CLI と同一の verify_with カーネルを実行するため、プロキシがフェッチを拒否する CRL は、単に到達不能な任意の CRL と同じ誠実な INDETERMINATE を生じる。
2 つの送出モデルは冗長ではなく相補的である。
| デプロイメント | 送出制御 | 根拠 |
|---|---|---|
| CLI(汎用) | アドレスネットガード(解決後ピン留め)、非公開 IP を拒否 | 任意の公開 CA を検証しなければならない。固定ホスト集合は存在しない |
| web エッジプロキシ(固定トラスト集合) | ホスト許可リスト(revocation-hosts.json) |
小さくキュレートされたルート集合。ブラウザに対して可能な限り厳格な送出 |
v1.1.2 以前、crl.js は redirect: "follow" を使用し、リダイレクト解決を Cloudflare fetch ランタイムに委ねていた。許可リスト済みの CA ホストにオープンリダイレクトがあった場合、プロキシは 2 ホップ目で許可リスト外の URL をフェッチするよう誘導される可能性があった。
v1.1.2 以降、プロキシは最大 MAX_REDIRECTS = 5 ホップを手動で追跡する。各 3xx レスポンスに対して Location を取り出し、現在の URL を基準に解決し、次のリクエストを発行する前にスキーム(http: / https: のみ)とホスト名(許可リスト上にあること)を再検証する。どちらかのチェックに失敗したホップは即座に 502 を返す。
LDAP URL はさらにポート 389 または 636 のみに検証される(ALLOWED_LDAP_PORTS = {389, 636})。従来のコードは許可リスト済みホスト名に対して任意のポートを受け入れていた。
プロキシは Sec-Fetch-Site を早期にチェックする。このブラウザが注入するヘッダが存在し、かつ same-origin、same-site、または none でない場合、許可リスト評価の前に 403 で拒否する。これにより、サードパーティの Web ページが当プロキシを経由して許可リスト済み CA ホスト集合へのフェッチを誘導するのを防ぐ(帯域増幅 / オープンリレー乱用)。Sec-Fetch-Site が存在しないサーバー間呼び出しには影響しない。
以下の残存リスクは、現在のリリースに対して 文書化され受容されている。政府レビュアーが完全な台帳を持てるように、ここに記録する(docs/network-egress-and-ssrf.md、docs/security-review-2026-06-20.md、docs/security-review-2026-06-23.md 「Residual risks already documented」)。
HTTP_PROXY/HTTPS_PROXY が設定されている場合、ガードはプロキシエンドポイントを検証するが、プロキシが接続しうるすべてのアドレスを検証するわけではない。プロキシの送出を制限することが、そのデプロイメントにおける運用者の権威ある制御である(§15.2.5)。std のみの設計のスコープ外であり、受容済みとして文書化される(§15.2.5。仕様 021 の前提)。INDETERMINATE)であって、偽造された「Good」ではない。docs/trust-anchor-policy.md)を通じて管理される出所/ガバナンスの懸念であり、本章のリソース/送出制御のスコープ外である。--offline / --from-bundle を優先し、オンライン検証は送出制限されたサンドボックスで実行すべきである(包括 #81)。ネットガードは多層防御であって、制限のないネットワークで信頼できない入力をオンライン検証する ライセンスではない。新規作業に対する常置のセキュリティ規律は次のとおりである。あらゆる新規依存はリリースブランチの 前に cargo vet check --locked を通過しなければならず(未 vet の dev 依存がリリース時にのみ表面化したときに学んだ罠 — docs/review-remediation-2026-06-20.md と cargo-vet dev-deps に関するプロジェクトメモリのノートを参照)、あらゆる新規のホスト側パーサまたはフェッチャは、自身のリソース予算を備え、§15.1.2 の不変条件と一貫して、ブロックされたまたは予算超過の操作を、でっち上げた判定ではなく INDETERMINATE へとルーティングしなければならない。
ブラウザデプロイメント(web/site/)は、攻撃対象面が検証カーネルではなく UI 自体であるため、CLI の I/O ガードとは別個の制御群を導入する。
v1.1.2 以前、検証フォームにドロップされた自己署名証明書はすべて、WASM カーネルへ送る trust_anchors 配列にサイレントにマージされていた。これによりソーシャルエンジニアリングベクターが生じた。攻撃者が攻撃者制御の CA 配下で署名した文書と、その CA の自己署名ルート証明書を提供できる場合、両方をドロップしたユーザーは、視覚的な警告なしに攻撃者提供のトラスト下での TOTAL_PASSED を目にすることになる。
v1.1.2 以降:
app.js の buildUploadRequest() は、#custom-trust-toggle チェックボックスがユーザーによって明示的にチェックされている場合にのみ、自己署名証明書を trust_anchors に追加する。maybeAutoVerify() を再トリガーするため、変更された信頼境界が結果に即座に反映される。デフォルトの場合(チェックボックス未チェック)、第10章で文書化された既存のトラストモデルを保護し、バンドルされた出所追跡済みルートのみを使用する。
WASM API は algorithm_policy フィールドを受け入れ、"default" は組み込みの CRYPTREC 一次ポリシーを選択する。v1.1.2 以前、本番ブラウザ UI はこのフィールドを送信しておらず、WASM ラッパーが None を解決してポリシーレイヤが無効のままになっていた。公開サイトを通じたすべての検証は今後 algorithm_policy: "default" を送信する。
web/site/_headers(Cloudflare Pages 静的ヘッダーファイル)は以下を設定する。
Content-Security-Policy: default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self'; connect-src 'self'; img-src 'self' data:; font-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'
Referrer-Policy: no-referrer
X-Content-Type-Options: nosniff
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
'wasm-unsafe-eval' は WebAssembly.instantiate(wasm-pack が生成するグルーが呼び出す)に必要である。connect-src 'self' は fetch() を同一オリジンに制限する。CRL プロキシエンドポイントは同一オリジンであるため、失効フェッチは緩和なしに機能する。Google Fonts の <link> タグは全サイト HTML ファイルから削除された。システムフォント(system-ui、-apple-system、"Hiragino Sans")がフォントスタックの先頭になり、サードパーティへのページ訪問漏洩を排除し、font-src の緩和が不要になる。
本章では、pverify がそのリリース成果物をどのように生成・アテステーション・公開するか、また 検証者(信頼当事者、RP)がダウンロードした成果物がタグ付けされた pverify ソースから公式の ビルドワークフローによってビルドされたものであることをどのように独立して確認するかを規定する。 本章の対象は意図的に検証カーネルの外側にある。すなわち、ここで述べる機構のいずれも pverify-core をリンクしたり、その依存関係グラフに入り込んだりすることはなく、いずれも pverify に署名能力を付与しない。本ツールは依然として検証専用かつ鍵レスであり(憲章 §I)、 サプライチェーン層は、バイナリが実行できることを正確に宣言する部品表(bill of materials)と、 バイトがどこから来たかを証明するビルドパイプラインを通じて、その同じ誠実性の規律を表現する。
この設計は、2 つの機能スライスの成果物である。
slsa-sbom-cbom(specs/013-slsa-sbom-cbom/):タグトリガーの SLSA Build-L3 リリースパイプライン、依存関係の SBOM、ドリフトゲートを備えた暗号 CBOM、 そして cargo vet / cargo audit のサプライチェーンゲート。bom-attestation(specs/024-bom-attestation/):既存の SLSA プロビナンス(来歴)の上に追加的に重ねられた型付き CycloneDX in-toto アテステーション。これにより、既製のサプライチェーン検証ツールが gh attestation verify を介して各 BOM を直接発見・検証できる。両スライスは、カーネル、レポートスキーマ(schema_version 1.9.0、第14章を参照)、および すべての検証判定をバイト単位で同一に保つよう明示的に制約されている(013 SC-009;024 FR-009/SC-004)。 これらはビルド・CI・ドキュメントのみの変更である。
統制対象となる 2 つの主要成果物は、ワークフロー .github/workflows/release.yml (タグトリガー)と .github/workflows/ci.yml(すべての push/PR)、リポジトリルートに コミットされた cbom.json、supply-chain/ の cargo vet ストア、および docs/release-verification.md の検証者向け手順である。
pverify は政府の顧客に提供されるが、その顧客はダウンロードしたビルドについて 3 つの異なる 問いに答えなければならない。サプライチェーン層は、各問いがちょうど 1 つの機構に対応するよう 構成されている。
| RP が問う問い | 機構 | 成果物 |
|---|---|---|
| 「これらのバイトは、まさにタグ付けされた pverify ソースから公式ワークフローによって改竄なくビルドされたのか?」 | SLSA v1.0 Build-L3 プロビナンス(鍵レス、Sigstore/Fulcio 証明書、Rekor ログ) | pverify-<tag>.intoto.jsonl |
| 「このバイナリはどのサードパーティクレートからビルドされているのか?」 | CycloneDX 依存関係 SBOM、ピン留めされたロックファイルから再生成可能 | sbom.json(+型付きアテステーション) |
| 「このバイナリはどの暗号処理を実行でき、そして検証のみ(決して署名しない)なのか?」 | CycloneDX 暗号 BOM、コードから導出されドリフトゲートで管理 | cbom.json(+型付きアテステーション) |
これらの軸は意図的に直交している。CBOM は pverify が検証に用いる暗号であり、SBOM は pverify がビルドされる元となるクレートであり、プロビナンスは両 BOM を含むすべての成果物に かかる出所の束縛である。検証者向けドキュメントは、監査人がこれらを混同しないよう、まさに この区別を平易な言葉で述べている(docs/release-verification.md、「CBOM vs SBOM」コールアウト)。
第 4 の、横断的な関心事 — pverify は正確にどの暗号をディスパッチするのか? — は、静的な ドキュメント単独ではなく、そのドキュメントが証明可能な形で最新であることを保証する CI ゲート によって答えられる(§16.4)。これは §I「事実報告」の原則のサプライチェーンにおける表現である。 すなわち、コードから黙って乖離しうる公表された暗号上の主張は事実ではないため、もしそれが 乖離した場合にはビルドが失敗する。
release.yml)リリースパイプラインはバージョンタグによってのみトリガーされる (on: push: tags: ["v*"]、release.yml:21-23)。タグなしの作業 — 機能ブランチ上の すべてのコミットを含む — はこれを決して呼び出さない。代わりに、パイプラインの構造的な 正しさは ci.yml の lint ジョブによって継続的にアサートされる(§16.7)。最初の実際の 呼び出しは v0.7.0 後の最初のタグであり、v0.9.0 が完全なバンドルを出荷するリリース済みの ベースラインである。
ワークフローはトップレベルで permissions: contents: read を宣言する (release.yml:29-30)。これは単なる防御的措置ではない。リポジトリは現在プライベートであり、 actions/checkout はプライベートリポジトリを取得するために contents: read をそもそも 必要とする(権限ゼロのトークンでは 404 が返る)。公開を行うジョブ — provenance、release、 および 2 つの BOM アテステーションジョブ — は、それぞれのジョブレベルの permissions: ブロックでこれを拡張し、必要な追加スコープだけを付与する(OIDC 鍵レス署名のための id-token: write、GitHub アテステーションストアのための attestations: write、 リリース資産アップロードのための contents: write)。どのジョブも使用しないスコープを 保持していない。
すべての下流ジョブは needs: supply-chain-vet を持つ(release.yml:55,125,182,217)ため、 cargo vet check --locked の失敗は、いかなる成果物がビルドされる前にリリース全体を 短絡させる — サプライチェーン監査は事後的なチェックではなく、リリースをブロックする前提条件 である。
build ジョブ(release.yml:53-118)は pverify-cli を 5 つのターゲットトリプルに対して コンパイルする。
| ターゲットトリプル | ランナー | 備考 |
|---|---|---|
x86_64-unknown-linux-gnu |
ubuntu-latest |
ネイティブ |
aarch64-unknown-linux-gnu |
ubuntu-latest |
gcc-aarch64-linux-gnu 経由でクロスリンク |
x86_64-apple-darwin |
macos-latest |
ネイティブ |
aarch64-apple-darwin |
macos-latest |
ネイティブ |
x86_64-pc-windows-msvc |
windows-latest |
ネイティブ |
pverify は RustCrypto のみで OpenSSL を使わない(憲章 §VI)ため、パイプラインは 典型的なクロスプラットフォーム Rust リリースよりも大幅に単純である。すなわちすべての OpenSSL/vcpkg のセットアップが削除されており、aarch64-linux ターゲットだけが GNU クロスリンカ(CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER、release.yml:79-88)を 必要とする。これは第3章で文書化した §VI WASM クリーン制約の直接的な見返りである。すなわち、 純 Rust の暗号グラフは些末にクロスコンパイルできる。
ジョブは fail-fast: false(release.yml:58)を設定し、ビルド後に、期待される target/<triple>/release/pverify[.exe] が存在しない場合に GitHub のエラーアノテーションを 発し exit 1 する明示的なバイナリ欠落チェックを行う(release.yml:99-103)。これは FR-021 を満たす。すなわち、生成できなかったターゲットは、黙って欠落した成果物ではなく、 可視の失敗として表面化する。各バイナリは .sha256 サイドカー(Linux では sha256sum、 macOS では shasum -a 256 をフォールバック、release.yml:106-110)とともに pverify-<triple>.tar.gz としてパッケージ化され、if-no-files-found: error で アップロードされる。
source ジョブ)source ジョブ(release.yml:123-174)は、git archive --format=tar.gz --prefix="pverify-<version>/" HEAD(release.yml:142)を介してタグにピン留めされた ソースアーカイブを生成する。また、CBOM バージョン注入(データモデルのエンティティ E5)も 行う。すなわち、コミットされた cbom.json は開発用プレースホルダ "0.0.0" を保持しており、 公開されるコピーは jq '.metadata.component.version=$v' でタグバージョンに書き換えられる。 空出力ガード([[ ! -s cbom.tmp ]]、release.yml:146-149)は、jq の失敗が切り詰められた CBOM を出荷しえないことを保証する — 代わりにジョブは声高に失敗する。バージョン注入は CBOM アテステーションの前に行われるため、アテステーションされたダイジェストは公開されるバイトと 一致する(§16.6)。
wasm ジョブ、FR-018)ブラウザビルドは一級のリリース対象である。wasm ジョブ(release.yml:180-210)は wasm32-unknown-unknown ターゲットと wasm-pack をインストールし、web/pverify-wasm を スタンドアロンでビルドし(これはワークスペースから切り離されたクレートである — 第3章 §3.2 を 参照 — ため、ルートワークスペースではなく web/ の内部からビルドされる)、web/site/pkg/ を SHA-256 サイドカーとともに pverify-wasm-<tag>.tar.gz に tar 化する。ブラウザホストは ネイティブ CLI とバイト単位で同一の verify_with カーネルを実行する(第3章の CLI と WASM の パリティ不変条件)ため、これをプロビナンス付き成果物として出荷することで、RP は自身が ロードするブラウザ内検証器が公式ビルドであることを確認できる。
sbom ジョブ、FR-022/FR-023)sbom ジョブ(release.yml:215-266)は cargo-cyclonedx をインストールし、ピン留めされた Cargo.lock から SBOM を生成する。監査人のために記録しておく価値のある微妙な点として、 cargo-cyclonedx 0.5.9 は集約ではなくワークスペースメンバーごとに 1 ファイルを出力する。 重要なのはリリースされるバイナリの完全な依存関係ツリーの SBOM であるため、ジョブは生成を crates/pverify-cli/Cargo.toml にスコープし(--manifest-path … --override-filename sbom、 release.yml:240-241)、出力が存在することをアサートし(release.yml:242-245、cargo-cyclonedx が何も生成しなければ声高に失敗する)、crates/pverify-cli/sbom.json をリポジトリルートの sbom.json に正規化する。したがって SBOM は、タグ付けされたコミットにおけるロックファイルの 機械的な関数であり、ソースアーカイブから誰でも再現可能である — 検証者向けドキュメントは正確な 再現手順を与える(docs/release-verification.md、「How the SBOM is generated」)。
compute-digests(release.yml:272-297)はすべての成果物をダウンロードし、すべての *.tar.gz に加えて cbom.json と sbom.json に対して(.sha256 サイドカーは明示的に 除外して)sha256sum を計算し、その結果を SLSA ジェネレータ向けに base64 エンコードする。 データモデルの不変条件 INV-8 は、このサブジェクト集合 ⊇ {5 つのバイナリ、ソース、CBOM、 SBOM、WASM} であることである。
終端の release ジョブ(release.yml:326-349)は、すべての tarball、すべての .sha256 サイドカー、cbom.json、sbom.json を、fail_on_unmatched_files: true を指定した softprops/action-gh-release を介して GitHub Release に添付する。SLSA プロビナンス .intoto.jsonl はプロビナンスジェネレータ自体によってアップロードされる (upload-assets: true)。
provenance ジョブ(release.yml:302-320)は、上流の slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0 再利用可能ワークフローの uses: である。これは compute-digests から base64 エンコードされた サブジェクトダイジェストを消費し、すべてのリリースサブジェクトを網羅する単一の署名済み pverify-<tag>.intoto.jsonl を出力する(FR-017/FR-018)。
SLSA Build Level 3 は、プロビナンスが堅牢で隔離されたビルダー(GitHub ホストの再利用可能 ワークフローはワークフロー作成者が改竄できないコンテキストで実行される)によって生成されること、 ビルドがソースリポジトリとタグによって識別されること、そして署名が鍵レスであることを 意味する。すなわち、ワークフローの OIDC アイデンティティから短命の Fulcio 発行 X.509 証明書が発行され、in-toto ステートメントの署名に使用され、その発行は Sigstore/Rekor の 公開透明性ログに記録される。長命の署名鍵はどこにも存在しない — §I(pverify は鍵を保持しない。 リリースインフラさえ鍵を保持しない)と整合している。
汎用 SLSA ジェネレータは、呼び出し側が private-repository: true(release.yml:320)で オプトインしない限り、プライベートリポジトリに対しては実行を拒否する。ワークフローの コメントは明示的であり、監査人はこれを正確に理解しなければならない。
オプトインすると、リポジトリ名とワークフロー ref が PUBLIC な Rekor 透明性ログに 記録される(署名された成果物は公開されないが、
digital-go-jp/pverifyは公に発見可能と なる)。
したがって、成果物はプライベートのままであるが、digital-go-jp/pverify リポジトリと そのリリースワークフロー ref の存在は公開 Rekor ログで開示される。これは意図的かつ 文書化されたトレードオフである — SLSA プロビナンスの経路は型付き BOM アテステーションの 経路よりもより公開的であり(§16.6.3)、検証者向けドキュメントはこの 2 つが決して混同 されないよう、その違いを明示的に旗印として示している。
cbom.json はリポジトリルートにある CycloneDX 1.6 ドキュメントである (bomFormat: "CycloneDX"、specVersion: "1.6")。その metadata.component は pverify を プレースホルダバージョン "0.0.0" の application として名指しし、それが「pverify が検証時に DISPATCH できるすべての暗号アルゴリズムを列挙する(検証のみ)」ことを記録するスコープ プロパティを持つ。これは17 個のコンポーネントを保持し、各々が cryptographic-asset である。 すなわち、primitive: "signature" を持つ 13 個(RSA-PSS の例外的扱いを含む、§16.4.3)と primitive: "hash" を持つ 4 個である。
各アルゴリズムエントリは、その OID、parameterSetIdentifier(RSA / P-256 / P-384 / Ed25519 / ML-DSA-44|65|87)、cryptoFunctions(常に ["verify"] であり、決して "sign" ではない)、 nistQuantumSecurityLevel(古典には 0、ML-DSA-44/65/87 には 2/3/5)、そして pverify:role や — 該当する場合は — pverify:weak="true" を含む properties を記録する。例えば、SHA-1 エントリと RSA/ECDSA-over-SHA-1 の組み合わせは weak とマークされる。すなわち pverify は SHA-1 を 認識してフラグを立てるのであり、それを拒否するふりはしない(FR-005。この誠実性は、第5章〜第7章で 述べる検証エンジンの認識してフラグを立てるポリシーと一致する)。網羅される集合はカーネルの検証 ディスパッチ表面である。すなわち、RSA PKCS#1 v1.5(SHA-1/256/384/512)、RSA-PSS(XAdES 経路)、 ECDSA P-256/P-384(SHA-1/256/384/512)、Ed25519、ML-DSA-44/65/87、およびハッシュ SHA-1/256/384/512 である。
決定的な設計上の特性は、ディスパッチ挙動と公表される CBOM が二重保守を通じて乖離しえない ことである。なぜなら、両者ともカーネル内の 1 つの権威ある列挙に固定されているからである。
crates/pverify-core/src/crypto.rs:288 — SUPPORTED_SIGNATURE_OIDS、 (OID, human-name) ペアの &[(&str, &str)]。crates/pverify-core/src/crypto.rs:308 — SUPPORTED_HASH_OIDS、同様。crypto.rs のユニットテスト(773 行付近)は、このテーブル自体がディスパッチ関数と内部的に 整合していることを強制する。すなわち、SUPPORTED_SIGNATURE_OIDS 内のすべての OID は signature_alg_from_oid を通じて Ok(_) に往復し(INV-1)、ディスパッチャが受理する OID の 集合はテーブルのキーとちょうど等しく(INV-2)、SUPPORTED_HASH_OIDS は HashAlg::oid_string() の値の集合と等しい(INV-3)。これらの const を公開することは 013 が 行う唯一のコア変更であり、依存関係を追加しない — FR-025 の「コアの依存関係グラフに触れない」 制約を満たしつつ、ディスパッチテーブルをツール側から機械可読にする。
RSA-PSS(1.2.840.113549.1.1.10)は、pverify 内では専ら XAdES XML-DSIG アルゴリズム URI 経路(pverify-xades)を介してのみ到達可能であり、CMS OID テーブル経由では決して到達できない — このテーブルは意図的に PSS を拒否する(テスト signature_alg_from_oid_rejects_rsa_pss)。 したがって PSS は SUPPORTED_SIGNATURE_OIDS から意図的に不在であるが、CBOM には検証能力 として存在する。ドリフトゲートはこれを特例として扱う。すなわち、例外扱いの OID は 「CBOM に存在することが期待され、const から不在」として許可リストに載せられ、ゲートはまた、 その例外が存在しなければならないことをアサートする(PSS エントリの欠落は能力のリグレッションで ある)。これは crypto.rs のドキュメントコメント(crypto.rs:282-287)とゲートのソースの 両方で文書化されている。
cargo xtask cbom-checkドリフトチェックはホスト専用の開発者ツールである(xtask/src/cbom_check.rs、cargo xtask cbom-check を介して呼び出される)。これは(コンパイル時に組み込まれた const、ディスク上の cbom.json)の純粋関数である。すなわち、ネットワークも、クロックも、ファイルシステムへの 書き込みもなく、その差分出力は決定的な実行のためにソートされる。そのアルゴリズムは以下の とおりである。
cbom.json をパースし、cryptographic-asset エントリを primitive によって cbom_sig と cbom_hash の OID 集合に分割し、cryptoFunctions != ["verify"] であるすべての署名 エントリを non_verify に収集する(cbom_check.rs:94-101)。SUPPORTED_SIGNATURE_OIDS / SUPPORTED_HASH_OIDS から読み取る。"sign" を 列挙する — は事実報告違反である(cbom_check.rs:116-120)。これは「pverify は決して 署名しない」のサプライチェーンにおける強制である。+ <oid>)。- <oid>)。ただし RSA-PSS の例外を除く。- RSA-PSS capability missing)。bail! → 非ゼロ終了であり、各不一致を名指しする (cbom_check.rs:163-171)。クリーンな実行は cbom-check: OK — <n> algorithms match を 出力する。ゲートは両方向のドリフトを捕捉する(FR-007、SC-003)。すなわち、cbom.json を更新せずに 新しいアルゴリズム OID をコードに追加すること、またはコードがディスパッチしないアルゴリズムを cbom.json で主張すること、どちらも失敗する。このチェックはバージョン非依存であり — metadata.component.version を完全に無視する(FR-009) — リポジトリ内の "0.0.0" プレースホルダでも、バージョン注入されたリリースコピーでも同一に動作する。これは CI で cbom-check ジョブとして実行され(ci.yml:49-61)、リリースをブロックする必須チェックである。
SBOM(sbom.json)は生成されるものであり、コミットされない — これは Cargo.lock の 機械的な関数であり、陳腐化した手保守のコピーを避けるために gitignore される。これはすべて同じ スコープ付けを用いる 3 つのコンテキストで生成される。
sbom ジョブ(§16.2.6)、web/build.sh、生成された sbom.json が存在すればコピーする)、SBOM は、ピン留めされたロックファイルの状態における、リリースされるバイナリの依存関係ツリーの 解決済みサードパーティクレート(名前、バージョン、ライセンス、purl)を列挙するため、 コミット/タグごとに再現可能である。検証者向けドキュメントは、逐語的な再現手順と、 コンポーネントを数える jq ワンライナーを与える(docs/release-verification.md、ステップ 4 と 「How the SBOM is generated」)。
SLSA プロビナンスはすでに BOM の完全性を保証する — cbom.json と sbom.json の両方が プロビナンスサブジェクトであるため、RP は各々がタグ付けされたリリースワークフローによって 生成され改竄されていないことを確認できる。v0.9.0 に欠けていたのは型付きで、独立して消費可能な 束縛であった。すなわち、標準的なサプライチェーンツールで BOM を取得するには、RP は以前、 SLSA プロビナンスのサブジェクトリストを手でパースしてダイジェストを手作業で照合しなければ ならなかった。スライス 024 は、既製の検証ツールが直接発見するエコシステム標準の CycloneDX 型付き in-toto アテステーションを追加する。これは純粋に追加的である — SLSA プロビナンスを 除去・置換・弱体化しない(FR-007)。
各 BOM は actions/attest-sbom@v2 でアテステーションされる。
sbom.json については sbom ジョブで(release.yml:254-258)、cbom.json については source ジョブで(release.yml:160-164)、バージョン注入の後、 アテステーションされるダイジェストが公開されるバイトと一致するように。各 BOM について、そのファイルは同時にアテステーションのサブジェクト(SHA-256 によって 束縛される — 改竄は検証を破る、SC-003)であり、かつ述語(predicate)(CycloneDX ドキュメント自体)である。CBOM は CycloneDX 1.6 ドキュメントであるため、attest-sbom は SBOM と同様にこれを一様に受理する。ワークフローのコメントは、attest-sbom が暗号のみの CycloneDX ドキュメントを拒否した場合のフォールバック(predicate-type: https://cyclonedx.org/bom を指定した actions/attest@v2)を記録する(FR-002、R-2)。
署名はワークフローの OIDC アイデンティティを介して鍵レスである。アテステーションは リポジトリにキー付けされた GitHub のアテステーションストアに保存される(外部 OCI レジストリはない)。決定的に、いずれのアテステーションステップも continue-on-error を 持たない。すなわち、アテステーションの失敗はそのジョブを失敗させ、これは compute-digests / provenance / release が決して実行されないことを意味する。したがって、部分的なリリースが アテステーションなしに BOM を出荷することはありえない(FR-010 fail-loud、構造上)。この 2 つの ジョブは、まさにこの目的のためにトークンスコープを id-token: write + attestations: write に拡張する(release.yml:129-132,222-225)。
検証者の信頼モデルは docs/release-verification.md(ステップ 5、「Trust model」)に文書化 されている。
digital-go-jp/pverify、ワークフロー .github/workflows/release.yml、ref refs/tags/v<version>。RP は検証を --signer-workflow (または --cert-identity)で制約するため、他のいかなるワークフローからのアテステーションも これを満たすことができない。private-repository: true オプトインを用いるため、リポジトリ+ワークフローのアイデンティティを公開 Rekor ログに 実際に記録する。ドキュメントは明示的に警告する。「両者を混同してはならない。型付き BOM アテステーション経路は、あなたのリポジトリアイデンティティを公開ログに公表しない。」この 2 つの 経路を精査する監査人は、この非対称性を念頭に置かなければならない。ci.yml)ci.yml は main へのすべての push とすべてのプルリクエストで実行され、リリースパイプライン とサプライチェーンドキュメントを、タグ時点だけでなく常に出荷可能な状態に保つ機構である。
| ジョブ | コマンド | 何を守るか |
|---|---|---|
lints |
cargo fmt --all --check;cargo clippy --workspace --all-targets -- -D warnings |
スタイル / lint のドリフト |
tests |
無ライセンスのサードパーティ fixture を取得(SHA-256 ピン留め、決してコミットしない)した後 cargo test --workspace |
検証スイート全体 |
cbom-check |
cargo xtask cbom-check |
CBOM とコードのドリフト(§16.4)、リリースブロッキング |
release-workflow-lint |
actionlint -color .github/workflows/release.yml(すべての run: ブロックで shellcheck を呼び出す) |
最初のタグの前に release.yml を既知良好に(FR-020、024 FR-011) |
wasm-gate |
cargo build --target wasm32-unknown-unknown -p pverify-core |
§VI WASM クリーンなコア(第3章) |
cargo-audit |
cargo audit --deny warnings --ignore RUSTSEC-2023-0071 |
既知脆弱性のある依存関係 |
perf-gate |
scripts/perf-gate.sh(較正済み中央値に対する 4 つの verify_with ケース) |
検証レイテンシのリグレッション |
このうち 2 つは本章のために詳述する価値がある。
release-workflow-lint — 発火する前にパイプラインを検証するrelease.yml は実際のタグによってのみ行使されるため、構造的なエラーは、そうでなければリリースの 最中に初めて表面化することになる。release-workflow-lint ジョブ(ci.yml:63-78)は actionlint を実行し — これはワークフロースキーマと式を検証し、すべての埋め込み run: スクリプトに対して自動的に shellcheck を呼び出す — すべての PR/push で実行されるため、 ワークフローは最初のタグ付き呼び出しの前に既知良好である(FR-020/SC-008)。スライス 024 は、 タグが push される前にアテステーションステップが導入した任意の構造的エラーを捕捉するために、 この同じゲートに依拠する(024 FR-011/SC-005)。
cargo audit と cargo vet — 二軸の依存関係ゲートpverify は両方の依存監査ツールを、異なる軸でライフサイクルの異なる時点で実行する。
cargo audit(ci.yml:92-105)はすべての PR/push で実行され、ロックされたツリーを --deny warnings で RustSec アドバイザリデータベースに対してチェックする。1 つのアドバイザリは 文書化された根拠とともに無視される。すなわち、RUSTSEC-2023-0071(rsa クレートの復号 経路に対する Marvin タイミング攻撃)である。その根拠は audit.toml に正規に記録されている。 pverify-core は RSA PKCS#1 v1.5 署名を検証するのみ(RsaPublicKey::verify)であって決して 復号しないため、攻撃ベクトル — 復号操作のタイミングを測ること — は決して存在しない。上流には 修正リリースがない(「No fixed upgrade is available!」)。この例外を評価する監査人は、それが pverify が行使しない能力に狭くスコープされており、rsa 0.10 が定数時間経路を出荷したときに 再評価されるべきであることに留意すべきである。旗印を立てる価値のある微妙な点として、 cargo-audit 0.22.x はもはや audit.toml から [advisories].ignore を読まないため、CI ワークフローは明示的な --ignore RUSTSEC-2023-0071 フラグを介して同じリストをミラーする — audit.toml が正規の根拠であり、ワークフローが実行者であり、この 2 つは一緒に編集 されなければならない。
cargo vet(supply-chain-vet ジョブ、release.yml:37-47)はタグ時点で リリースをブロックするゲートとして実行される。すなわち cargo vet check --locked。 cargo audit(「いずれかの依存関係は既知脆弱性があるか?」を問う)とは異なり、cargo vet は「すべての依存関係は監査されたか、または明示的に免除されたか?」を問う。そのストアは supply-chain/ にある。すなわち、config.toml(bytecode-alliance、Google、Mozilla の共有 監査セットをインポートし、その後 safe-to-deploy または safe-to-run 基準を持つクレート ごとの [[exemptions.*]] の長いリスト)、audits.toml(ファーストパーティ監査)、および imports.lock である。
プロジェクトの組織的記憶に記録されている、常在する運用上の危険として、保守担当者のために 述べておく価値があるのは次のことである。cargo vet はリリース時にのみ発火するため、 未 vet のクレート(dev/test 専用の依存関係を含む)は機能ブランチ上で静かに蓄積し、最初のタグで リリースブロックとして表面化する。規律は、新しいクレートがツリーに入るたびにローカルで cargo vet check --locked を実行することである。スライス 034 はまさにこれを発見し解決した。 すなわち、スライス 028 が未 vet のまま残していた insta スナップショットテスト依存関係 (およびその console/encode_unicode/similar 推移依存)が、safe-to-run 免除で解消された。 safe-to-run と safe-to-deploy の区別はここで重要である。すなわち、出荷されるバイナリに 決して入らない dev/test 専用クレートは safe-to-run だけを必要とし、これが insta チェーンが その弱い基準を持つ一方で、本番依存関係が safe-to-deploy を持つ理由である。
エンドツーエンドの検証者手順は docs/release-verification.md である。その信頼経路は RP ↔︎ GitHub ↔︎ slsa-verifier であり — 稼働中の pverify サービスを必要としない。5 つの ステップは以下のとおりである。
gh release download)。slsa-verifier verify-artifact … --source-uri github.com/digital-go-jp/pverify --source-tag v$VERSION で SLSA プロビナンスを検証する。これは、成果物のダイジェストが プロビナンスサブジェクトであること、かつプロビナンスが期待されるソースリポジトリとタグで pverify リリースワークフローによって生成されたことを確認する。各サブジェクト(binary、CBOM、 SBOM、WASM、source)について独立して繰り返される — 各々が自身のサブジェクト(INV-8)であり、 単独で検証可能である。sha256sum -c)。権威ある 束縛はステップ 2 のプロビナンスのままである。jq チェックを含む。すなわち、CBOM が リリースバージョンを保持していること(0.0.0 プレースホルダではないこと)、そして すべての署名エントリの cryptoFunctions がちょうど ["verify"] であること — pverify が 検証専用であることをドキュメントから直接証明する。gh attestation verify <bom> --repo digital-go-jp/pverify --signer-workflow digital-go-jp/pverify/.github/workflows/release.yml で型付き BOM アテステーションを検証 する。これはローカルファイルを再ダイジェストし、GitHub から一致するアテステーションを取得し、 署名者アイデンティティに対して Sigstore バンドルをチェックする。ドキュメントはまた、 失敗しなければならない否定的チェックも与える。すなわち、改竄された BOM(一致する アテステーションがない)と誤った署名者アイデンティティ(アイデンティティの不一致)である。 また、接続性の前提条件を記録する(gh attestation verify はデフォルトでオンラインであり、 エアギャップ環境での使用には事前ダウンロードしたバンドルが必要である)。ドキュメントはさらに、トラストアンカーのインベントリをサプライチェーンの透明性成果物として 表面化させる。すなわち、pverify trust-anchors {list,dump} は、pverify が配布するすべての アンカー(本番ピン留め台帳、EU トラステッドリストの取り込み、AATL の取り込み、PQC 相互運用) に加えて、意図的に除外された AATL アイデンティティを、その除外理由とともにレンダリングする。 dump の出力は実行ごとにバイト単位で同一である(クロックやランダムソースは読まれない。 第10章を参照)ため、リリース間のインベントリ差分は小さく可読である — 配布されるトラスト集合の すべての変更が監査可能なパッチとして表面化する。
サプライチェーン層は、検証カーネルに影響を与ええないように構築されている。
verify を宣言し決して sign を宣言しない。これは ドリフトゲートの INV-4 によって強制される(§16.4.4)。パイプライン内のすべての署名は鍵レス OIDC である。ツールとしての pverify は鍵を保持しない。ドリフトゲートは、公表された暗号上の 主張を、陳腐化したドキュメントではなく、証明可能な形で最新の事実にする。cbom-check と トラストアンカーの dump は、クロック/ランダム入力を持たない純粋関数である。xtask、CI/リリースワークフロー、ホストツールに存在する — 決して pverify-core や WASM グラフには存在しない。xtask は(OID const を読むために)pverify-core に依存するが、 コアは決して逆方向に依存しない。scripts/cargo-tree-gate.sh は、xtask(および pverify-test-helpers)が pverify-core と pverify-cli の両方の本番 cargo tree -e normal グラフから不在であることをアサートする(第3章 §3.2)。2 つの OID const を追加する ことが唯一のコアソース変更であり、依存関係を追加しない(FR-025)。schema_version が いずれのスライスによっても変更されないことを要求し — テストスイートがそれを実証する。いずれのスライスも憲章を改正しない。両者ともビルド/CI/ドキュメントの追加であり、 いかなるポリシーテーブルやレポートスキーマにも触れることなく §I/§II/§VI を補強する。
本章は、調達上のデューデリジェンスを実施する検証者(信頼当事者)の観点から、 pverifyを準拠すべき標準群へとマッピングする。本章には三つの目的がある。第一に、 レポートのレベル命名が示唆しうるものとは対照的に、pverifyが実際に検証する 欧州(eIDAS / ETSI)の高度電子署名(AdES)レベルがどれであるかを正確に述べる ことである。第二に、JNSA デジタル署名検証ガイドライン 第 1.1 版(2023-12-20) に対するサプライヤ側の適合性マトリクスを再掲し、各主張をコードに根拠付けて説明 することである。第三に、残存するギャップについて誠実に説明することであり、これ には欠陥ではなく意図的な設計上の選択(「意図的な逸脱」)であるものも含まれる。
本章が依拠する二つの主要なソース文書は、docs/jnsa-conformance-statement.md (サプライヤ適合性宣言)と docs/jnsa-guideline-gap-review-2026-06-20.md (優先順位付けされたギャップレビュー)である。ギャップレビューは六件の指摘 (High二件、Medium四件、Low二件)を提起した。そのうち五件は着地しており、本章は 解決済みの状態と残存する留保事項の双方を記録する。全体を通じて支配する原則は、 憲章 §I「事実報告(Fact-Reporting)」(「断言できないことは断言しない」) である。すなわち、pverifyがある事実を確立できない場合、TOTAL_FAILED や TOTAL_PASSED を捏造するのではなく、正確なクローズドな列挙(閉じた語彙)の サブインディケーションを伴って INDETERMINATE へと降格する。この規律が強制される アーキテクチャについては第3章を、以下で参照されるインディケーション優先順位の 機構については第5章 §5.7 とResult Model(第14章)を参照のこと。
pverifyは高度電子署名(AdES)の検証器である。署名作成器でもなく、トラスト リストの運用者でもなく、業務上の判定エンジンでもない。pverifyが適合性を主張する 標準と、各主張の範囲は以下のとおりである。
| 標準 | 主題 | pverifyの主張 |
|---|---|---|
| RFC 5280 | X.509 パス検証 | §6.1 basic-plus: パス構築、有効期間、BasicConstraints、KeyUsage、名前制約、証明書ポリシー、CRLセマンティクス。crates/pverify-core/src/path/mod.rs、path/name_constraints.rs、path/policy.rs、path/bridge.rs。 |
| RFC 5652 | CMS SignedData | 単一署名者のSignedDataパース + signedAttrs/messageDigest/content-typeバインディング。crates/pverify-core/src/cms/signed_data.rs。複数署名者は意図的に拒否する。 |
| RFC 5035 | ESS signing-certificate v1/v2 | 署名証明書ハッシュバインディング(証明書すり替え防御)。 |
| RFC 3161 / 5816 | タイムスタンプトークン | TST CMS検証、messageImprint再計算、TSAチェーン構築 + RFC 5816 ESSバインディング。crates/pverify-core/src/timestamp.rs。 |
| RFC 6960 | OCSP | リクエスト構築、BasicOCSPResponseパース、§4.2.2.2 指定署名者の認可、鮮度、ノンス、署名。crates/pverify-core/src/revocation/ocsp.rs。 |
| RFC 7515 / 7797 | JWS / 非エンコードペイロード | JWS JSONシリアライゼーション署名入力の再計算、b64:false バリアント。crates/pverify-jades。 |
| ETSI EN 319 102-1 | 検証インディケーション | TOTAL_PASSED / INDETERMINATE / TOTAL_FAILED + クローズドなサブインディケーション列挙。crates/pverify-core/src/report/etsi.rs。 |
| ETSI EN 319 122 | CAdES | B-B / B-T / B-LT / B-LTA(archive-time-stamp-v3、§6.3.4 インプリント再計算)。 |
| ETSI EN 319 142 | PAdES | 埋め込みCMS + /DocTimeStamp 上のB-B / B-T / B-LT / B-LTA、/DSS+/VRI+revocationInfoArchival(ブランチ034)。 |
| ETSI EN 319 132 / TS 101 903 | XAdES | B-T / B-LT を伴うエンベロープドB-B / B-LTA(ArchiveTimeStamp インプリント、ETSI TS 101 903 v1.4.2 Annex A.1.5、ブランチ036)、Exclusive C14Nのみ。 |
| ETSI TS 119 182 | JAdES | JWS-JSON-シリアライゼーション B-B / B-T / B-LT / B-LTA(ブランチ 037; unprotected header の sigTst/xVals/rVals/arcTst を解析)。rfsTst/tstVd・sigD・Compact は拒否。 |
| ETSI EN 319 162 | ASiC | ASiC-S / ASiC-E コンテナ、内側署名はCAdES/XAdESへ委譲する。 |
| eIDAS | 規制の枠組み | AdESファミリとトラストリストモデル(EUトラステッドリスト、pverify-eutl)。 |
| CRYPTREC / NIST | アルゴリズムポリシー | オプトインの、VRTスコープのアルゴリズム妥当性評価(--algorithm-policy)。 |
| JNSA 署名検証ガイドライン 第 1.1 版 | 検証要件プロファイル | §17.4 で再掲するM/E/O適合性マトリクス。 |
最も重要な誠実性の表明は次のとおりである。pverifyからの TOTAL_PASSED は、 「pverifyが実行したすべてのチェックが、本章で開示する実装上の制約の範囲内で 肯定的に返った」ことを意味する。それは、当該署名クラスに対する必須(Mandatory) チェックのいずれかが未実装または部分的にしか実装されていない場合には、JNSAの VALID 判定と等価ではない。§17.4 の適合性マトリクスは、まさに、検証者 (信頼当事者)が所与の署名クラスに対してどの必須チェックが有効であったかを判断 できるようにするために存在する。
レポートの signatures[].format フィールドはクローズド列挙型 (SignatureFormat、crates/pverify-core/src/report/mod.rs:264)である。その ワイヤ値と意味は以下のとおりである。
| ワイヤ値 | ファミリ | 意味 |
|---|---|---|
CAdES-BES |
CAdES | ベースラインB / BES — 基本署名、信頼できるタイムスタンプなし。 |
CAdES-T |
CAdES | B + 1個以上の署名タイムスタンプ(RFC 3161)。 |
CAdES-LT |
CAdES | T + 埋め込み検証材料(certificate-values / revocation-values)。 |
CAdES-LTA |
CAdES | LT + archive-time-stamp-v3(EN 319 122-1 §6.3.4 インプリント再計算)。 |
PAdES-B |
PAdES | 埋め込みCMS上のベースラインB。 |
PAdES-B-T |
PAdES | B + 署名タイムスタンプ / /DocTimeStamp。 |
PAdES-B-LT |
PAdES | T + /DSS 経由の検証材料(ブランチ034)。 |
PAdES-B-LTA |
PAdES | LT + /DocTimeStamp アーカイブタイムスタンプ。 |
XAdES-B-B |
XAdES | 肯定的に検証されたエンベロープドXAdESベースラインB。 |
XAdES-B-T |
XAdES | + 検証済みの SignatureTimeStamp / SigAndRefsTimeStamp。 |
XAdES-B-LT |
XAdES | + 埋め込み CertificateValues / RevocationValues。 |
XAdES-B-LTA |
XAdES | + ArchiveTimeStamp インプリント検証(ETSI TS 101 903 v1.4.2 Annex A.1.5、ブランチ036)。インプリント = ds:Signature 全体の Exclusive C14N から後続 ArchiveTimeStamp ノードセットを減算したもの。 |
XAdES-unsupported |
XAdES | 検出のみの拒否経路(detached / enveloping / 非Exclusive C14N)。 |
JAdES-B-B |
JAdES | 肯定的に検証されたJWS-JSON-シリアライゼーションベースラインB。 |
JAdES-B-T |
JAdES | + sigTst 署名タイムスタンプ検証済み(ブランチ 037)。 |
JAdES-B-LT |
JAdES | + xVals/rVals 埋め込み証明書・失効情報消費済み(ブランチ 037)。 |
JAdES-B-LTA |
JAdES | + arcTst アーカイブタイムスタンプ検証済み; インプリント = protected.payload.sig.sigTst_b64u(ブランチ 037)。 |
JAdES-unsupported |
JAdES | 拒否経路(sigD、Compact シリアライゼーション、rfsTst/tstVd、非JAdES JWS)。 |
CAdESのレベル昇格の階梯は verify.rs:1034 で実装されている。エントリは、より 上位のレベルに対する構造的証拠が存在し検証された場合にのみ、BES → T → LT → LTA へと昇格する。同じ階梯の形状がPAdESおよびXAdESについても反映されている。
ギャップレビューのLow指摘「AdESレベルの命名はサポートを過大に表現しうる」は、 読者が全体を通じて保持すべき留保事項である。すなわち format 値は観測された 構造を報告するものであり、当該レベルに対するすべてのJNSA必須行が有効であった ことの保証ではない。たとえば PAdES-B-LT ラベルは、LT検証材料が存在し消費された ことを主張するが、--algorithm-policy が供給されていない限り、アルゴリズム妥当性 の必須チェックが判定に影響するゲートであったことは主張しない(§17.5)。 適合性マトリクスが、「観測された構造」から「実行されたチェック」への権威ある マッピングである。
「断言できないことは断言しない」規律は、一群の拒否サブインディケーションとして 表面化する。これらは意図的なINDETERMINATEの結果であり、決して捏造された失敗では ない。
| 条件 | サブインディケーション | コード箇所 |
|---|---|---|
| 複数署名者のCMS | cms_multi_signer_unsupported |
verify.rs:494、1912 で発出 |
| サポート外のXAdES C14N | xades_unsupported_canonicalization |
XAdES経路 |
| Detached / enveloping のXAdES | xades_unsupported_profile |
XAdES経路 |
| XAdES署名者証明書が解決不能 | xades_signer_certificate_unavailable |
XAdES経路 |
JAdES Compact / sigD |
jades_unsupported_serialization |
JAdES経路 |
| JAdES B-T+ / 非ベースライン | jades_unsupported_profile |
JAdES経路 |
| JAdES署名者証明書が不在 | jades_signer_certificate_unavailable |
JAdES経路 |
| 認識不能なZIP / コンテナ | asic_unsupported_container |
ASiC経路 |
| 埋め込み失効情報なしのオフライン | revocation_not_checked_offline |
失効検証パイプライン |
| 失効が確認されたTSA証明書 | revoked_no_poe |
TSA失効レイヤ |
| オプトインのアルゴリズムポリシー失敗/確証不能 | crypto_constraints_failure_no_poe |
アルゴリズム妥当性レイヤ |
複数署名者の拒否は、本原則の規範的な例である。029以前のパーサは、最初の SignerInfo のみを黙って保持していた。現行コードは切り詰めることを拒否し、 誤解を招くスコープの結果を提示するのではなく、INDETERMINATE / cms_multi_signer_unsupported へとルーティングする。
JNSAは VALID / INVALID / INDETERMINATE で語る。pverifyはETSI EN 319 102-1 のインディケーションを報告する。マッピング(docs/jnsa-conformance-statement.md より)は以下のとおりである。
| JNSA用語 | pverify etsi_indication.indication |
pverifyにおける意味 |
|---|---|---|
VALID |
TOTAL_PASSED |
pverifyが実行したすべてのチェックが肯定的に返った、§17.4 の実装上の制約の範囲内で。必須チェックのいずれかが未実装の場合はJNSAの VALID と等価ではない。 |
INVALID |
TOTAL_FAILED |
pverifyが実行したチェックのうち少なくとも一つが否定的に返った(暗号検証して失敗、失効証明書、インプリント不一致)。 |
INDETERMINATE |
INDETERMINATE |
利用可能な情報が判断するのに不十分であった(オフライン + 埋め込み失効情報なし、サポート外アルゴリズムOID、到達不能なレスポンダ、等)。 |
終了コードに関する留保事項(自動化にとって重要)。 CLIはあらゆる報告された 判定について 0 で終了する — TOTAL_FAILED および INDETERMINATE を含む。 非ゼロの終了コードは、呼び出し/実行時のエラー(不正な引数、I/O失敗)のために 留保されている。自動化は終了コード 0 を署名の有効性として扱ってはならない。 JSON出力から signatures[*].etsi_indication.indication を読み取らなければ ならない。これはギャップレビューのMedium指摘「結果語彙と終了コードのセマンティクス には検証者(信頼当事者)向けのマップが必要」を解決する。
アグリゲータ(aggregate_etsi、verify.rs:2343)が用いる重大度の順序付けは TOTAL_FAILED > INDETERMINATE > TOTAL_PASSED であり、最初に見つかったものが 勝つ(first-finding-wins)。レイヤ化された後処理(署名者バインディング、 content-type、オプトインのアルゴリズム妥当性、TSA失効)は、TOTAL_FAILED でない 基底を降格することしかなく、証明された TOTAL_FAILED を決して隠蔽しない。この 「隠蔽しない」規律はそれ自体が適合性の特性である。すなわち、実際の暗号的失敗が、 後続の決定的でないチェックによって隠されることはありえない。
記法: M = 必須(Mandatory)、E = 存在する場合は必須(Mandatory-if-Exists)、 O = 任意(Optional)。ステータス: Y = 実装済みかつ判定に影響する、 F = 事実として記録され、判定に影響しない(意図的な逸脱)、P = 部分的、 N = 未実装(明示的な制限)。以下のマトリクスは docs/jnsa-conformance-statement.md を再掲したものであり、各行はコードに根拠 付けられている。
| JNSA参照 | M/E/O | ステータス | レポートフィールド | コード根拠 / 制限 |
|---|---|---|---|---|
| フォーマット / パッケージングの認識 | M | Y | signatures[].format |
拡張子ではなく内容スニフ(verify.rs:359 detect_format)。 |
署名対象コンテンツの messageDigest |
M | Y | content_digest_check |
RFC 5652 §11.2 再計算。 |
| 署名者証明書のアンカーまでのチェーン | M | Y | chain_result |
RFC 5280 §6.1(path/mod.rs)。 |
| 検証時点での署名者証明書の有効性 | M | Y | chain_result.steps[].validity_at_time |
基準時刻 = オブジェクトごとのVRT(§17.4.7)。 |
| 署名者証明書の失効 | M | P | chain_result.steps[].revocation |
CRL + OCSP。オフライン/from-bundleは誠実に revocation_not_checked_offline へ降格する。 |
| TSAチェーンの失効 | M | Y | timestamps[].chain_result.steps[].revocation |
全フォーマットで一貫(033)、失効 → revoked_no_poe。 |
| 検証時点でのアルゴリズム妥当性 | M | P | algorithm_validity(オプトイン)+ weak_algorithms[] |
オプトイン判定(032)、デフォルトオフ ⇒ 事実のみ。§17.5 を参照。 |
| オブジェクトごとの検証基準時刻 | M | Y | signatures[].vrt.{…} |
オブジェクトごとのVRTエンジン(031)、§17.4.7 を参照。 |
署名者証明書の失効の行が Y ではなく P とマークされているのは、誠実な オフラインおよびfrom-bundleのモードが、ライブチャネルがなく埋め込み//DSS 材料も存在しない場合に正当に INDETERMINATE / revocation_not_checked_offline を生成しうるためである — pverifyはレスポンダ接触を捏造しない(憲章 §VII)。 これは正しい適合性の姿勢であるが、JNSAの意味における必須チェックの部分的な充足 である(チェックが常に結論を出すとは限らない)。
| JNSA参照 | M/E/O | ステータス | レポートフィールド |
|---|---|---|---|
| CMS SignedDataがパースできる | M | Y | パースエラー時の拒否エントリ |
SignerInfo.sid による署名者識別 |
M | Y | signer_identifier_check(issuerAndSerial / SKI) |
| ESS signing-certificate v2 バインディング | E | Y | signing_certificate_binding(RFC 5035) |
messageDigest 署名属性 |
M | Y | content_digest_check |
content-type の一致 |
E | Y | content_type_check(RFC 5652 §5.3) |
| 複数署名者のSignedData | E | N | cms_multi_signer_unsupported(INDETERMINATE — 誠実な拒否) |
signature-time-stamp(CAdES-T) |
E | Y | timestamps[] ロール signature_timestamp |
archive-time-stamp-v3(CAdES-A) |
E | Y | timestamps[] ロール archive_timestamp + インプリントチェック |
埋め込み revocation-values(LT/LTA) |
E | Y | 失効検証パイプラインが消費する |
| JNSA参照 | M/E/O | ステータス | レポートフィールド / 制限 |
|---|---|---|---|
| XMLパース + 正規化 | M | P | Exclusive C14Nのみ → さもなくば xades_unsupported_canonicalization。 |
| リファレンスダイジェスト vs c14nノード | M | Y | xades_reference_checks[]、改ざん → xades_reference_digest_mismatch(TOTAL_FAILED)。 |
| Enveloped / enveloping / detached | M | P | Envelopedのみ、さもなくば xades_unsupported_profile。 |
SigningCertificateV2 バインディング |
E | Y | signing_certificate_binding。 |
KeyInfo/X509Data 経由の署名者証明書 |
M | Y | 外部解決はスコープ外 → xades_signer_certificate_unavailable。 |
SignatureTimeStamp / SigAndRefsTimeStamp |
E | Y | timestamps[](019)。 |
ArchiveTimeStamp(XAdES-B-LTA) |
E | Y | timestamps[] ロール archive_timestamp + インプリントチェック(036)。インプリント不一致 → archive_timestamp_imprint_mismatch(TOTAL_FAILED)。 |
| JNSA参照 | M/E/O | ステータス | レポートフィールド / 制限 |
|---|---|---|---|
| ByteRangeがリビジョン末尾をカバーする | M | Y | pdf_byte_range、pdf_covers_eof、改ざん → pdf_byte_range_does_not_cover_eof。 |
| バイトレンジ下の埋め込みCMS | M | Y | CAdESと同一。 |
--required-policy の強制 |
E | Y | policy_check、PAdESはuser-initial-policy-setをCAdESと同一に絞り込む(029)。 |
/DSS + /VRI の消費 |
E | Y | validation_objects[].origin = pdf_dss / pdf_vri(034)。§17.6 を参照。 |
CMS内の revocationInfoArchival |
E | Y | validation_objects[].origin = signature_embedded(034)。§17.6 を参照。 |
/DocTimeStamp(PAdES-LTA) |
E | Y | timestamps[] ロール archive_timestamp(009)。 |
| JNSA参照 | M/E/O | ステータス | レポートフィールド / 制限 |
|---|---|---|---|
| JWS JSONシリアライゼーションがパースできる | M | Y | Compact → jades_unsupported_serialization。 |
| 署名入力の再計算(RFC 7515/7797) | M | Y | b64:true および b64:false。 |
x5c / x5t#S256 署名者解決 |
M | Y | 欠落 → jades_signer_certificate_unavailable。 |
JAdES B-B sigT + 署名証明書バインディング |
E | Y | signing_certificate_binding。 |
JAdES B-T(sigTst) |
E | Y | 署名タイムスタンプ検証済み; VRT パイプラインへ接続(037)。 |
JAdES B-LT(xVals/rVals) |
E | Y | 埋め込み証明書・失効情報を既存失効パイプラインへ供給(037)。 |
JAdES B-LTA(arcTst) |
E | Y | アーカイブタイムスタンプ + 非循環インプリント protected.payload.sig.sigTst(037)。 |
rfsTst / tstVd |
E | N | スコープ外 → jades_unsupported_profile。 |
sigD detached ペイロード |
E | N | → jades_unsupported_serialization。 |
ES512 / Ed448 / alg:none |
M | F | 拒否 signature_algorithm_unsupported — 誠実な「暗号処理を実行しなかった」(jades.rs:184)。 |
ASiC(EN 319 162): ZIP整合性 + パストラバーサル拒否(エントリは決してディスクへ 書き出されない)、マニフェストダイジェストの一致(改ざん → asic_data_object_digest_mismatch)、データオブジェクトの存在(欠落 → asic_data_object_missing)、および認識可能なASiCゲート(認識不能 → asic_unsupported_container、決してCAdESとして誤分類されない)は、すべて M / Y である。
RFC 3161 タイムスタンプトークン(全フォーマット): TSTパース、messageImprint 再計算(改ざん → archive_timestamp_imprint_mismatch、アーカイブロールでは TOTAL_FAILED)、アンカーまでのTSAチェーン、genTime でのTSA有効性、TSA失効 (失効 → revoked_no_poe)、およびインプリントアルゴリズムがサポート集合内 (SHA-1/256/384/512について archive_timestamp_imprint_unsupported_algorithm) は、すべて M / Y である。
トラストアンカー: inputs[] 内でハッシュにより記録されるプロビナンス(EUTL、 AATL、GPKIバンドル、ブラウザストアミラー)、自己署名の自己整合性チェック、および 検証時点での失効(期限切れ → anchor_expired_at_time)は、すべて M / Y である。
ギャップレビューの最初のHigh指摘 — 「長期署名の検証基準時刻が完全には適用されて いない」 — は、オブジェクトごとのVRTエンジン(crates/pverify-core/src/vrt/、 ブランチ031)によって解決される。本エンジンは、JNSA §4 の再帰的な外側カバー規則 により、すべてのオブジェクト(署名者チェーン、署名者の署名/材料、各署名 タイムスタンプ、各アーカイブタイムスタンプ)について固有の検証基準時刻を導出する。 有効な外側タイムスタンプによってカバーされるオブジェクトは、そのタイムスタンプの GenTimeで判定される(連鎖したアーカイブタイムスタンプについては再帰的に)。カバー されないオブジェクトは request.verification_time(--at / 実行時刻)へ フォールバックする。
その後、カーネルは各オブジェクトをそれ自身のVRTで再検証する — 署名者チェーンは verify.rs:775 で、各タイムスタンプトークンはトークンごとのループ内で、各TSA チェーンの失効は apply_tsa_chain_revocation_for_token(033)経由でオブジェクト 自身のVRTで行う。決定的に重要なのは、VRTエンジンが純粋計算であることである。 すなわち時刻ソースとして request_at のみを読み取り、Clock::now を決して呼ばず、 カバーグラフは下流の失効/アルゴリズムレイヤによって再導出または変更されることが ない。選択されたVRTとその derivation タグ(カバーするTSが将来GenTime / 非アンカ TSA / インプリント不一致のために拒否された際の、候補ごとの demotion_reason を 含む)は signatures[].vrt の下に逐語的に表出され、ブロックの形状はCAdES / PAdES / XAdES / JAdES / ASiC を通じて同一である。エンジンの内部については第12章を 参照のこと。
ギャップレビューの二番目のHigh指摘 — 「アルゴリズム妥当性はフラグ付けされるのみで 強制されない」 — は、ブランチ032(algorithm_policy/、 report/algorithm_validity.rs)によって解決されるが、その解決は意図的に オプトインであり、デフォルトの挙動は検証者(信頼当事者)が理解すべき意図的な 逸脱である。
デフォルトの挙動(--algorithm-policy なし): accept-and-flag。 脆弱な アルゴリズム(SHA-1、脆弱なRSA鍵サイズ)は認識され signatures[].weak_algorithms[] に表出されるが、判定を変更しない。したがってSHA-1を用いる署名でも、他の すべてのチェックが通れば TOTAL_PASSED になりうる。これは透明であり、事実報告の 姿勢(憲章 §I)である。すなわちpverifyは観測されたアルゴリズムの事実を 報告し、accept/rejectのポリシーは運用者に委ねる。JNSAの用語では、必須の アルゴリズム妥当性チェックはデフォルトでは判定ではなく事実として実装されて いる — ゆえに §17.4.1 における P ステータスである。
オプトイン判定(--algorithm-policy [FILE])。 ポリシーが供給されると、各 オブジェクトは発効日と最小鍵サイズを持つバージョン管理されたポリシーに対して判定 され、そのオブジェクト自身のオブジェクトごとのVRT(§17.4.7)の時点で評価される。 失敗または確証不能なアルゴリズムチェックは、インディケーションを INDETERMINATE / crypto_constraints_failure_no_poe へ降格する — 決して TOTAL_FAILED には ならず、「断言できないことは断言しない」を尊重する(陳腐化したが破られていない アルゴリズムは証明の欠如であって、偽造の証明ではない)。デフォルトポリシーは CRYPTREC優先 / NIST副次(SHA-1とRSA < 2048 は2014-01-01にサンセット)である。 配備はより厳格なファイルを供給してよい。ポリシーをオフにすると、レポートは schema_version 文字列を除いて032以前とバイト単位で同一である。 docs/algorithm-validity-policy.md を参照のこと。
これはETSI EN 319 102-1 の CRYPTO_CONSTRAINTS_FAILURE_NO_POE を反映するが、 それを失敗ではなくINDETERMINATEへマッピングする — これはツール全体の規律と整合した 意識的な適合性の選択である。
/DSS / /VRI / revocationInfoArchival(解決済み)ギャップレビューの指摘 — 「PAdESのDSS/VRI検証材料が消費されない」 — は、 ブランチ034によって解決される。034以前は、PAdESの失効呼び出しがパイプラインに空の 埋め込みスライス(&[],&[])を供給していたため、文書が自身の失効証明を保持して いるにもかかわらず、完全に証拠付けられたオフラインのPAdES-LT/LTAが INDETERMINATE / revocation_not_checked_offline で停滞していた。この解決は、 新たな判定ロジックをゼロにして、その材料を既存のパイプラインへとスレッドする。
ホスト抽出(憲章 §V — コアにPDFパースなし)。 ホスト抽出器 (pverify-cli/src/pdf.rs はネイティブで、web/pverify-wasm/src/lib.rs は ブラウザ内で、バイト単位で同一)は、カタログレベルの /DSS 辞書の /Certs、 /CRLs、/OCSPs プール — すべての差分PDFリビジョンにわたって統合され、DERに よって重複排除される — と、各署名の /Contents の大文字16進SHA-1をキーとする /VRI サブ辞書(PAdES / EN 319 142-1 §5.4)をパースする。その結果は追加的な pdf_validation_data フィールド(PdfValidationData、 crates/pverify-core/src/pades/mod.rs:80)へ供給される。非PDFまたはDSSなしの 入力は空の値を持ち、バイト単位の同一性を保つ(FR-008)。
オブジェクトごとの解決(純粋計算)。 resolve_material (pades/mod.rs:148)は、オブジェクトごとに優先材料をVRI優先、DSSグローバル フォールバック(クラリフィケーションQ1)として解決する。すなわち、オブジェクトの /Contents SHA-1をキーとする /VRI エントリが存在し空でない場合、そのサブセットが 使用される(プロビナンス PdfVri)。さもなくば文書レベルの /DSS プールが使用 される(プロビナンス PdfDss)。解決されたCRL/OCSP DERは次に、以前は &[],&[] を受け取っていた既存の呼び出しへと流れ込む。すなわち署名者チェーンに対する apply_revocations(pades/mod.rs:702)と、各タイムスタンプのTSAチェーンに対する apply_tsa_chain_revocation_for_token(pades/mod.rs:1010)であり、それぞれが 当該オブジェクト自身のVRTで行われる。
三つの意図的な制約、それぞれが適合性の事実:
DSS /Certs は決してトラストアンカーではない(Q7 / FR-005)。それらは中間 / リーフのパス構築候補のみである — pades/mod.rs:82(「NEVER trust anchors」)と 呼び出し箇所(pades/mod.rs:665)で文書化されている。文書は /DSS に自己署名 ルートを保持することで自身のトラストをブートストラップすることはできない。
全モードにおける追加的フォールバック(Q5)。材料は、ライブ/CDPチャネルが 判定不能なときにのみ発火する埋め込みCRL/OCSPチャネルに乗る(019の前例)。 したがってオンラインのライブ取得の成功はバイト単位で同一のままであり、DSS材料は 証拠がなかった箇所に証拠を供給できるのみであって、取得された回答を上書きする ことは決してない。失効検証パイプラインはCRLを発行者+シリアルで、OCSPをcertIDで 照合するため、追加的な材料がgoodの結果を捏造することは決してありえない (上位集合は安全である)。
新たな判定ロジックはゼロ(Q2)。DSS由来のCRL/OCSPは既存の RevocationOutcome 語彙を逐語的に生み出す。すなわち失効した署名者は既存の TOTAL_FAILED であり、失効したTSAは033の revoked_no_poe である。新たな結果も 新たなサブインディケーションも導入されなかった。唯一のワイヤレベルの変更は プロビナンスである。すなわち二つの追加的な ValidationObjectOrigin 値、 pdf_dss と pdf_vri(report/validation_objects.rs:113、:118)である。
Adobe revocationInfoArchival。 独自のCMS属性(OID 1.2.840.113583.1.1.8)は埋め込みCMSから射影され(pades/mod.rs:1340 以降、 ベストエフォートのDERウォーク)、署名者チェーンの失効材料へとマージされる — pdf_dss ではなく signature_embedded とタグ付けされる。なぜならそれはPDF構造では なくCMS内に存在するためである(R-5)。失効をこの属性のみに保持する生成者は、 等価な /DSS から到達するであろうのと同じ決定的な結果に到達する。
正味の適合性への効果: 完全に証拠付けられたオフラインのPAdES-LTは、いまや INDETERMINATE へ降格する代わりに TOTAL_PASSED に到達する。これはJNSAが PAdESをCAdES/XAdESとは構造的に異なるものとして明示的に取り扱うこと(検証材料が PDF辞書内に存在しうる)を満たす。スコープ外(かつそのように開示される): DSSの 生成、DSS証明書のアンカーとしての利用、VRI /TU//TS 内部整合性チェック、 および revocationInfoArchival 内の otherRevInfo。ならびにCAdES/XAdES/JAdES/ASiC に対する等価なDSS機構(XAdESは019経由ですでに自身の埋め込み材料を消費している)。
ギャップレビューの指摘 — 「TSA証明書失効のカバレッジが不均一」 — は、 ブランチ033によって解決される。033以前は、一部のXAdES経路は追加的なTSA失効処理を 持っていたが、CAdES/PAdES経路はTSA証明書チェーンに同じパイプラインを一様には 適用していなかった。共有ヘルパ apply_tsa_chain_revocation_for_token (verify.rs:1552)は、いまやすべてのタイムスタンプオブジェクトに対して、すべての フォーマット(CAdES / PAdES / XAdES / JAdES / ASiC)で、当該オブジェクト自身の オブジェクトごとのVRT(§17.4.7)で呼び出され、署名者チェーンの失効検証パイプライン とオフラインポリシーを逐語的に再利用する。
結果は各タイムスタンプ自身の timestamps[].chain_result.steps[].revocation に 報告され、署名者失効とは区別される。失効が確認されたTSAは、インディケーションを INDETERMINATE / revoked_no_poe へ降格する — 決して TOTAL_FAILED にはならない。 なぜなら後に失効したTSAからのタイムスタンプは存在証明(PoE)の欠如であって、偽造の 証明ではないためである(時刻証拠に適用された「断言できないことは断言しない」原則)。 判定不能なTSA失効の状態がINDETERMINATEを下回ることは決してない。
ギャップレビューのMedium「サプライヤ適合性宣言が欠落している」は、 docs/jnsa-conformance-statement.md 自体の存在 — §17.4 で再掲しコードに根拠付けた M/E/Oマトリクス — によって解決される。これは明示的に第三者評価ではない。それは pverifyのメンテナがpverifyの挙動について述べたものである。独立した評価のために 提供される成果物は、クローズドなJSON出力スキーマ(ルートの report-schema.json / specs/001-verify-cades-pades/contracts/report-schema.json)と fixtures/ 配下の フィクスチャである。結果語彙と終了コードのマップ(§17.3)は、関連するMedium 「結果語彙と終了コードのセマンティクスには検証者(信頼当事者)向けのマップが必要」 に対処する。
以下は誠実に開示される。項目1〜3は意図的な設計上の選択であり、欠陥ではない。 項目4〜5は真の残存スコープである。
脆弱なアルゴリズムはデフォルトでaccept-and-flagであり、rejectではない (§17.5)。必須のアルゴリズム妥当性チェックはデフォルトで事実であり、 --algorithm-policy の下でのみ判定となる。論拠: 事実報告(憲章 §I)。 運用者は自身のaccept/rejectポリシーを適用する。
複数署名者のCMSはモデル化されず拒否される。 各 SignerInfo はまだ個別の signatures[] エントリとして発出されていない。文書は最初の署名者へ黙ってスコープ するのではなく、INDETERMINATE / cms_multi_signer_unsupported へルーティングする。 署名者ごとに1エントリのモデル化は将来のスコープである。
終了コードは判定ではない(§17.3)。READMEと適合性宣言に文書化されている。
署名ポリシーのモデル化は --required-policy <OID> に限定される(ギャップ レビューLow)。pverifyは証明書ポリシーを処理し(RFC 5280 §6.1 valid_policy_tree、マッピング、anyPolicy、抑止フラグ、path/policy.rs)、 --required-policy 経由でuser-initial-policy-setを受け入れる。それはまだ、 アルゴリズム規則、受け入れ可能なTSAアンカー/タイムスタンプポリシー、パス制限、 任意から必須へのエスカレーション、またはユースケースに必要なAdESレベルの制約を カバーする、より広範なバージョン管理された検証ポリシーファイルを公開していない。 政府の配備はしばしば証明書ポリシーOIDを超えたポリシー選択を望む。これが推奨される 次の拡張である。
AdESレベルの命名は適合性宣言の留保事項を必要とする(ギャップレビューLow)。 format 値は観測された構造を報告する。検証者(信頼当事者)は、どの必須行が 有効であったかを知るために §17.4 と併せてそれを読むべきである。推奨される将来の UI改良は、「観測された構造」を「完全に検証されたレベル」とは別に表示し、後者を 当該レベルのすべてのJNSA M/E行に対してゲートすることである。
加えて、フォーマットスコープの制限が引き続き有効であり、それら自体がバグではなく 適合性の事実である。すなわち、XAdESはenveloped + Exclusive-C14Nのみ(他の プロファイル/正規化はINDETERMINATEへ拒否する)、JAdESはB-B JWS-JSONのみ (B-T+、sigD、Compact、alg:none、ES512/Ed448 はINDETERMINATEへ拒否する)、 そしてDSS等価の材料はPAdES(034)とXAdES(019)について消費されるが、CAdES/JAdES/ASiC についてはまだ既存の埋め込みチャネルを超えて射影されていない。
| # | 重大度 | 指摘 | ステータス | 解決 |
|---|---|---|---|---|
| 1 | High | LTV検証基準時刻が完全には適用されていない | RESOLVED | オブジェクトごとのVRTエンジン(031、schema 1.3.0)。§17.4.7。 |
| 2 | High | アルゴリズム妥当性がフラグ付けのみで強制されない | RESOLVED | オプトインのVRTスコープのアルゴリズムポリシー(032、schema 1.4.0)。§17.5。 |
| 3 | Medium | TSA失効のカバレッジが不均一 | RESOLVED | 全フォーマットにわたる共有TSA失効ヘルパ(033、schema 1.5.0)。§17.7。 |
| 4 | Medium | PAdES /DSS//VRI が消費されない |
RESOLVED | DSS/VRI/revocationInfoArchival の消費(034、schema 1.7.0)。§17.6。 |
| 5 | Medium | サプライヤ適合性宣言が欠落 | RESOLVED | docs/jnsa-conformance-statement.md + 本章。§17.8。 |
| 6 | Medium | 結果語彙 / 終了コードのマップ | RESOLVED | §17.3(+ README、web UI)。 |
| 7 | Low | 署名ポリシーが過小にモデル化されている | OPEN | --required-policy のみ、バージョン管理された検証ポリシーファイルは将来のスコープ。§17.9(4)。 |
| 8 | Low | AdESレベルの命名はサポートを過大に表現しうる | OPEN(緩和済み) | 適合性宣言が留保事項を提供する、観測vs検証のUI分離は将来のスコープ。§17.9(5)。 |
上記のすべてを担うレポート形状のバージョンは SCHEMA_VERSION = "1.10.0"(crates/pverify-core/src/report/schema.rs)で あり、ルートの report-schema.json へ1:1でミラーされている。上記の各解決済み指摘は スキーマを追加的に(MINOR)バンプしており、追加的バージョニングの不変条件と整合する。 すなわち、クローズドな列挙値の追加(例: pdf_dss / pdf_vri / revoked_no_poe / crypto_constraints_failure_no_poe)または任意フィールドの追加はMINORバンプであり、 いずれかの削除または再利用はMAJORとなる。
ブランチ036は XAdES B-LTA のギャップを解消する。ArchiveTimeStamp 要素 (ETSI TS 101 903 v1.4.2 Annex A.1.5)のインプリントが再計算・検証される ようになった。インプリント入力は ds:Signature 要素の Exclusive C14N 直列化 であり、後続の ArchiveTimeStamp 要素のノードセットを正規化出力から減算した もの(非循環インプリント構造)である。これは bergshamra-c14n の NodeSet subtract API により実現されており、新たな依存関係を必要としない。
SignatureFormat::XadesBLta("XAdES-B-LTA")バリアントがレポート列挙型に 追加された。ArchiveTimestampImprintRecord ワイヤ型(CAdES ですでに存在)は 共有されており、新たなスキーマ型もスキーマバンプも不要である。合成フィクスチャ (build_blta / build_blta_tampered)とエンドツーエンドテスト (xades_blta_e2e)が、一致・不一致の両経路をカバーする。
スコープ外: XPath変換を使用するDSS XAdES-LTAフィクスチャ(非対応)、 ArchiveTimestampV3(CAdES連結方式)、複数連鎖ArchiveTSのループ処理、および JAdES arcTst。
本付録は、監査者が pverify レポートをそのソースとともに読む際に必要となる リファレンス資料を集約したものである。具体的には、(18.1) サーベイ語彙を拡張 した統合用語集、(18.2) それをシリアライズする Rust の型に基づく、トップレベル レポートスキーマのフィールドごとのリファレンス、(18.3) スキーマが許容する クローズドな列挙(閉じた語彙)のカタログ(ETSI インディケーション/サブ インディケーション、RevocationOutcome、ValidationObjectOrigin)、(18.4) v0.1 から v1.1.0 の設計目標に至るまでのスペック/バージョン系譜(各 Spec Kit スライスのディレクトリを、その機能および schema_version への影響に対応 づけたもの)、そして (18.5) どのクレートおよびモジュールがどの関心事を所有 するかを示すソースファイルマップを収録する。
本章のすべての記述は、ブランチ 036-xades-blta におけるワークスペースに 基づいている。レポートの形に関する唯一の真実の源(single source of truth)は、 crates/pverify-core/src/report/mod.rs を根とする Rust 型ツリーであり、その クローズドな列挙は crates/pverify-core/src/report/etsi.rs にある。JSON Schema のミラーは specs/001-verify-cades-pades/contracts/report-schema.json に存在し、スキーマ整合性テストによって Rust 定数と 1:1 で照合される(WASM クリーンな境界については第3章を、レポート導出パイプラインについては第14章を 参照)。
サーベイ段階の用語集(以下に再掲・拡張する)は、AdES、PKI、および pverify 固有の語彙を定義する。本節では、コードに照らして正確である用語をそのまま 保持し、ずれがあれば補正し、本章および隣接する章で繰り返し登場するが サーベイ集合に含まれていなかった用語を追加する。用語はドメインごとに グループ化されており、グループ内では読みやすさのために並べられ、アルファ ベット順ではない。
| 用語 | 定義 |
|---|---|
| AdES | 高度電子署名(Advanced Electronic Signature)。pverify が検証する ETSI 署名ファミリの総称: CAdES(CMS / EN 319 122)、PAdES(PDF / EN 319 142)、XAdES(XML / EN 319 132)、JAdES(JWS / TS 119 182)、および ASiC(EN 319 162)によるパッケージ化。 |
| B-B (BES) | ベースラインレベル B: 署名証明書と署名属性を持つ基本的な署名であり、信頼できるタイムスタンプを持たない。CAdES、PAdES、XAdES、JAdES でサポートされる。SignatureFormat(report/mod.rs:264)におけるワイヤ値は CAdES-BES、PAdES-B、XAdES-B-B、JAdES-B-B。 |
| B-T | ベースラインレベル T: B に加えて、少なくとも1つの信頼できる署名タイムスタンプ(RFC 3161)を持つ。CAdES(CAdES-T)、PAdES(PAdES-B-T)、XAdES(XAdES-B-T)でサポートされるが、現スライスでは JAdES については拒否(INDETERMINATE / jades_unsupported_profile)される(report/mod.rs:354)。 |
| B-LT | ベースラインレベル LT(Long-Term / 長期): T に加えて、後の検証のために署名が自己完結するように、埋め込まれた検証材料(certificate-values、revocation-values)を持つ。CAdES(CAdES-LT)、PAdES(PAdES-B-LT、ブランチ 034 以降は /DSS 経由)、XAdES(XAdES-B-LT)でサポートされる。 |
| B-LTA | ベースラインレベル LTA(Long-Term with Archive / 長期アーカイブ付き): LT に加えて、構造全体を再保護するアーカイブタイムスタンプを持つ。CAdES(CAdES-LTA)は archive-time-stamp-v3(ETSI EN 319 122-1 §6.3.4 のインプリント再計算)を用い、PAdES(PAdES-B-LTA)は /DocTimeStamp を用い、XAdES(XAdES-B-LTA)は ETSI TS 101 903 v1.4.2 Annex A.1.5 に従う ArchiveTimeStamp を用いる(ブランチ 036)。 |
| XAdES-B-LTA | XAdES の長期アーカイブレベル。ArchiveTimeStamp 要素のインプリントを再計算する: 入力は ds:Signature 要素の Exclusive C14N であり、後続の ArchiveTimeStamp 要素のノードセットを減算したもの(非循環構造、bergshamra-c14n NodeSet subtract API)。SignatureFormat におけるワイヤ値は "XAdES-B-LTA"。ArchiveTimestampImprintRecord は CAdES と共有。インプリント不一致は archive_timestamp_imprint_mismatch(TOTAL_FAILED)を生じる。 |
| ArchiveTimeStamp | XAdES の xades:ArchiveTimeStamp 非署名プロパティ(ETSI TS 101 903 §7.5.1)。後続の ArchiveTS ノードセットを除いた ds:Signature 全体を対象とした RFC 3161 TST を担持する。CAdES の archive-time-stamp-v3 属性や PAdES の /DocTimeStamp とは別の機構だが、いずれも role = archive_timestamp を持つ同一の TimestampToken ワイヤ表現を共有する。 |
| LGPKI2 | Local Government PKI 2 — J-LIS(地方公共団体情報システム機構)が運営する日本の地方公共団体向け公開鍵基盤。OrgCA (R2) PAdES-LT/LTA の off-by-1 は v1.3.0 で修正済み。v1.3.1 では dirname_to_ldap_url サフィックスパターンにより CN=CRL{N},OU=Organization CA R2,O=LGPKI2,C=JP の DirectoryName CDP → ldap://www.lgpki.go.jp:389 を合成。 |
| CdpEntry | 構造化された CDP(CRL 配布点)レコード(report/mod.rs): kind(uri_http / uri_ldap / uri_other / dirname)、value(文字列)、fetchable(bool)。schema 1.8.0 → 1.9.0 バンプにおいてフラットな cdp_uris: Vec<String> を置き換えた。DirName CDP(例: GPKI OSCA)を表面化する。 |
| DocTimeStamp | あるリビジョンの /ByteRange を対象に RFC 3161 TST を担持する PDF /Type /DocTimeStamp 辞書 — PAdES のアーカイブタイムスタンプ機構である。role = archive_timestamp を持つ TimestampToken として表出される。両 PDF 抽出器(pverify-cli/src/pdf.rs、web/pverify-wasm/src/lib.rs)が PdfSigDictType でタグ付けすることで、コアは PDF モデルに依存しないまま保たれる(スライス 009)。 |
| 用語 | 定義 |
|---|---|
| RFC 5280 パス検証 | X.509 §6 の basic-plus アルゴリズム: 発行者/主体者 DN 照合によってリーフ→アンカーを構築し、有効期間、BasicConstraints、KeyUsage、名前制約および証明書ポリシーを検査する。crates/pverify-core/src/path/mod.rs に DFS によるパス構築、バックトラック、および DER フィンガープリントによるサイクル検出とともに実装されている。 |
| ブリッジCA | 複数のドメインルートと相互認証された CA(例: GPKI BridgeCA)であり、ドメイン間トラストを可能にする。pverify は 0..N 個のブリッジを横断する DFS+バックトラックの相互認証証明書のトラバーサルを実行する(path/bridge.rs)。非推奨の bridge_ca_required_unsupported サブインディケーションは、保存済みレポートのデシリアライズのために保持されるが、もはや発行されない(v0.3 以降)。 |
| 名前制約 | RFC 5280 §4.2.1.10 の permittedSubtrees(許可サブツリー)/excludedSubtrees(除外サブツリー)の状態機械(path/name_constraints.rs)。違反は INDETERMINATE indeterminate_named_constraint へ、サポートされない GeneralName 形式は name_constraints_unsupported_form へ、不正な形式の拡張は name_constraints_malformed へ振り分けられる。 |
| 証明書ポリシー/ポリシー処理 | RFC 5280 §6.1 の valid_policy_tree(有効ポリシーツリー)処理、ポリシーマッピング、anyPolicy および inhibit_* の取り扱い(path/policy.rs)。user-initial-policy-set は VerificationRequest.required_policies(CLI --required-policy)を介して供給され、空は {anyPolicy} を意味する。空のツリーは requireExplicitPolicy が発火した場合にのみ失敗し(RFC 5280 §6.1.5 — PR #48 の修正を参照)、indeterminate_policy_rejected を生じる。 |
| トラストアンカー | 検証者がアプリオリに信頼する自己署名ルート(crate::traits::TrustAnchor、traits.rs: der_bytes、SHA-256 フィンガープリント、生の主体者 DN、validity_window_covers_request_time フラグ、AKID タイブレーク用 subject_key_identifier)。ホスト側でロードされる。DSS /Certs は決してアンカーに昇格されない(FR-005、Q7)。 |
| OCSP | RFC 6960 Online Certificate Status Protocol。pverify はリクエストを構築し、BasicOCSPResponse をパースし、§4.2.2.2 の指定署名者の認可、鮮度、ノンスおよび署名を強制する(revocation/ocsp.rs)。 |
| CRL | RFC 5280 Certificate Revocation List(証明書失効リスト)。pverify は CertificateList をパースし、間接CRLの検出および陳腐化を扱う(revocation/crl.rs、revocation/indirect.rs)。埋め込み CRL チャネルは、CDP/ライブチャネルが判定不能(indeterminate)である場合にのみ発火する。 |
| RFC 3161 / 5816 タイムスタンプ | メッセージインプリントを TSA が表明する時刻に結びつけるタイムスタンプトークン(TST)。pverify は TST CMS、インプリント、および TSA 証明書チェーンを検証する(timestamp.rs)。RFC 5816 ESS 証明書バインディングが尊重される。TSA 証明書チェーンは各オブジェクトの VRT で失効検証される(スライス 033)。 |
| messageImprint | タイムスタンプまたは archive-time-stamp-v3 がコミットするハッシュ値。CAdES-LTA については、pverify は §6.3.4 の正規入力を再計算して比較する。不一致は archive_timestamp_imprint_mismatch(TOTAL_FAILED)である。 |
| EU トラステッドリスト(TSL / LOTL) | ETSI TS 119 612 TrustServiceStatusList、適格トラストサービスのエンベロープド-XAdES リスト。pverify-eutl(署名者ピンモデル、時刻 t におけるサービスステータスフィルタ)によって通常のトラストアンカーへ取り込まれる。決して再公開せず、「適格(qualified)」を判定しない(§I)。 |
| AATL | Adobe Approved Trust List — CMS 署名された PDF(それ自体が PAdES 署名)であり、XML の TrustedIdentities リストをラップする。pverify-aatl により、ピン留めされた Adobe Root CA G2 に対して取り込まれ、政府関連の Root && CertifiedDocuments アイデンティティへフィルタリングされる。 |
| foreign-government | data_source の値。台湾 GRCA(GRCA1/2/3)・韓国 GPKIRootCA1・シンガポール NCA B1 など、EU LOTL / FPKI / GPKI 以外の外国政府 PKI のルート証明書を手動台帳(web/roots/*.json)に直接ピン登録する際に使用するカテゴリ。SHA-256 は取得 DER バイトから算出し、公式配布元(および可能な場合は Microsoft CCADB)と照合して来歴を記録する。key_algorithm フィールドを必須とする(v1.4.1 以降)。 |
| 用語 | 定義 |
|---|---|
| CMS SignedData | RFC 5652 cryptographic message syntax — CAdES のコンテナであり、PAdES の埋め込みペイロードである。寛容な手書きのウォーカ(cms/signed_data.rs)によってパースされ、SignerInfo、signedAttrs、証明書および unsignedAttrs を生成する。マルチ署名者 CMS は拒否され(INDETERMINATE / cms_multi_signer_unsupported)、決して暗黙のうちに最初の署名者へ縮約されない(スライス 029)。 |
| ESS署名証明書バインディング | RFC 5035 signing-certificate(v1) / v2 の署名属性であり、署名者証明書をハッシュ(+任意の issuerSerial)によってピン留めする。certHash の不一致は TOTAL_FAILED(signing_certificate_digest_mismatch、証明書すり替えに対する防御)である。v1 の SHA-1 は受理してフラグを立てる(accept-and-flag)(cms/ess.rs、スライス 025)。 |
content-type 署名属性バインディング |
RFC 5652 §5.3、content-type 署名属性 OID と eContentType の等価性(スライス 026)。不一致は TOTAL_FAILED(content_type_mismatch)、signedAttrs が存在するのに属性が欠落している場合は content_type_missing、パース不能は content_type_not_evaluable(INDETERMINATE)である。 |
| Exclusive C14N | ダイジェスト計算前に XAdES のノードセットを正規化するために使用される Exclusive XML Canonicalization(W3C)。監査済みの bergshamra-c14n クレートによって提供される。非排他的 C14N は xades_unsupported_canonicalization へ降格する。Plain Canonical XML 1.0(C14nMode::Inclusive)は XAdES タイムスタンプのインプリントについてのみ使用される(スライス 019)。 |
| JWS Signing Input | JAdES については、BASE64URL(protected) '.' payload-segment という文字列(RFC 7515 §5.1)であり、RFC 7797 の b64:false 非エンコードペイロードバリアントは pverify-jades によって扱われる。JWS 署名はこの入力を対象に検証される。 |
| DSS (Document Security Store) | /Certs、/CRLs、/OCSPs のプールに文書全体の検証材料を保持する、PDF カタログレベルの /DSS 辞書(PAdES / EN 319 142-1。日本語訳: 文書セキュリティストア)。pverify はすべてのインクリメンタルリビジョンにわたってこれを和(union)として統合し、DER によって重複排除し、/Certs を中間/リーフのみとして扱い、決してトラストアンカーとしない(スライス 034、Q6/Q7)。 |
| VRI (Validation Related Information) | 署名の /Contents の大文字16進 SHA-1 を、その署名に関連する /Cert、/CRL、/OCSP の部分集合へ対応づける /DSS のサブ辞書(日本語訳: 検証関連情報)。pverify はオブジェクトごとにVRI 優先、DSS グローバルフォールバックで解決する(pades::resolve_material、スライス 034、Q1)。 |
| revocationInfoArchival | 署名内に CRL / OCSP / otherRevInfo を担持する Adobe 独自の CMS 属性(OID 1.2.840.113583.1.1.8)。pverify はその CRL / OCSP DER を射影し、signature_embedded 材料としてマージする(スライス 034)。otherRevInfo はスコープ外である。 |
| 用語 | 定義 |
|---|---|
| ETSI インディケーション | EN 319 102-1 によるトップレベルの判定: TOTAL_PASSED、INDETERMINATE、または TOTAL_FAILED。report/etsi.rs:24 のクローズドな列挙 Indication。 |
| サブインディケーション | あるインディケーションを限定する、精密な機械可読の理由(例: messageDigest_mismatch、signer_certificate_revoked、crypto_constraints_failure_no_poe)。単一のクローズドな Rust 列挙 SubIndication(report/etsi.rs:39)であり、report-schema.json へ 1:1 でミラーされる。自由形式の値は禁止される(§I)。カタログについては §18.3.1 を参照。 |
| 断定回避 | 憲章原則 §I: pverify がある事実を確立できない場合、サブインディケーションを伴う INDETERMINATE を返し、決して捏造された TOTAL_FAILED や TOTAL_PASSED を返さない。例えば、失効した TSA 証明書は偽造の主張ではなく revoked_no_poe(INDETERMINATE)を生じる。すなわち「断言できないことは断言しない」。 |
| RevocationOutcome | ステップごとの失効結果のクローズドな列挙(GoodOnCrl、RevokedOnCrl、GoodOnOcsp、RevokedOnOcsp、IndeterminateRevocationOffline など)であり、report/mod.rs:768 にある。署名者チェーン、TSA チェーン、および DSS 由来の検査によって逐語的に再利用される。§18.3.2 を参照。 |
| VRT (Validation Reference Time / 検証基準時刻) | あるオブジェクトの証明書有効性および失効鮮度を判定する、オブジェクトごとの時刻。crate::vrt における再帰的な外側カバーの規則(JNSA §4)によって導出される。次の外側のカバーするタイムスタンプの genTime が、内側のオブジェクトの基準時刻を繰り上げる。署名ごとに signatures[].vrt で表出される(スライス 031)。 |
| ValidationObjectOrigin | 統合された各検証対象オブジェクトの出所(プロビナンス)タグ: signature_embedded、bundle、fetched_online、trust_anchor、supplied_input、pdf_dss、pdf_vri(report/validation_objects.rs:97)。pdf_dss / pdf_vri はブランチ 034 で追加された。origin[] 配列について、宣言順がソート順である。 |
| Mode | 失効情報の取得を制御するクローズドな列挙 {Online, Offline, FromBundle}(report/mod.rs:54)。Offline はソケットを開かない(オフラインの誠実性)。FromBundle は <bundle>/crls、/ocsp、/trust-anchors を読む。 |
| オフラインの誠実性 | 憲章原則 §VII: --offline モードにおいて pverify はレスポンダ接触を捏造してはならない。IndeterminateRevocationOffline というアウトカム/ revocation_not_checked_offline というサブインディケーションが、以前の不誠実な ocsp_responder_unreachable の発行を置き換えた(スライス 012)。 |
| アルゴリズムポリシー(CRYPTREC / NIST) | 各オブジェクトの VRT 時点を基準として、ダイジェスト/署名ファミリ/鍵長をポリシーに対して評価する、オプトイン(--algorithm-policy)のオブジェクトごとの評価であり、ETSI CRYPTO_CONSTRAINTS_FAILURE_NO_POE を反映する。既定ではオフであり、決して TOTAL_FAILED を生成しない(algorithm_policy/、report/algorithm_validity.rs、スライス 032)。 |
| 用語 | 定義 |
|---|---|
no_std + alloc カーネル |
pverify-core は標準ライブラリなしでコンパイルされ、alloc クレートのみを使用し、unsafe コードおよびあらゆる直接の I/O を禁止する — これは、それが wasm32-unknown-unknown へコンパイル可能かつ決定的であり続けるための前提条件である(pverify-core/src/lib.rs は #![no_std] および #![forbid(unsafe_code)] を宣言する)。 |
| バイト単位の同一性(追加的機能) | 再現性の不変条件: 追加的機能は、その機能がオフの場合/入力が存在しない場合、schema_version 文字列を除いてレポート本体をバイト単位で同一に保たねばならない。例えば、DSS のない PDF のレポートは 034 以前と schema_version のみで異なる(FR-008)。 |
| CLIとWASMのパリティ | ネイティブ CLI とブラウザの WASM ホストは、同一の構造を抽出し、同一の verify_with カーネルを実行せねばならず、同一の入力に対してバイト単位で同一のレポートを生成する。抽出器に対するパリティテストによって強制される。 |
| schema_version | レポートの形の SemVer(report/schema.rs の SCHEMA_VERSION、現在 1.12.0)であり、report-schema.json にミラーされる。クローズドな列挙値またはオプションフィールドの追加は MINOR、新しい必須のトップレベルフィールドの追加は MINOR、値の削除/転用は MAJOR である。系譜については §18.4 を参照。 |
レポートは、文書が担持する署名の数にかかわらず、verify 呼び出しごとに1つの JSON オブジェクトである。Rust の真実の源は pub struct Report (crates/pverify-core/src/report/mod.rs:69)であり、serde はシリアライズ時に フィールドの宣言順を保持する。これがスキーマの required 順序であり、実行を またいで JSON をバイト単位で同一に保つものである。
ヘッダ(pverify_version … mode)は、report/schema.rs 内のコンパイル時の 憲章的定数から Report::new_header(report/mod.rs:95)によって埋められる。 検証パイプラインは signatures[] を追加し、最後に derive_validation_objects(report/validation_objects.rs:191)を介して validation_objects[] を導出する。
+---------------------------+----------------------+-----------+-------------------------------------------------------------+
| Field | JSON type | Required | Meaning / source |
+---------------------------+----------------------+-----------+-------------------------------------------------------------+
| pverify_version | string | yes | Binary version (CARGO_PKG_VERSION) — schema.rs PVERIFY_VER. |
| schema_version | string (SemVer) | yes | Report-shape version, "1.12.0" — schema.rs SCHEMA_VERSION. |
| trust_profiles | array<string> | yes | Active trust profiles, e.g. ["jpki"]. minItems: 1. |
| path_validation_phase | string | yes | RFC 5280 §6 slice actually run; "v0.6-bridge-acceptance". |
| bridge_ca_supported | bool | yes | Binary capability flag; true since v0.3. |
| dn_match_method | string | yes | DN-compare rule; "rfc4518-minimal-v0.1". |
| verification_time | string (RFC 3339) | yes | The clock the run judged against (--at or startup capture). |
| mode | enum {online, | yes | Revocation acquisition mode (report/mod.rs:54). |
| | offline, from_bundle}| | |
| inputs | array<InputHash> | yes | Every file/OID consumed, by SHA-256 + role. |
| fetch_log | array<FetchLogEntry> | yes | Outbound HTTP fetches (online mode); empty otherwise. |
| weak_algorithms | array<WeakAlgFlag> | yes | Union of per-step weak-algorithm observations (SHA-1). |
| signatures | array<SignatureEntry>| yes | One entry per signature in the document. |
| validation_objects | array<ValidationObj> | yes | Dedup-by-SHA-256 inventory of every consulted material. |
+---------------------------+----------------------+-----------+-------------------------------------------------------------+
5つの憲章的開示定数(schema_version、path_validation_phase、 bridge_ca_supported、dn_match_method、および暗黙の pverify_version)は 決して設定から読まれない — それらは、レポートを生成したバイナリに関する コンパイル時の事実である(report/schema.rs のヘッダコメント、§I 事実報告)。 verification_time は、--at が供給されない限り CLI 起動時に一度捕捉され、 --at は常に優先される(§II 再現性)。
inputs[] — InputHash(report/mod.rs:203)+-------------+----------------------------------------------+--------------------------------------------------+
| Field | Type | Meaning |
+-------------+----------------------------------------------+--------------------------------------------------+
| path | string | Filesystem path, OR (role=required_policy) the |
| | | OID dotted-decimal string itself. |
| role | enum: signature | detached_content | | What the file is to the verifier. |
| | trust_anchors_dir | bundle_root | | |
| | bundle_manifest | required_policy | |
| body_sha256 | string (64 hex) | SHA-256 of the file bytes (or OID UTF-8 bytes). |
+-------------+----------------------------------------------+--------------------------------------------------+
required_policy エントリは、発行前に辞書順でソートされるため、フラグの供給 順序はレポートに影響しない(バイト単位の同一性を保つ)。
signatures[] — SignatureEntry(report/mod.rs:348)これは中心的なレコードであり、署名ごとに1つである。オプションフィールドは skip_serializing_if を使用するため、それらを担持できない形式は以前のスキーマ バージョンとバイト単位で同一に保たれる。
+-------------------------------+----------------------------+-----------+-------------------------------------------------+
| Field | Type | Required | Meaning |
+-------------------------------+----------------------------+-----------+-------------------------------------------------+
| format | SignatureFormat enum | yes | Detected level (see §18.2.4). |
| pdf_byte_range | [u64;4] | null | yes/null | PAdES /ByteRange; null for non-PDF. |
| pdf_covers_eof | bool | null | yes/null | Whether this revision covers its %%EOF. |
| content_source | enum {embedded, detached} | yes | Which byte source the digest check ran against. |
| content_source_redundant | bool | optional | Some(true) iff eContent AND byte-equal detached.|
| chain_result | ChainResult | yes | RFC 5280 path-validation result (§18.2.5). |
| signed_attrs_check | SignedAttrsCheck | yes | signedAttrs digest + signature outcome. |
| content_digest_check | ContentDigestCheck | yes | messageDigest vs covered content + its SHA-256. |
| timestamps | array<TimestampToken> | yes | Signature/content/archive timestamps (§18.2.6). |
| embedded_validation_data | EmbeddedValidationData|null| yes/null | LT/LTA embedded certs/CRLs/archive-TS. |
| container | ContainerFormat enum | optional | ASiC-S / ASiC-E if extracted from a container. |
| asic_manifest_checks | array<AsicManifestCheck> | optional | ASiC-E CAdES manifest digest-chain step 2. |
| asic_conformance_deviations | array<AsicDeviation> | optional | EN 319 162 packaging deviations (best-effort). |
| signer_identifier_check | SignerIdentifierCheck | optional | CMS SignerInfo.sid form + match (slice 025). |
| signing_certificate_binding | SigningCertificateBinding | optional | ESS signing-certificate(v2) binding (025). |
| content_type_check | ContentTypeCheck | optional | RFC 5652 §5.3 content-type equality (slice 026).|
| etsi_indication | EtsiIndication | yes | The verdict for this signature (§18.2.7). |
| vrt | Vrt | yes | Per-object validation reference times (031). |
| algorithm_validity | AlgorithmValidityCheck | optional | Opt-in algorithm policy verdict (slice 032). |
+-------------------------------+----------------------------+-----------+-------------------------------------------------+
vrt はスライス 031 以降必須であるが、それを省略する保存済みの 1.2.0 レポートは Vrt::request_at_fallback() の既定値を介してデシリアライズされ、 Report::from_json(report/mod.rs:127)は、合成された UNIX_EPOCH セン チネルに対して verification_time を代入する — これは、以前に保存された レポートのための意図的な前方互換性の経路である。
SignatureFormat 列挙(report/mod.rs:264)format は検出された AdES レベルであり、スコープについて明示的である。ワイヤ値と それぞれが サポート対 検出のみ・拒否 の観点で何を意味するか:
+--------------------+-----------------------------------------------------------------------------+
| Wire value | Status |
+--------------------+-----------------------------------------------------------------------------+
| CAdES-BES | Supported (B-B). |
| CAdES-T | Supported (B-T, signature-time-stamp). |
| CAdES-LT | Supported (B-LT, embedded validation material). |
| CAdES-LTA | Supported (B-LTA, archive-time-stamp-v3 §6.3.4 imprint recomputation). |
| PAdES-B | Supported (B-B over embedded CMS). |
| PAdES-B-T | Supported (B-T, signature-time-stamp / DocTimeStamp). |
| PAdES-B-LT | Supported (B-LT, /DSS + /VRI + revocationInfoArchival as of slice 034). |
| PAdES-B-LTA | Supported (B-LTA, /DocTimeStamp archive). |
| XAdES-B-B | Supported (enveloped B-B, Exclusive C14N). |
| XAdES-B-T | Supported (SignatureTimeStamp / SigAndRefsTimeStamp). |
| XAdES-B-LT | Supported (embedded CertificateValues / RevocationValues). |
| XAdES-B-LTA | Supported (ArchiveTimeStamp imprint per ETSI TS 101 903 v1.4.2 Annex A.1.5, branch 036). |
| XAdES-unsupported | Detection-only refusal: detached/enveloping, non-exclusive C14N, etc. |
| JAdES-B-B | Supported (JWS JSON Serialization B-B). |
| JAdES-unsupported | Detection-only refusal: sigD detached, Compact serialization, B-T+. |
+--------------------+-----------------------------------------------------------------------------+
結合された「ASiC×format」バリアントは存在しない: ASiC は container フィールド (ContainerFormat::{AsicS, AsicE})に直交的に記録され、一方 format は内側の 署名のネストされた CAdES / XAdES の値を担持する。
chain_result — ChainResult(report/mod.rs:586)ChainResult は RFC 5280 §6 のパス検証の結果を記録する: 順序付けられた steps[](各々が subject_fingerprint、cert_signature_alg、BasicConstraints、 KeyUsage、有効期間の事実、ステップごとの revocation レコード、および ChainStepSignature の証明書署名検証結果を持つ ChainStep)、 bridge_attempts[] の相互認証証明書のトラバーサル、 terminating_anchor_fingerprint、および名前制約/ポリシー処理の検査である。 ステップごとの revocation フィールド(RevocationRecord、report/mod.rs:826) は、スライス 034 における DSS/VRI 材料、およびスライス 033 における TSA チェーン 失効の統合点である。
timestamps[] — TimestampToken(report/mod.rs:998)+----------------------+--------------------------------------+--------------------------------------------------+
| Field | Type | Meaning |
+----------------------+--------------------------------------+--------------------------------------------------+
| role | enum: signature_timestamp | | Which timestamp role (RFC 3161 TST). |
| | content_timestamp | archive_timestamp| |
| tsa_subject_dn_text | string | TSA cert subject DN. |
| tsa_chain_outcome | ChainResult | TSA cert chain validation (+ revocation, 033). |
| gen_time | string (RFC 3339) | TSA-asserted genTime. |
| hash_algorithm_oid | string | messageImprint hash algorithm OID. |
| message_imprint_match| bool | Whether the imprint recomputation matched. |
| token_sha256 | string (64 hex) | SHA-256 of the TST bytes (omit if absent). |
| imprint_record | ArchiveTimestampImprintRecord | null | §6.3.4 recompute record (archive role only). |
+----------------------+--------------------------------------+--------------------------------------------------+
etsi_indication — EtsiIndication(report/etsi.rs:435)+----------------+-------------------------------------------+--------------------------------------------------+
| Field | Type | Meaning |
+----------------+-------------------------------------------+--------------------------------------------------+
| indication | enum: TOTAL_PASSED | INDETERMINATE | | The verdict (Indication, §18.3 / EN 319 102-1). |
| | TOTAL_FAILED | |
| sub_indication | SubIndication | null | The precise reason (closed enum, §18.3.1). |
| failing_locus | string | null | Free-text identifier of the failing object/step. |
+----------------+-------------------------------------------+--------------------------------------------------+
アグリゲータ aggregate_etsi(verify.rs)は、重大度順のベース (TOTAL_FAILED > INDETERMINATE > TOTAL_PASSED、最初に見つかったものが優先)を 確立し、後続のレイヤ(署名者バインディング、content-type、algorithm-validity、 TSA 失効)は TOTAL_FAILED でないベースを降格させるのみである — それらは 証明された TOTAL_FAILED を決してマスクしない。
validation_objects[] — ValidationObject(report/validation_objects.rs:53)実行が実際に参照したすべての生材料の、重複排除され決定的にソートされた一覧。 これは、組み立てられたレポートツリーに対する純粋な導出パスである(分岐する ハッシュ計算なし、副入力なし)。
+---------+-----------------------------------------------+--------------------------------------------------+
| Field | Type | Meaning |
+---------+-----------------------------------------------+--------------------------------------------------+
| sha256 | string (64 hex) | Material digest; primary dedup key. |
| kind | enum: certificate | crl | ocsp_response | | Artefact class. |
| | timestamp_token | signed_content | |
| origin | array<ValidationObjectOrigin> | Where the bytes came from; sorted, deduped. |
| usage | array<ValidationObjectUsage> | Cross-references (cert role / rev target / TS). |
| parsed | bool (default true, omit when true) | false iff consulted but failed to parse. |
+---------+-----------------------------------------------+--------------------------------------------------+
重複排除は sha256 による(HashMap ではなく BTreeMap を介する)。出所 (origin)と用途(usage)はマージされ、parsed は寄与者にわたる論理 AND であり、 配列は (kind, sha256) でソートされる。同一の入力に対する2回の実行は、バイト 単位で同一の配列を生成する(§II / SC-003)。
レポートの機械可読な値空間は、report-schema.json へ 1:1 でミラーされる クローズドな Rust 列挙である。値の追加は追加的な MINOR の変更であり、値の削除 または転用は MAJOR である(§18.4)。非推奨のバリアントは、保存済みレポートの デシリアライズのために保持されるが、発行されない。
SubIndication カタログ(report/etsi.rs:39)ペアとなる判定によってグループ化されている。TF = TOTAL_FAILED、IND = INDETERMINATE。ワイヤ文字列は serde(rename) の値である。
チェーン/構造(注記なき限り TF):
chain_signature_failed TF cert-chain signature verify failed
signer_certificate_revoked TF signer revoked on CRL
signer_certificate_revoked_via_ocsp TF signer revoked on OCSP
signer_certificate_expired_at_time TF signer cert expired at VRT
signer_certificate_not_yet_valid_at_time TF signer cert not yet valid at VRT
anchor_expired_at_time TF trust anchor expired at VRT
key_usage_missing_required_bit TF KeyUsage lacks a required bit
chain_constraints_failure TF (deprecated; re-routed to indeterminate_named_constraint)
署名属性/コンテンツ/署名者バインディング:
messageDigest_mismatch TF messageDigest != content digest
signed_attrs_signature_failed TF signedAttrs signature verify failed
signer_certificate_not_found TF SignerInfo.sid matched no embedded cert (025)
signer_identifier_malformed IND sid malformed/absent — cannot identify signer (025)
signing_certificate_digest_mismatch TF ESS certHash mismatch (025)
signing_certificate_issuer_serial_mismatch TF ESS issuerSerial mismatch (025)
signing_certificate_digest_not_evaluable IND ESS certHash alg uncomputable (025)
content_type_mismatch TF content-type OID != eContentType (026)
content_type_missing TF content-type attr absent w/ signedAttrs (026)
content_type_not_evaluable IND content-type unparseable (026)
パス制約/ポリシー(IND):
indeterminate_named_constraint IND Name-Constraints state machine rejected the chain
name_constraints_unsupported_form IND unsupported GeneralName form
name_constraints_malformed IND malformed nameConstraints extension
indeterminate_policy_rejected IND policy processing rejected (requireExplicitPolicy/intersection)
policy_processing_inconclusive IND (deprecated; re-routed)
policy_mappings_malformed IND policyMappings maps anyPolicy etc.
bridge_ca_required_unsupported IND (deprecated v0.3; retained for stored reports)
アルゴリズム/鍵:
signature_algorithm_unsupported IND unrecognised sig OID or unsupported curve
public_key_malformed IND recognised alg but undecodable SPKI (010)
crypto_constraints_failure_no_poe IND opt-in algorithm-policy failure at VRT (032)
not_yet_supported_in_v0.1 IND legacy placeholder
タイムスタンプ/アーカイブ:
archive_timestamp_imprint_mismatch TF archive-TS-v3 imprint recompute mismatch (§6.3.4)
archive_timestamp_imprint_unsupported_algorithm IND archive-TS imprint alg outside catalogue
xades_timestamp_imprint_mismatch IND XAdES SignatureTimeStamp imprint mismatch (019)
revoked_no_poe IND TSA cert chain revoked at its VRT (033)
失効チャネル(OCSP/CRL):
ocsp_responder_signature_invalid TF OCSP response signature invalid
ocsp_response_stale IND nextUpdate past / absent / producedAt future
ocsp_responder_chain_unauthorised IND RFC 6960 §4.2.2.2 designated-signer failed
ocsp_unknown_status IND CertStatus unknown / nonce mismatch / malformed
ocsp_responder_unreachable IND online transient or from_bundle bundle-miss
revocation_not_checked_offline IND --offline, no socket opened (012)
crl_stale_no_fresher IND CRL stale, no fresher available
unsupported_indirect_crl IND indirect CRL not supported
unsupported_cdp_protocol IND CDP scheme not supported
ocsp_only_revocation_pointers IND (deprecated v0.4; retained)
PAdES / XAdES / JAdES / ASiC の構造:
pdf_byte_range_does_not_cover_eof IND trailing bytes uncovered by ByteRange
pdf_byte_range_malformed IND malformed /ByteRange
xades_unsupported IND un-categorised XAdES catch-all
xades_reference_digest_mismatch TF ds:Reference digest mismatch (tamper)
xades_signing_certificate_mismatch TF SigningCertificateV2 CertDigest mismatch
xades_unsupported_profile IND detached/enveloping rather than enveloped
xades_unsupported_canonicalization IND non-exclusive C14N
xades_signer_certificate_unavailable IND no embedded signer cert
jades_payload_digest_mismatch TF JWS signature != signing input
jades_signing_certificate_mismatch TF JAdES x5t#S256 / x5t#o binding mismatch
jades_unsupported_profile IND JAdES B-T+ / plain JWS without JAdES props
jades_unsupported_serialization IND sigD detached / Compact serialization
jades_signer_certificate_unavailable IND no x5c / x5t#S256 lookup result
asic_data_object_digest_mismatch TF ASiC-E manifest DigestValue mismatch
asic_data_object_missing IND referenced data object absent from archive
asic_unsupported_container IND ZIP not a recognisable ASiC container
cms_multi_signer_unsupported IND >1 SignerInfo — refused, not first-only (029)
no_signatures_found IND OFD: 文書に Signatures.xml がない (044)
signer_certificate_unverified IND OFD: 証明書は取得できたが発行PKIがトラストプロファイル外 (044)
RevocationOutcome カタログ(report/mod.rs:768)+----------------------------------+----------+-----------------------------------------------------+
| Variant (Rust) | Verdict | Meaning |
+----------------------------------+----------+-----------------------------------------------------+
| GoodOnCrl | passed | CRL: certificate good. |
| RevokedOnCrl | failed | CRL: certificate revoked. |
| GoodOnOcsp | passed | OCSP: good, all gates passed. |
| RevokedOnOcsp | failed | OCSP: CertStatus revoked. |
| IndeterminateNoCrl | indet. | No usable CRL: none fetchable, OR a fetched CRL |
| | | failed to parse / failed signature verification. |
| IndeterminateStaleCrl | indet. | A CRL was fetched but is outside its freshness |
| | | window (thisUpdate in the future, or nextUpdate |
| | | already past). |
| IndeterminateIndirectCrl | indet. | Indirect CRL unsupported. |
| IndeterminateUnsupportedProtocol | indet. | CDP protocol unsupported. |
| IndeterminateOcspUnknown | indet. | OCSP unknown / nonce mismatch. |
| IndeterminateOcspResponderUnauthorised | indet. | RFC 6960 §4.2.2.2 designated-signer failed. |
| IndeterminateOcspStale | indet. | nextUpdate past / absent / producedAt future. |
| IndeterminateOcspMalformed | indet. | Non-decodable / non-successful response status. |
| IndeterminateOcspUnreachable | indet. | All AIA fetches failed (online) / bundle-miss. |
| IndeterminateRevocationOffline | indet. | --offline, no socket opened (012, offline honesty). |
| IndeterminateOcspOnly | indet. | Legacy v0.1; superseded by OCSP-channel outcomes. |
+----------------------------------+----------+-----------------------------------------------------+
DSS 由来または VRI 由来の CRL/OCSP は、まさにこれらのアウトカムを生じる — スライス 034 は新しい失効アウトカムも新しいサブインディケーションも追加 しなかった。出所フィールド CrlSummary.pdf_source / OcspAttempt.pdf_source (PdfRevocationSource::{PdfDss, PdfVri}、report/mod.rs:870)のみが追加され、 一覧が材料の出所をタグ付けできるようにした。
ValidationObjectOrigin(report/validation_objects.rs:97)宣言順が origin[] 配列のソート順である。2つの PDF の値は、既存の配列が バイト単位で安定し続けるよう、まさにブランチ 034 で最後に追加された。
signature_embedded in-signature material (CMS unsignedAttrs, embedded certs/CRLs)
bundle --from-bundle directory
fetched_online live HTTP / CDP / AIA fetch
trust_anchor the terminating anchor
supplied_input detached content / supplied OID
pdf_dss PDF /DSS pool (DSS-global fallback) — slice 034
pdf_vri PDF /VRI-keyed subset (VRI-preferred) — slice 034
PDF ソースタグは、derive_validation_objects(CRL については validation_objects.rs:307、OCSP については :334)における モード/OcspSource ベースのヒューリスティックに優先する: DSS 由来または VRI 由来のレスポンスは埋め込みチャネルに乗るため、その OcspSource はそうでなければ embedded_ocsp_vals と読まれてしまう — 明示的な pdf_source タグが出所を補正 する。
pverify は Spec Kit のもとでスライスごとに構築される。各スライスは specs/<NNN>-<name>/ のもとにディレクトリを持ち、その plan.md、research.md、 data-model.md、contracts/、quickstart.md および tasks.md を担持する。 以下の表は、着地した各スライスを、その機能および schema_version への影響に 対応づける。情報源は docs/speckit-history.md、specs/ 下のディレクトリ一覧、 および crates/pverify-core/src/report/schema.rs 内の年代順に注釈された SCHEMA_VERSION のドキュメントコメント(すべてのバンプを記録する)である。
schema_version の SemVer とリリースされたバイナリのタグ(v0.x)は独立した 軸であることに注意せよ: 多くのスライスは schema_version をバンプせずに クローズドな列挙値またはオプションフィールドを追加する(その規律は、新しい 必須のトップレベルフィールド、または意図的な結合された交代のみがそれを バンプするというものである)。リリースされたタグの系譜(v0.1 … v0.9、 そして v1.1.0)は末尾に要約されている。
+-----------+------------------------------+-------------------------------------------+-------------------+
| Spec dir | Feature | Schema effect | schema_version |
+-----------+------------------------------+-------------------------------------------+-------------------+
| 001 | verify-cades-pades | Baseline report shape. | 1.0.0 |
| 002 | ecdsa-attached-cades | ECDSA + attached CAdES; +2 sub-ind. | 1.0.0 (held) |
| 003 | v03-rfc5280-path | RFC 5280 §6 complete + Bridge-CA + arch- | 1.0.0 (held); |
| | | TS-v3 imprint; +7 sub-ind, bridge flag. | phase rotated |
| 004 | v04-ocsp-support | RFC 6960 OCSP; +6 sub-ind, +7 rev-outcome.| 1.0.0 (held); |
| | | | phase v0.4-ocsp |
| 005 | v04-1-reinforcement | Reinforcement slice. | held |
| 006 | v041x-ocsp-harness | Synthetic-responder test harness. | held |
| 007 | v05-carry-over | bridge_attempt -> bridge_attempts rename. | 1.0.0 -> 1.1.0 |
| 008 | v06-bridge-acceptance | Multi-bridge + Name-Constraints + policy; | 1.1.0 (held); |
| | | +2 sub-ind, phase v0.6-bridge-acceptance. | phase rotated |
| 009 | v07-pades-incremental | PAdES incremental-update + DocTimeStamp. | 1.1.0 (held) |
| 010 | ml-dsa-verify | ML-DSA (FIPS 204) verify; +1 sub-ind. | 1.1.0 (held) |
| 011 | xades-bb-core | XAdES B-B (enveloped, exc-c14n) + RSA-PSS;| 1.1.0 (held) |
| | | +1 format, +5 sub-ind. | |
| 012 | ldap-crl | LDAP-CRL fetch (CLI + core BER client). | held |
| 012 | offline-revocation-honesty | Offline honesty fix; +1 sub-ind, +1 rev. | 1.1.0 (held) |
| 013 | slsa-sbom-cbom | SLSA L3 + SBOM + CBOM (CI/build). | held (no report) |
| 014 | eutl-ingestion | pverify-eutl (EU TSL -> anchors). | held (own types) |
| 015 | dss-interop-fixtures | DSS interop regression vectors (+~25 LOC | 1.1.0 (held) |
| | | offline-honesty fix for PAdES/XAdES). | |
| 016 | diagnostic-data | validation_objects[] (new REQUIRED field);| 1.1.0 -> 1.2.0 |
| | | +3 digest fields, +4 closed enums. | |
| 019 | xades-bt-blt | XAdES B-T / B-LT; +2 format, +1 sub-ind. | 1.2.0 (held) |
| 020 | asic-container-verify | ASiC-S/-E (pverify-asic); +2 format-side | 1.2.0 (held) |
| | | enums, +3 sub-ind, +container field. | |
| 021 | cli-net-guard | SSRF net-guard (CLI-only). | 1.2.0 (held) |
| 024 | bom-attestation | Typed CycloneDX attestation (CI/docs). | held (no report) |
| 025 | cades-signer-binding | SignerInfo.sid + ESS binding; +5 sub-ind, | 1.2.0 (held) |
| | | +1 weak-alg role, +2 optional fields. | |
| 026 | aatl-ingestion | pverify-aatl (AATL -> anchors). | held (own types) |
| 026 | cades-contenttype-binding | content-type signed-attr; +3 sub-ind, | 1.2.0 (held) |
| | | +1 optional field. | |
| 027 | jades-bb-verify | JAdES B-B (pverify-jades); +2 format, | 1.2.0 (held) |
| | | +5 sub-ind. | |
| 028 | trust-anchor-viz | Trust-anchor visualisation (web/tooling). | held |
| 031 | vrt-per-object | signatures[].vrt (new REQUIRED field); | 1.2.0 -> 1.3.0 |
| | | +3 closed enums under $defs.Vrt. | |
| 032 | algorithm-validity | Opt-in algorithm policy; +1 optional | 1.3.0 -> 1.4.0 |
| | | field, +3 enums, +1 sub-ind (constitution | |
| | | 1.1.0 -> 1.2.0). | |
| 033 | tsa-revocation-consistency | TSA-cert revocation across all formats; | 1.4.0 -> 1.5.0 |
| | | +1 sub-ind (revoked_no_poe). Content-only.| |
| 034 | pades-dss-vri | PAdES /DSS + /VRI + revocationInfoArchival;| 1.5.0 -> 1.7.0 |
| | | +2 origin values, +2 pdf_source fields. | |
| 035 | multi-findings | EtsiIndication.findings[] 並列報告; | 1.7.0 |
| | | 省略時バイト同一性。 | |
| 035-retry | ocsp-retry | OcspAttempt.request_der_hex; /ocsp プロキシ| 1.7.0 -> 1.8.0 |
| | | リトライ; RFC 6960 §4.2.2.1 nextUpdate | |
| | | 欠落修正(MOJ CRPKI); 外側 OCSPResponse | |
| | | アンラップ修正。 | |
| 035 P1+P2 | ocsp-client | RFC 8954 CSPRNG nonce (NonceSource trait);| held (no schema) |
| | | Workers online mode (WorkersOnlineFetcher | |
| | | + WasmNonceSource); nonce mismatch -> | |
| | | revocation_not_checked_online. | |
| patch | akid-anchor-selection | TrustAnchor.subject_key_identifier; | held (no schema) |
| | | 同一 DN を持つアンカーに AKID タイブレーク| |
| | | (GPKI OSCA 2019/2024 修正)。 | |
| cdp-entry | 構造化 CDP | cdp_uris:Vec<String> → cdp_entries: | 1.8.0 -> 1.9.0 |
| | | Vec<CdpEntry>(kind/value/fetchable); | |
| | | DirName CDP が表面化(GPKI OSCA); | |
| | | RevocationRecord に aia_ocsp_uris 追加。 | |
| ltv-jws | 実験的 LTV-JWS | SIG-B〜SIG-LTA(draft-miyachi-00); | held (no schema) |
| | | SignatureFormat 4 変種追加。 | |
| gpki-hint | GPKI DirName→LDAP URL | dirname_to_ldap_url ヒントテーブル。 | held (no schema) |
| 036 | xades-blta | XAdES B-LTA: ArchiveTimeStamp インプリント| held (no schema) |
| | | (ETSI TS 101 903 v1.4.2 Annex A.1.5); | |
| | | +1 format(XAdES-B-LTA); CAdES と | |
| | | ArchiveTimestampImprintRecord を共有; | |
| | | bergshamra-c14n NodeSet subtract API。 | |
| lgpki2 | LGPKI2 DirName→LDAP URL | dirname_to_ldap_url サフィックスパターン。 | held (no schema) |
| | | CN=CRL{N},OU=Organization CA R2, | |
| | | O=LGPKI2,C=JP → www.lgpki.go.jp:389; | |
| | | LGPKI2 PAdES-LT がオンラインで | |
| | | TOTAL_PASSED に。 | |
| 040 | trust-profile-selection | --trust-profile フラグ; 6 プロファイル | 1.10.0 -> 1.11.0 |
| | | (jpki/aatl/eutl/fpki/fgov/local); 新 | |
| | | 必須 trust_profiles[] レポートフィールド; | |
| | | UI TZ ドロップダウン削除; 6 チェックボックス。| |
| 044 | ofd-verify | OFD (GB/T 33190-2016) 署名検証; | 1.11.0 -> 1.12.0 |
| | | SignatureFormat::Ofd; OfdSignatureEntry; | |
| | | SM2/SM3 暗号; GB/T 38540 電子印章解析; | |
| | | SubIndication::NoSignaturesFound 追加。 | |
+-----------+------------------------------+-------------------------------------------+-------------------+
リリースされたバイナリのタグ系譜(git タグ、docs/speckit-history.md の §「Released-baseline lineage」より): v0.1(001)· v0.2(002)· v0.3 (003)· v0.4(004)· v0.4.1(005)· v0.4.1.1(006)· v0.5(007)· v0.6(008)· v0.7(009)· v0.8(019、XAdES B-T/B-LT、フル SLSA L3)· v0.9(021、CLI ネットガード)· v1.1.0(034、PAdES /DSS+/VRI)· v1.1.9 (patch: AKID ベースアンカー選択、GPKI OSCA 2019/2024 修正)· v1.2.0(CdpEntry 構造化 CDP+GPKI DirName hint+LTV-JWS 実験的実装)· v1.3.0(036-xades-blta: XAdES B-LTA ArchiveTimeStamp インプリント+LGPKI2 PAdES off-by-1 修正+OCSP nonce 強化、schema 1.9.0)· v1.3.1(lgpki2-ldap: LGPKI2 DirectoryName CDP → LDAP URL 合成、www.lgpki.go.jp:389 到達確認)· v1.3.2(037-jades-bt-blta: ETSI JAdES B-T/B-LT/B-LTA、sigTst/xVals/rVals/arcTst unprotected header 解析、schema 1.10.0)· v1.4.0(038-crl-dashboard: GPKI/LGPKI CRL ダッシュボード、 pverify crl-inspect サブコマンド、週次 CI 自動更新、全クレート version 1.4.0 に統一)· v1.4.1(外国政府 PKI 信頼アンカ追加: 台湾 GRCA1/2/3・韓国 GPKIRootCA1・シンガポール NCA B1、data_source: foreign-government、issue #95 close; key_algorithm 全台帳 JSON に付与しルート一覧 UI に表示; scripts/hooks/pre-push fmt+clippy フック; GitHub Actions Node 24 バンプ; header_emits_constitutional_invariants テスト修正)· v1.5.0(040-trust-profile-selection: --trust-profile フラグ+ 6 プロファイル jpki/aatl/eutl/fpki/fgov/local、trust_profiles[] 必須 レポートフィールド、UI TZ ドロップダウン削除、schema 1.11.0) · v1.6.0(044-ofd-verify: OFD GB/T 33190-2016 電子文書署名検証; SignatureFormat::Ofd; SM2/SM3 crypto via pverify-ofd; GB/T 38540 電子印章 DER 解析; SubIndication::NoSignaturesFound + SignerCertificateUnverified 追加; OfdSignerCert ブラウザ抽出 (pverify-core::ofd); AATL 政府系 CA を roots.json に追加し fgov プロファイルと一致; schema 1.11.0→1.12.0) · v1.7.0(新規信頼アンカ追加: HK Post Root CA 3(香港郵政、RSA 4096)・CCA India 2022 (インド CCA、RSA 4096)を foreign-government ソースで追加; 暗号統計セクション新設: trust-anchors.json からアルゴリズム別件数・有効期限年別・ 発行年別チャートを描画(プロファイルフィルタ対応・fingerprint 重複排除); 統計ページを /stats.html に改称(旧 URL 301 リダイレクト); key_algorithm 全 947 件化: EUTL・AATL の取り込み時に DER から導出、EC カーブ OID デコードバグ修正(p.value()→p.to_der())・RSA-PSS OID 追加; 収録ルート一覧 UI に鍵アルゴリズム表示・EUTL タブ折りたたみ廃止; AATL プロファイルチェックボックス追加(商用ルートの遅延ロード); CI: web のみ変更時 Rust ジョブスキップ; schema 1.12.0 据え置き) · v1.7.1(統計ページ新チャート・収録ルート一覧 UI 改善・信頼アンカ追加・CI 修正: 新規信頼アンカ my FinTech my Digital Certificate-Sign G1(aatl)追加; 統計ページにセキュリティ強度散布図・有効期間ヒートマップを追加; 収録ルート一覧で その他 タブを 外国政府 に改称・AATL タブ独立; web-lint CI ジョブ追加; en.json ダブルクォートエスケープバグ修正; schema 1.12.0 据え置き) · v1.7.2(JAdES sigTst/arcTst ハッシュ入力修正 + ES512/P-521 + DSS 相互運用フィクスチャ: sigTst インプリントを ETSI TS 119 182-1 §5.4.1 に従い BASE64URL エンコード済み signature メンバの ASCII バイト列に修正; arcTst ハッシュ入力を §5.3.6.3.1 に従い payload.protected.signature.etsiU[0]etsiU[1]…(etsiU 間ドット区切りなし)に修正、 DSS リファレンス実装(d9473b8e)と一致; ECDSA P-521 / ES512 追加; DSS 相互運用フィクスチャ jades-bt-clear-etsiu・jades-blta を追加; schema 1.12.0 据え置き) · v1.7.3(RustCrypto 次世代スタック一括移行: der 0.7→0.8、spki 0.7→0.8、 pkcs8 0.10→0.11、sha1/sha2 0.10→0.11、signature 2→3、elliptic-curve 0.13→0.14; x509-cert 0.2→0.3-rc.4、p256/p384/p521 0.13→0.14-rc.15、ecdsa 0.16→0.17-rc.23、 rsa 0.9→0.10-rc.18(全 RC exact-pin); schema 1.12.0 据え置き) · v1.7.4(署名検証トレースモード: CLI --trace フラグで ANSI グリーンのステップバイステップ トレースを stderr に出力(stdout JSON は無変更); ブラウザ: 検証結果下に常設折りたたみ トレースパネル(黒地緑文字、デフォルト折りたたみ); 新規 Cargo 依存なし; schema 1.12.0 据え置き) · v1.7.5(SLH-DSA FIPS 205 + FN-DSA FIPS 206 署名検証: 全 12 SLH-DSA パラメータセット (SHA2 + SHAKE、OID .3.4.3.20–.31)を slh-dsa 0.2.0-rc.5 で実装; FN-DSA-512・ FN-DSA-1024(OID .3.4.3.46–.47)を fn-dsa-vrfy/comm 0.3.0 で実装; key_algorithm_from_cert_der に PQC OID 17 アームを追加(従来 "—" 表示だったものを修正); CBOM 33 アルゴリズム; schema 1.12.0 据え置き)。 タグのシーケンスは v0.9.0 から v1.1.0 へ直接跳ぶ: その間の v1.0.0(JAdES B-B / 027)および v0.10.0 のマイルストーンはウェブ専用 リリースであり、git タグとして切られたことがないため、git tag には現れない。 v1.6.0 から v1.7.5 において schema_version は 1.12.0 である。
監査者の注意に値するいくつかの系譜上の機微:
schema_version のバンプは稀であり、常に構造的な理由に結合している。 これをバンプしたのは4つのスライスのみである: 007(フィールド名の変更)、 016(新たに必須となった validation_objects[])、031(新たに必須となった signatures[].vrt)、および単調な 032/033/034 の追加的シーケンスである。 他のすべてのスライスはクローズドな列挙値またはオプションフィールドを追加し、 バージョンを保持した — report/schema.rs にインラインで記録された規律である。path_validation_phase は別個の開示軸であり、RFC 5280 §6 の 能力 (capability)の変化に対してのみ交代する(v0.1-basic → … → v0.6-bridge-acceptance、その現在の凍結された値)。スライス 031–034 は §6 の 入力の精緻化(どの時刻で検証するか、どの失効材料を供給するか)であって フェーズの変化ではないため、フェーズ文字列は v0.6 以降交代していない。012 と 026) — 012-ldap-crl / 012-offline-revocation-honesty および 026-aatl-ingestion / 026-cades-contenttype-binding — これは1つの傘のもとでの並行作業を反映する。 両方のディレクトリが specs/ 下に存在する。bridge_ca_required_unsupported、 chain_constraints_failure、policy_processing_inconclusive、 ocsp_only_revocation_pointers、not_yet_supported_in_v0.1、 IndeterminateOcspOnly)は、古いバイナリからの保存済みレポートが依然として デシリアライズできるよう、ワイヤ列挙に残されている。現在のバイナリはそれらを 発行しない。これは「値の削除は MAJOR である」という規則の実務的な顔である。このマップは、どのクレートおよびモジュールがどの関心事を所有するかを命名する ため、レビュアーがレポートフィールドまたは挙動から、それを生成するコードへと 辿ることができる。依存方向は厳密に host → clean → core である(第3.2章を 参照)。pverify-core はワークスペース内の何にも依存しない。
pverify-core — WASMクリーンな検証カーネル+-------------------------------------+------------------------------------------------------------+
| File | Concern |
+-------------------------------------+------------------------------------------------------------+
| lib.rs | Crate root; #![no_std], #![forbid(unsafe_code)]. |
| verify.rs | Orchestrator: VerificationRequest, verify_with, format |
| | sniff (detect_format), per-format dispatch, verify_cades, |
| | aggregate_etsi, per-object VRT re-validation, TSA-chain |
| | revocation wiring (apply_tsa_chain_revocation_for_token). |
| traits.rs | Capability boundary: Clock, RevocationFetcher, |
| | TrustAnchorStore, TrustAnchor. |
| crypto.rs | RustCrypto verify dispatch (verify_with_alg); SUPPORTED_* |
| | OID consts (CBOM source of truth); RSA-PSS. |
| algorithms.rs | Algorithm OID <-> family/digest classification. |
| error.rs | Core error type. |
| ber/mod.rs | Lenient BER/DER walker primitives. |
| cms/signed_data.rs | CMS SignedData parse (SignerInfo, signedAttrs, certs). |
| cms/signed_attrs.rs | signedAttrs reconstitution + digest. |
| cms/unsigned_attrs.rs | unsignedAttrs (T/LT/LTA material). |
| cms/ess.rs | RFC 5035 ESS signing-certificate(v1/v2) binding (025). |
| cms/canonical.rs / cms/verify.rs | CMS canonicalisation + signature verification. |
| crypto.rs / path/* | (see below) chain + crypto. |
| path/mod.rs | RFC 5280 §6 DFS chain build + backtrack + cycle detect. |
| path/bridge.rs | Bridge-CA cross-certificate traversal. |
| path/name_constraints.rs | §4.2.1.10 permitted/excluded subtree state machine. |
| path/policy.rs | §6.1 valid_policy_tree / mappings / inhibit_*. |
| x509/mod.rs, name.rs, extensions.rs | Certificate parse, DN match (rfc4518-minimal), extensions. |
| revocation/mod.rs | Revocation orchestration, embedded fall-through, PDF source|
| | tagging (PdfRevocationSource), apply_revocations. |
| revocation/crl.rs | RFC 5280 CRL parse + staleness. |
| revocation/indirect.rs | Indirect-CRL detection. |
| revocation/ocsp.rs | RFC 6960 OCSP request/response + §4.2.2.2 authorisation. |
| timestamp.rs | RFC 3161/5816 TST verify + imprint + TSA chain. |
| vrt/mod.rs, coverage.rs, promotion.rs| Per-object VRT recursive-outer-covering engine (031). |
| pades/mod.rs | PAdES verify_pdf; PdfValidationData / VriEntry boundary |
| | types; DSS/VRI resolve_material; revocationInfoArchival. |
| xades.rs | XAdES orchestration (verify_xades). |
| jades.rs | JAdES orchestration (verify_jades) + boundary types. |
| asic.rs | ASiC orchestration (verify_asic_signature) + boundary. |
| algorithm_policy/{mod,policy,extract}.rs | Opt-in algorithm policy engine (032). |
| report/mod.rs | Report + SignatureEntry + all support structs/enums. |
| report/etsi.rs | Indication / SubIndication closed enums + EtsiIndication. |
| report/schema.rs | SCHEMA_VERSION + constitutional disclosure consts. |
| report/validation_objects.rs | validation_objects[] derivation + origin enum (016/034). |
| report/vrt.rs | Vrt wire types (VrtEntry, VrtDerivation, etc.). |
| report/algorithm_validity.rs | AlgorithmValidityCheck wire types (032). |
| report/time_fmt.rs | RFC 3339 time serde helpers. |
+-------------------------------------+------------------------------------------------------------+
wasm32 へコンパイル、暗号なし)+----------------+---------------------------------------------------------------+
| Crate | Concern |
+----------------+---------------------------------------------------------------+
| pverify-xades | XML parse + Exclusive C14N + XadesComponents extraction. |
| pverify-asic | ZIP read + ASiC manifest parse + AsicSignature extraction. |
| pverify-jades | JWS JSON Serialization parse + JadesComponents extraction. |
+----------------+---------------------------------------------------------------+
これらは(それが所有する境界型のために)pverify-core に依存するが、暗号を 一切含まず、scripts/cargo-tree-gate.sh によってコアのランタイムグラフから 禁止されている。
std、コア/WASM ランタイムグラフには含まれない)+--------------------------+------------------------------------------------------------------+
| Crate / file | Concern |
+--------------------------+------------------------------------------------------------------+
| pverify-cli/src/main.rs | CLI entry; Command::Verify; clock capture; mode selection. |
| pverify-cli/src/pdf.rs | PDF parse (lopdf): extract_signatures + extract_validation_data |
| | (/DSS union-across-revisions + /VRI by /Contents SHA-1). |
| pverify-cli/src/trust.rs | Trust-anchor load + SHA-256 fingerprint + validity-covers flag. |
| pverify-cli/src/modes/ | online.rs (+ net_guard), offline.rs, from_bundle.rs, ldap.rs. |
| pverify-cli/src/net_guard.rs | SSRF resolve-then-pin classifier (021). |
| pverify-cli/src/render.rs| Report -> JSON serialisation. |
| pverify-eutl | EU Trusted List ingestion -> TrustAnchor (014). |
| pverify-aatl | AATL ingestion -> TrustAnchor (026). |
| pverify-anchor-inventory | Read-over aggregator of all anchor distribution channels. |
| web/pverify-wasm/src/lib.rs | Browser host: in-WASM PDF/XAdES/ASiC/JAdES extraction + |
| | verify_with; CLI-parity extractors. |
+--------------------------+------------------------------------------------------------------+
publish = false)fixture-gen Test-fixture / signing tooling.
pverify-test-helpers Shared test helpers (CLI dev-dep only).
xtask cargo xtask cbom-check drift gate (013); depends ON core.
+--------------------------------------------------------+----------------------------------------------+
| Artefact | Concern |
+--------------------------------------------------------+----------------------------------------------+
| crates/pverify-core/src/report/schema.rs | SCHEMA_VERSION source of truth. |
| specs/001-verify-cades-pades/contracts/report-schema.json | JSON Schema mirror (1:1 with the enums). |
| scripts/cargo-tree-gate.sh | Empirical core-graph isolation guard. |
| scripts/fetch-dss-interop-fixtures.sh | Fetch+pin DSS interop vectors (never commit).|
| cbom.json | CycloneDX crypto BOM (verify-only). |
+--------------------------------------------------------+----------------------------------------------+
signatures[].vrt を埋める、オブジェクトごとの VRT エンジンおよび再帰的な 外側カバーの規則については、タイムスタンプ/VRT の章(第12章)および第14章 (レポート導出/結果モデル)を参照。§18.3 のクローズドな列挙の規律と §18.4 の系譜は、監査者にとっての2つの錨 (anchor)である: レポートで見られるいかなる値も §18.3 に現れねばならず、 見られるいかなる schema_version も §18.4 に現れねばならない。それらの表の 外側にある値またはバージョンは、構造上、この系譜の pverify バイナリが発行 できるものではない。