@target-uri、adagents.json の authorized_agents[].url エントリ、TMP AvailablePackage の seller_agent.agent_url、format-id と ProviderEntry の agent_url、その他 URL がプライマリキーである任意のレジストリ。単一の正準化アルゴリズムがこれらすべてを統治するため、どのサーフェスがルックアップをしていても、バイト単位で異なるが意味的に等しい 2 つの URL は等しく比較されます。このページはそのアルゴリズムの権威ある拠点です。リクエスト署名プロファイル はそれを引用し、トランスポート固有の拡張を追加します。
アルゴリズム
正準化は、この順序で RFC 3986 §6.2.2(構文ベースの正規化)と §6.2.3(スキームベースの正規化)を適用します。実装はすべてのステップを適用し、結果をバイト単位で比較しなければなりません(MUST)。-
スキームを小文字化(
HTTPS→https)。スキーム自体は保持されます —httpとhttpsは異なる形式に正準化され、識別子比較で一致してはなりません(MUST NOT)。 -
ホストを小文字化。 IDN ラベルについては、UTS-46 Nontransitional processing(
CheckHyphens=true、CheckBidi=true、UseSTD3ASCIIRules=true、Transitional_Processing=false) を使って Punycode A-label(ACE 形式)に変換します(bücher.example→xn--bcher-kva.example)。処理モードのピン留めが重要です: ToASCII の前に非 ASCII 入力を ASCII 小文字化すると、UTS-46 正しい処理とは異なる A-label が生成され、TypeScript(url.domainToASCII)、Go(golang.org/x/net/idna)、Python(idnaパッケージ — IDNA2003 であるstr.encode('idna')ではない)はモードのデフォルトで正当に分岐します。生成者によって ToASCII 正規化されていない生の非 ASCII バイトを含むホストは、比較者によって拒否されなければなりません(MUST) — 受信者は黙って再正規化しません。IPv6 リテラルについては、[と]ブラケットを保持し、その内部の 16 進数を小文字化します([2001:DB8::1]→[2001:db8::1])。IPv6 ゾーン識別子(RFC 6874)は拒否されなければなりません(MUST) — ゾーン ID はノードローカルで、生成ホストの外では意味を持ちません。実装は[...]内に%25を含む任意の URL を拒否しなければなりません(MUST)。 -
userinfo を除去。
user:pass@host→host。次の authority 形状は不正な形式で拒否されなければなりません(MUST) — 生成者はそれらを発してはならず(MUST NOT)、比較者はそれらを拒否しなければなりません(MUST):- userinfo だがホストなし:
https://user@/p - ホストがまったくない:
https:///p、https://:443/p - 閉じブラケットが欠けたブラケット付きホスト:
https://[::1/p - ブラケット外の素の IPv6 アドレス:
https://fe80::1/p
- userinfo だがホストなし:
-
デフォルトポートを除去。 https には
:443、http には:80。他のすべてのポートを保持(:8443)。 -
パスに
remove_dot_segments(RFC 3986 §5.2.4)を適用するが、連続するスラッシュはバイト単位で保持。/a//bは/a//bのままでなければなりません(MUST) — RFC 3986 はそれらを折り畳むことを義務付けず、保持することでパス混同攻撃サーフェスを閉じます: 一方が/admin//foo→/admin/fooを折り畳み、他方が/admin//fooを異なる(潜在的により無防備な)ハンドラーにディスパッチする場合、攻撃者は 1 つの URL に署名または認可し、別のものを実行できます。URL ベースの認可をデプロイするサーバーは、影響を受けるルートでスラッシュ折り畳みを無効にしなければなりません(MUST)(nginx: merge_slashes off;、Express: 事前正規化しない、Go 1.22+http.ServeMux: 受信パスを保持する明示的なhttp.Handlerを使う)。パスが空でありかつ authority が存在する場合、/を代入します(RFC 3986 §6.2.3。https://host?x=1→https://host/?x=1)。 -
パーセントエンコーディングを正規化。 16 進数を大文字化(
%2f→%2F)。パーセントエンコードされた unreserved 文字をデコード(RFC 3986 §2.3 に従いALPHA / DIGIT / "-" / "." / "_" / "~"なので%7E→~、%2Dfoo→-foo、%41→A)。reserved 文字はパーセントエンコードされたままにする(%3Aは%3Aのまま、%2Fは%2Fのまま)。パーセントエンコーディング正規化はパスとクエリに適用されます。ゾーン識別子はステップ 2 で拒否されるのでこのステップに到達しません。 -
クエリ文字列をバイト単位で保持。 パラメーターを並べ替えてはならず(MUST NOT)、再エンコードしてはならず(MUST NOT)、
+をスペースとして解釈してはなりません(MUST NOT)。空のクエリを伴う末尾の?は保持されます(https://host/p?はhttps://host/p?に正準化され、https://host/pとは別)。?のない URL は?なしのままです。クエリパラメーター順のみが異なる 2 つの URL は、等価ではなく異なる正準形式です。 - フラグメントを除去。 フラグメントは識別子比較に決して参加せず、RFC 9421 §2.2.2 に従いワイヤー上で送られません。
どこに適用されるか
署名プロファイル拡張
リクエスト署名プロファイル は、このアルゴリズムの上にトランスポート固有のルールを重ねます:@authorityは正準化された authority から導出され、同じ正準化の後に HTTP/2:authority疑似ヘッダー(または受信した HTTP/1.1Hostヘッダー)と比較されます。非署名の呼び出し元は URL のみから@authorityを導出します。- 不正な形式の authority は、署名パスで
request_target_uri_malformedで拒否されます。非署名の呼び出し元は独自の認可失敗コードを使います(例: TMP にはseller_not_authorized)。 - 受信した HTTP/2 リクエストに
:authorityとHostの両方が存在するとき、署名プロファイルは正準化後のバイト等価を要求します。これは署名固有のゲートです。なぜなら HTTP/1.1Hostは転送中に書き換えられうるからです。
適合性ベクター
canonicalization.json セットは、固定された { input_url, expected_target_uri, expected_authority } トリプルと、不正な形式の authority 拒否ケースで、上記のすべてのルールを実行します。非署名の呼び出し元は expected_target_uri のみと比較します — expected_authority は署名プロファイルが使う HTTP ヘッダー由来の形式です。上の表のサーフェスのいずれかを実装する SDK は、すべてのコミットでこのセットを実行すべきです(SHOULD)。正準化の分岐は、本番の相互運用バグがサーフェスするまで静かです。
よくある落とし穴
- ToASCII の前に IDN を ASCII 小文字化。
Bücher.exampleを ASCII で小文字化 →bücher.exampleだが、UTS-46 正しいパスは元のバイトを処理しなければならない。TypeScripturl.domainToASCII、Gogolang.org/x/net/idna、Python のidnaパッケージ(IDNA2003 であるstr.encode('idna')ではない)はモードのデフォルトで分岐する。上記の 4 つのフラグを持つ UTS-46 Nontransitional にピン留めする。 - 連続するスラッシュの折り畳み。
/admin//fooと/admin/fooは異なる正準形式。折り畳む生成者と折り畳まない比較者(またはその逆)はパス混同攻撃を開く。 - クエリの再エンコード。 クエリ文字列の正規化は魅力的に見えるが禁止。
?x=1&y=2と?y=2&x=1は異なる正準形式。 - 空のクエリを伴う末尾の
?。https://host/p?とhttps://host/pは異なる。生成者が送ったものを保持する。adagents.jsonや類似のレジストリに URL を登録するパブリッシャーは、空クエリ形式を意図しない限り末尾の?なしで貼り付けるべき。 - フラグメント除去を忘れる。 フラグメントは識別子比較に決して参加しない。
http://とhttps://の混在。 スキームは強制ではなく保持される。authorized_agents[].urlを登録するパブリッシャーは、パブリックインターネットで到達可能を意図するものすべてにhttps://を使わなければならない(MUST) —http://エントリはhttps://呼び出し元との一致に失敗し、その逆も同様で、非 HTTPS URL はトランスポート完全性保証を持たない。