PKCEなんなの?
https://tools.ietf.org/html/rfc7636
スマホアプリがカスタムURLスキームで認可レスポンスを受信する場合、悪意のあるアプリがクライアントのアプリと同じカスタムURLスキームを利用していると、リダイレクト先が意図しないアプリになる可能性があって、その場合認可コードが盗まれる。
スマホアプリの場合、Clientのシークレットは安全ではないので、シークレットと認可コードを盗まれると、悪意のあるアプリがアクセストークンを取得できてしまう。
特に、Clientのシークレットがアプリに対して固定されてる場合、悪意のあるアプリ開発者はClientのアプリを自分で解析したりしてよしなにできちゃう。
という文脈で、Dynamic Client Registrationのが安全なんだな、という風に理解している。(が、そもそもアプリ側が脆弱だったりするとやっぱりシークレットは漏れる)
で、これをなんとかするための仕様がPKCE。
どうやるか?
- クライアントはcode_verifierという暗号学的に安全な乱数を生成し、そこからcode_challengeを生成して、認可リクエストに含める
- このときcode_challenge_method、というパラメータを含めても良い
- code_challenge_methodが指定もしくはPLAINの場合、code_challengeはcode_verifier
- code_challenge_methodがSHA256の場合、code_challengeはBASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
- サーバーは認可コードをレスポンスする
- このとき発行した認可コードと、クライアントが送信したcode_challenge、code_challenge_methodの値を紐づけて保持しておく
- クライアントはトークンリクエストの際に、認可リクエストの時に生成したcode_verifierをパラメーターを含める
- サーバーはトークンリクエストに含まれるcode_verifierから生成したcode_challengeが、認可リクエストで送信されてきたcode_challengeと同じかどうかを検証する
これでなんでトークンが盗まれるのを防げるのか
- 悪意のあるアプリは、認可コードは盗めるが、認可リクエストの内容は盗めない(盗めるケースについては後述)
- ので、悪意のあるアプリはトークンリクエストで送信すべきcode_challengeの値がわからない
- ので、悪意のあるアプリはトークンを発行できない
気になった仕様
サーバーが認可コードとクライアントが送信したcode_challenge、code_challenge_methodの値をどう保持するか
具体的に仕様では規定されてないんだけど、
Typically, the "code_challenge" and "code_challenge_method" values
are stored in encrypted form in the "code" itself but could
alternatively be stored on the server associated with the code. The
server MUST NOT include the "code_challenge" value in client requests
in a form that other entities can extract.
とあって、認可コードに暗号化して埋め込んじゃう話がある
認可コードをJWEにしちゃう、とかでいいはず
ここの実装時にここの永続化がだるそうだなと思っていて、なるほど感があった
code_challenge_methodがSHA256だと何を守れるのか
4b. A more sophisticated attack scenario allows the attacker to
observe requests (in addition to responses) to the
authorization endpoint. The attacker is, however, not able to
act as a man in the middle. This was caused by leaking http
log information in the OS. To mitigate this,
"code_challenge_method" value must be set either to "S256" or
a value defined by a cryptographically secure
"code_challenge_method" extension.
攻撃者が認可リクエストも見れちゃった場合、code_challenge=code_verifierだと認可リクエストに含まれるcode_challengeをそのままトークンリクエストに含めればトークンが取得できてしまう。
が、code_challengeがSHA256でハッシュ化してあると攻撃者はcode_verifierがわからないので、トークンを取得できない。
クライアントが認可リクエストにcode_challengeを指定しなかった場合は?後からPKCE対応したらどうなる?
If the server requires Proof Key for Code Exchange (PKCE) by OAuth
public clients and the client does not send the "code_challenge" in
the request, the authorization endpoint MUST return the authorization
error response with the "error" value set to "invalid_request".
なので、エラーにしないといけない、が、5. Compatibilityに
Server implementations of this specification MAY accept OAuth2.0
clients that do not implement this extension. If the "code_verifier"
is not received from the client in the Authorization Request, servers
supporting backwards compatibility revert to the OAuth 2.0 [RFC6749]
protocol without this extension.
とある。
認可リクエストで送信するのが"code_verifier"となっている意味がよくわからない(認可リクエストで送るのは"code_challenge"じゃないのか?)が、認可リクエストに"code_challenge"が含まれてなければPKCEしない、で良いのかな。
トークンリクエストに"code_verifier"がなかったら検証なし、だとそもそも成立しない(悪意のあるアプリがトークンを取得できてしまう)ので、それは違うと思うんだけども・・・。
qiita.com
この記事で紹介されているコードだと、認可リクエストに"code_challenge"が指定されてなかったらトークンリクエストの"code_verifier"をスキップしてるっぽいので、多分そういうことなんだろうな、と思う。(多分・・・)