1. 概要

本稿は Web/A 文書の Layer 2 Encryption (L2E) を定義します。Layer 1 はテンプレート(質問)の完全性、Layer 2(Signature)はユーザー回答(回答者)の真正性を保証します。Layer 2 Encryption は 機密性 を提供し、配送経路やブラウザ保存が信頼できない場合でも、回答内容が 受領者(Issuer/Aggregator)だけ に読めるようにします。

2. 脅威モデル

  • 機密性: メール・CDN・悪意ある拡張機能など中間者から回答を保護。
  • テンプレート結合: 暗号文が特定の Layer 1 テンプレートに強く結び付くこと(回答の差し替え攻撃を防止)。
  • 前方秘匿性 (Forward Secrecy): 過去の通信が、将来の鍵漏洩によって遡及的に復号されないこと。
  • トラフィック分析耐性: メッセージのサイズから内容を推測されないこと。

3. 暗号構成

3.1. HPKE 風ハイブリッド暗号

Web/A L2E は HPKE (RFC 9180) の設計思想を取り入れた構成を採用しています。

  • KEM (Key Encapsulation Mechanism):
    • 古典暗号: X25519
    • 耐量子暗号 (任意): ML-KEM-768
  • KDF (Key Derivation Function): HKDF-SHA256
  • AEAD (Authenticated Encryption with Associated Data): AES-256-GCM

3.2. AAD によるバインド

AEAD の aadlayer1_ref を含めることで、テンプレートの差し替えを防止します。layer1_ref が一致しない場合、AEAD の認証タグ検証に失敗(復号不能)します。


4. セキュリティ強化 (Security Enhancements)

4.1. リプレイ保護 (Nonce Tracking)

Web/A プロトコルはステートレスであるため、攻撃者が有効な暗号化エンベロープを物理的に再送する(リプレイ攻撃)リスクがあります。

  • 対策: Layer2Encrypted 構造体に meta.nonce フィールドを含みます。
  • 要件: 集計ツール(Aggregator)は 必ず nonce の検証を実装しなければなりません。
  • 永続化ストア: 再起動後もリプレイを防止するため、CLI用(JsonFileReplayStore)およびブラウザ用(LocalStorageReplayStore)の永続化ストアが用意されています。

4.2. トラフィック分析(パディング)

暗号文の長さは、平文のおおよそのサイズを露呈させ、情報の推測(例:「はい」と「いいえ」の回答差)を許す可能性があります。

  • 対策: ペイロードを バケット方式の固定サイズ (1KB, 4KB, 16KB, 64KB等) に切り上げるパディング処理を行います。これにより、データ量が増大してもメッセージサイズから正確な内容を特定することを極めて困難にします。

4.3. サイドチャンネル対策 (Unified Errors)

暗号操作における詳細なエラーメッセージは、攻撃者にとっての「オラクル(神託)」となります(例:AADミスマッチとMAC失敗の区別による推測)。

  • 対策: 復号処理を行う decryptLayer2 関数は、失敗の理由(AADミスマッチ、MAC不一致、パースエラー等)に関わらず、一律で "Decryption failed" という汎用エラーを返します。

4.4. 前方秘匿性 (Forward Secrecy) と Pre-Key

  • 現状: デフォルトでは、Layer 1 フォームに埋め込まれた静的な受信者公開鍵を使用します。これは運用が容易ですが、将来受信者の秘密鍵が漏洩した場合、過去の通信が復号されるリスクがあります。
  • 対策(鍵の更新): 階層的鍵派生により、キャンペーンごとに鍵を分けることが容易です。運用者は頻繁に鍵を更新することが推奨されます。
  • 強化(Pre-Key 方式): 高いセキュリティが求められる用途向けに Pre-Key をサポートします。フォームに prekey_url が設定されている場合、クライアントは送信直前にサーバーから「一度使い捨ての公開鍵」を取得し、それを用いて暗号化します。これにより、長期鍵が漏洩しても過去の通信の安全性が保たれます。

5. データ構造

5.1. Layer 2 Encrypted Envelope

{
  "weba_version": "0.1",
  "layer1_ref": "sha256:...",
  "layer2": {
    "enc": "HPKE-v1",
    "suite": {
      "kem": "X25519(+ML-KEM-768)",
      "kdf": "HKDF-SHA256",
      "aead": "AES-256-GCM"
    },
    /* ... 鍵カプセル化データ、暗号文、AAD ... */
  },
  "meta": {
    "id": "did:content:sha256:...",         // メッセージ内容のハッシュ
    "thread_id": "did:content:sha256:...",  // スレッド開始メッセージのID
    "in_reply_to": "did:content:sha256:...",// 直前のメッセージのID
    "action": "submit",                     // submit, approve, comment, reject 等
    "nonce": "base64...",
    "created_at": "2025-12-29T..."
  }
}

