Skip to content

fix(state-node): unify request PoP with signed timestamp; drop jti single-use; verify JWT over wire bytes (#60, #61) - #62

Closed
somasekimoto wants to merge 1 commit into
mainfrom
fix/jwt-pop-request-freshness
Closed

somasekimoto wants to merge 1 commit into
mainfrom
fix/jwt-pop-request-freshness

Conversation

@somasekimoto

Copy link
Copy Markdown
Contributor

概要

Issue #61(委譲 JWT の jti 単回消費が SDK のトークン再利用設計と矛盾)と #60(JWT 署名検証が再シリアライズ依存で brittle)の修正。#54/#56 とは独立の main 向け PR。

#61: PoP を {operation}:{resource}:{timestamp} に統一(案 A)

問題の構造: 委譲 JWT の PoP 署名対象が固定文字列 {iss}:{aud}:{jti} で、同じトークンを使う限り署名が毎回同一 = リプレイと区別できない。timestamp ヘッダは署名に入っておらず差し替え可能。その帳尻合わせとして入っていた jti 単回消費が、

  • SDK の「share 時に委譲トークンを 1 個渡し TTL(1h)内で再利用する」設計と矛盾(1 トークン 1 リクエストしかできない)
  • 通常の read は履歴取得 → データ取得で最低 2 リクエストなので、正規の利用が成立しない
  • nonce 記録がノードごとに独立で、セキュリティ特性としても一貫しない

修正: 認証スキームをトークン種別によらず 1 本化。

  • 署名対象 = {operation}:{resource}:{timestamp}(書き込みは body hash + timestamp)。リクエストの新しさが署名の中に入る
  • リプレイ防御は署名内 timestamp の鮮度チェック(5 分窓)に一本化。jti 単回消費・nonce ストアを廃止
  • timestamp は構造的に必須(サーバ時刻フォールバック廃止 = 欠如は認証エラー)。従来はフォールバックにより鮮度チェックを構造上スキップ可能だった
  • 役割分担が明確になる: 権限があること = JWT(owner 署名のケイパビリティ)/ 本人が今送っていること = 署名内 timestamp 付き PoP
  • 盗まれた署名でできるのは「同じリソースへの同じ操作を 5 分以内に再実行」のみ — owner 用の非 JWT パスと完全に同水準(relay はどのみち暗号文を運ぶ役で得られる情報は増えない)
  • PoP 検証は verify_caller_signature に一本化(全経路が authorize より前に必ず通る)。ucan_adapter は権限判定に専念し、リクエスト署名は存在チェックのみ

#60: JWT 署名をワイヤ上のバイト列で検証

パース後構造体を再シリアライズして署名対象を作り直していたため、発行者の JSON フィールド順序・空白が異なると正当なトークンを拒否する実装だった(test-auth-generator に canonical field order ハックが必要だったのはこれが原因)。受信した header.payload セグメントそのものを検証する verify_jwt_signature_wire を導入し、JWT 検証 2 箇所(認証アダプタ・認可アダプタ)を置換。

テスト

🤖 Generated with Claude Code

…op jti single-use; verify JWT over wire bytes

Closes #61, closes #60.

問題(#61): 委譲 JWT の PoP 署名対象が固定文字列 {iss}:{aud}:{jti} で、
リクエストの新しさが署名に入っていなかった。その帳尻合わせの jti 単回消費が
「委譲トークンを 1 個渡して TTL 内で再利用する」SDK の設計と矛盾し、
履歴取得 → データ取得という通常の read すら成立しなかった(nonce 記録は
ノードごとに独立で一貫性もない)。

修正(案 A): 署名対象をトークン種別によらず {operation}:{resource}:{timestamp}
(書き込みは body hash + timestamp)に統一。リプレイ防御は署名内 timestamp の
鮮度チェック(5 分窓)に一本化し、jti 単回消費と nonce ストアを廃止。
timestamp はサーバ時刻フォールバックをやめ構造的に必須とした(欠如 = 認証
エラー)。盗まれた署名でできることは「同じリソースへの同じ操作を 5 分以内に
再実行」のみで、owner 用の非 JWT パスと同水準。

問題(#60): JWT 署名検証がパース後構造体の再シリアライズに依存し、発行者の
JSON フィールド順序が異なると正当なトークンを拒否する brittle な実装だった。

修正: 受信したワイヤ上の header.payload セグメントに対して検証する
verify_jwt_signature_wire を導入し、JWT 検証 2 箇所をこれに置換。

- PoP 検証は verify_caller_signature に一本化(全経路が authorize 前に通る)。
  ucan_adapter は権限判定に専念し、署名は存在チェックのみ(検証は上流で済)
- test-auth-generator / テストヘルパも統一形式へ。フィールド順序非依存・
  改ざん拒否・トークン再利用・timestamp 必須の回帰テストを追加

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@somasekimoto

Copy link
Copy Markdown
Contributor Author

PR #56 に統合しました(ユーザー方針: 認証まわりの修正も read 経路の PR に一本化)。コミット b676287 として feature/read-response-signing にマージ済み。#60 / #61 は #56 のマージでクローズされます。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant