PKCEについて気になった仕様いくつか

PKCEなんなの?

https://tools.ietf.org/html/rfc7636

スマホアプリがカスタムURLスキームで認可レスポンスを受信する場合、悪意のあるアプリがクライアントのアプリと同じカスタムURLスキームを利用していると、リダイレクト先が意図しないアプリになる可能性があって、その場合認可コードが盗まれる。 スマホアプリの場合、Clientのシークレットは安全ではないので、シークレットと認可コードを盗まれると、悪意のあるアプリがアクセストークンを取得できてしまう。

特に、Clientのシークレットがアプリに対して固定されてる場合、悪意のあるアプリ開発者はClientのアプリを自分で解析したりしてよしなにできちゃう。 という文脈で、Dynamic Client Registrationのが安全なんだな、という風に理解している。(が、そもそもアプリ側が脆弱だったりするとやっぱりシークレットは漏れる)

で、これをなんとかするための仕様がPKCE。

どうやるか?

  1. クライアントは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)))
  2. サーバーは認可コードをレスポンスする
    • このとき発行した認可コードと、クライアントが送信したcode_challenge、code_challenge_methodの値を紐づけて保持しておく
  3. クライアントはトークンリクエストの際に、認可リクエストの時に生成したcode_verifierをパラメーターを含める
  4. サーバーはトークンリクエストに含まれる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"をスキップしてるっぽいので、多分そういうことなんだろうな、と思う。(多分・・・)