5.2. Reply メタデータとルーティング

L2E で受領した回答に対する返信を定義するため、meta.reply_to を追加 します。返信先の決定、DID 解決、暗号化と署名、そして Folio への保存 位置をここで標準化します。

reply_to の必須フィールド(最小セット)

  • reply_to.did: 返信先の DID。必須。
  • reply_to.endpoint: 返信先の配信エンドポイント。必須。
  • reply_to.broker: 仲介を行うブローカーの DID。任意。

DID 解決手順(返信先の確定)

  1. reply_to.endpoint があれば、それを優先して使用。
  2. reply_to.endpoint が無い場合、reply_to.did を解決して DID Doc を 取得。
  3. DID Doc の service から type: "weba-reply" を検索し、serviceEndpoint を返信先として採用。
  4. did:web の場合は HTTPS で DID Doc を取得し、サービスエントリを 検索する。
  5. 取得結果が複数ある場合は、priority の低いもの(数値が小さいもの) を優先する。解決不能なら返信は失敗とする。

暗号化対象と署名対象

  • 返信ペイロードは「送信者署名 → 受信者 DID への L2E 暗号化」の順。
  • 署名対象は、返信本文・reply_tolayer1_ref を含む「平文 JSON」。
  • 暗号化対象は署名済みペイロード全体(署名情報を含む)。

ブローカー経由のルール

  • ブローカーは reply_to.didreply_to.endpoint上書きしては ならない
  • ブローカーは転送記録を meta.forwarded_by[] に追記する。
  • 送信者は reply_to.broker を指定した場合、ブローカーによる forwarded_by 追記を許容する。

スレッド管理とメッセージ ID

  • 各メッセージは自分自身を識別する meta.id を持つ。推奨される計算式は did:content:sha256:<Hash of Layer2Payload> である。
  • meta.thread_id: スレッド(一連のやり取り)の起点となる最初のメッセージ ID。
  • meta.in_reply_to: 直接の返信先となる親メッセージの ID。
  • meta.action: メッセージの意図を定義する(例: submit(申請), approve(承認), reject(却下), comment(補足))。これらによりエージェントはワークフローの進行状況を把握する。
  • did:content:<hash> 形式を採用することで、中央サーバーなしでメッセージの一意性と参照を保証する。

Folio への保存先とスレッド一覧

  • Folio では返信メタデータを history/ 内のメッセージ記録に保持する。
  • メッセージ本文とは別に、history/<message-id>.meta.jsonreply_toidthread_idin_reply_toaction および forwarded_by を保存する。
  • AI エージェントは thread_id でグループ化し、in_reply_to を辿ることで、送信履歴を論理的なスレッドとして復元し、action に基づいて現在の状況を提示する。

6. 実装とロードマップ

6.1. サプライチェーンセキュリティ

  • ベンンダリング (Vendoring): @noble/* 等のクリティカルな暗号ライブラリをプロジェクト内に直接取り込み(src/vendor/)、外部レジストリへの依存とインジェクションリスクを排除しました。
  • SBOM/CBOM: CycloneDX形式の sbom.json を整備し、暗号資産とアルゴリズムの透明性を確保しています。

6.2. 実装状況

  • コアロジック: ベンンダリングされたプリミティブを用いた TypeScript 実装(src/core/l2crypto.ts)が完了しています。
  • リプレイ保護: CLI およびブラウザアグリゲーターでの永続化ストレージ対応が完了しています。
  • WebAssembly (WASM) による実装: JavaScript 環境における実行タイミングの揺らぎやメモリ管理の不透明性を解消するため、コアとなる暗号ロジック(AES-GCM, X25519, ML-KEM/ML-DSA等)は Rust で記述され、WebAssembly にコンパイルされて提供されています。これにより、ブラウザのメインスレッドをブロックせず、かつ安全で決定論的な暗号操作が保証されます。

7. 結論

Web/A Layer 2 Encryption は、標準的なプリミティブ(HPKE, AES-GCM)とブラウザのネイティブ能力を活用し、サーバーレスでありながら高度な機密性を提供します。本稿で定義したセキュリティ強化策を適用することで、個人向けのアンケートから組織間の機密データ交換まで、幅広いユースケースにおいて信頼できるインフラを提供します。