- ブラウザURL入力今ここ
- DNS名前→IP
- ルータ経路選択
- ロードバランサ振り分け
- Nginx受け口
- アプリ処理今ここ
- DB保存
このコースでは、ブラウザが確かめるのは「どこから来たか」までで、「本人が望んだか」はサーバが確かめるしかない、という境目を見てきました。最終回は、別サーバが発行した身元の主張を受け取る側が、何を自分で確かめなければならないかを読みます。
まず結論
ID トークンの署名が正しいことは、本物の発行者が出したことを示すだけで、自分に宛てて出したことは示しません。OpenID Connect Core は、aud に自分の client_id が無いトークンを MUST で拒否させます。
https で証明書の名前を照合するのと同じ形の検査です。どちらも「本物の発行者が、別の相手向けに出した本物」を弾くためにあり、受け取った側が自分でしか行えません。
自分(client_id が app-a)の検証器に、同じ発行者が同じ鍵で正しく署名したトークンが届きました。違いは aud が app-b であることだけです。どうなるでしょうか。
当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。
署名が示すのは差出人で、宛先ではない
ID トークンは、発行者が「この利用者を認証した」と主張する JWT です。受け取る側(RP)は、何を確かめれば受け入れてよいのでしょうか。
署名が正しければ、本物の発行者が出したことは分かります。ところが発行者は多くのアプリにトークンを出しています。別のアプリに宛てて出された本物も、署名は正しいままです。
そこで OpenID Connect Core §2 は aud を REQUIRED にし、It MUST contain the OAuth 2.0 client_id of the Relying Party as an audience value. と定めます。§3.1.3.7 は受け取る側の義務です。
The Client MUST validate that the aud (audience) Claim contains its client_id value registered at the Issuer identified by the iss (issuer) Claim as an audience. The aud (audience) Claim MAY contain an array with more than one element. The ID Token MUST be rejected if the ID Token does not list the Client as a valid audience, or if it contains additional audiences not trusted by the Client.
自分が載っていなければ拒否します。信頼しない宛先が混じっていても拒否します。JWT 一般(RFC 7519 §4.1.3)では aud は OPTIONAL です。ID トークンで必須になっているのは、宛先違いの本物を受け取る場面を仕様が想定しているからです。
- ・署名が止めるのは書き換え。宛先違いの本物は aud でしか止まらない
- ・どちらの検査も、受け取った側が自分で行うしかない
同じ鍵で、aud だけ変えたトークン
自前のおもちゃの発行者で試しました。実在の発行者ではなく、1プロセスの中で RSA 鍵を作り、RS256 で署名し、そのまま検証しています。検証器は自分を app-a とし、署名・iss の完全一致・aud・exp・nonce を見ます。iss を http にしたのと、「自分以外の宛先は信頼しない」としたのは、おもちゃとしての単純化です(仕様の iss は https の URL)。
--- T1 aud=app-a (for me)checks: {"signature":true,"iss_exact":true,"aud_contains_my_client_id":true,"aud_no_untrusted_extra":true,"exp_in_future":true,"nonce_matches":true}verdict: ACCEPT--- T2 aud=app-b (same issuer, same key, only aud differs)checks: {"signature":true,"iss_exact":true,"aud_contains_my_client_id":false,"aud_no_untrusted_extra":false,"exp_in_future":true,"nonce_matches":true}verdict: REJECT (aud_contains_my_client_id, aud_no_untrusted_extra)--- T3 T1 with aud edited to app-b, not re-signedchecks: {"signature":false,"iss_exact":true,"aud_contains_my_client_id":false,"aud_no_untrusted_extra":false,"exp_in_future":true,"nonce_matches":true}verdict: REJECT (signature, aud_contains_my_client_id, aud_no_untrusted_extra)
T2 は署名が通り、openssl で独立に検算しても Verified OK でした。iss・exp・nonce も通ります。落ちたのは aud だけです。 T3 は自分宛てのトークンの aud を後から書き換えたもので、署名が壊れて openssl も Verification failure を返しました。鍵と nonce を変えた2回目も同じ判定でした。
実演の T2(aud だけ app-b にして正しく署名)と T3(自分宛てのトークンの aud を書き換え、署名はそのまま)の違いは何ですか。
宛先と、自分が頼んだ応答か
同じ節は nonce も扱います。§3.1.2.1 では、クライアントが認証要求に載せる値です(OPTIONAL、推測されない十分なエントロピーが MUST)。§2 は The value is passed through unmodified from the Authentication Request to the ID Token. と書き、§3.1.3.7 は送った場合に一致の確認を MUST とします。CSP の nonce とは名前が同じだけの別物です。
aud は「自分宛てか」、nonce は「このセッションが出した要求への応答か」を確かめます。どちらも差出人の確認では代わりになりません。
署名にも範囲があります。§3.1.3.7 は、トークンエンドポイントから直接受け取った ID トークンなら the TLS server validation MAY be used to validate the issuer in place of checking the token signature と書きます。省いてよいのは発行者の確認手段だけで、aud の検査は同じ節で MUST のままです。
このコースの芯に戻ります。ブラウザが確かめられるのは「どこから来たか」まででした。ID トークンでは、署名や TLS が示す「どこから来たか」の先に、「自分宛てか」「自分が頼んだか」が残ります。それを確かめられるのは、受け取った側だけです。
https も、名前を照合している
同じ形の検査が https にあります。RFC 9110 §4.3.4 です。
To establish a secured connection to dereference a URI, a client MUST verify that the service's identity is an acceptable match for the URI's origin server. Certificate verification is used to prevent server impersonation by an on-path attacker or by an attacker that controls name resolution.
証明書が信頼できる認証局に正しく署名されていても、名前が自分の行きたかったオリジンに合わなければ受け入れません。 別のサーバ宛てに正しく発行された証明書を弾くためです。クライアントは URI のホストから照合の基準(reference identity)を組み立てます。
手順は Section 6 of [RFC6125] を参照しています。RFC 6125 は 2023年11月の RFC 9525 で置き換えられましたが、9110 は改訂されていません。9525 では語も reference identifier に変わっています。
OpenID Connect Core もこの照合と無縁ではありません。§16.17 は Whenever TLS is used, a TLS server certificate check MUST be performed, per RFC 6125 と定め、§5.3.4 も RFC 6125 による検査で応答した OP を確かめさせます。9110 が参照するのと同じ手順です。§3.1.3.7 の「TLS のサーバ検証で代えてよい」も、この名前照合が行われていることを前提に読めます。
教材の整理なのは、aud の検査と証明書の名前照合を「同じ形」として対応させる部分だけです。証明書のチェーンの検証は、コース『HTTPとTLSの仕組み』で扱います。
RFC 9110 §4.3.4 が https のクライアントに MUST で求めている照合はどれですか。
ID トークンの aud
発行者は複数のアプリに発行する。署名が正しくても、aud に自分の client_id が無ければ拒否する(OpenID Connect Core §3.1.3.7)。
照合の基準 自分の client_id照合する値 トークンの aud合わないとき 拒否(MUST)
https の証明書の名前
認証局は多くのサーバに証明書を発行する。署名が正しくても、名前が URI のオリジンサーバに合わなければ受け入れない(RFC 9110 §4.3.4)。
照合の基準 URI のホストから作る reference identity照合する値 証明書が示すサービスの身元合わないとき 受け入れない(MUST)
片方はアプリ間の認証、片方は通信路の話です。2つの検査が弾いている相手は何でしょうか。
「偽物」ではなく「本物だが自分宛てではないもの」を考えてください。
この章のまとめ
- 署名が示すのは差出人だけ。別のアプリに宛てた本物も署名は正しい。
- OpenID Connect Core は aud を REQUIRED にし、自分の client_id が無ければ、または信頼しない宛先が混じっていれば MUST で拒否させる。
- 実測では、aud だけ変えた正しい署名のトークンが aud の検査だけで落ちた。書き換えは署名が止め、宛先違いは aud が止める。
- nonce は「自分が出した要求への応答か」を確かめる値。CSP の nonce とは別物。
- 直接受け取った ID トークンでは TLS で発行者の確認を代えてよい(MAY)が、aud の検査は残る。
- https の名前照合も同じ形。RFC 9110 §4.3.4 は RFC 6125 を参照し、6125 は RFC 9525 に置き換えられている。
- コースの芯: 「どこから来たか」の先にある「自分宛てか」「本人が望んだか」は、受け取った側が自分で確かめるしかない。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
認可コードフローで、トークンエンドポイントから直接受け取った ID トークンについて、OpenID Connect Core §3.1.3.7 は署名をどう扱っていますか。
用語集(4語)
- ID トークン
- OpenID Connect で発行者が認証結果を表す JWT。iss・sub・aud・exp・iat が REQUIRED。
- aud (audience)
- トークンの宛先。JWT 一般(RFC 7519)では任意だが、ID トークンでは REQUIRED で、RP の client_id を含まなければならない。
- nonce (OpenID Connect)
- クライアントが認証要求に載せ、ID トークンにそのまま戻ってくる値。CSP の nonce とは別物。
- reference identity
- https のクライアントが URI のホストから組み立てる、照合の基準になる身元。RFC 9525 では reference identifier と呼ぶ。