現在JavaScriptの設定が無効になっています。このページを利用するにはJavaScriptの設定を有効にしてください。2019の19日目の記事です。一覧はこちら(外部リンク)こんにちは。サービス統括本部の都筑(@kazuki229_dev)です。ID連携のサーバーサイド、iOSのSDKの開発などを担当しています。JAPANのシングルサインオンやユーザーの属性情報を取得するID連携の仕組みです。ID連携ではPKCEというOAuth2.0の拡張仕様を実装しました。https://developer.yahoo.co.jp/changelog/2019-12-12-yconnect.htmlそこで、PKCEの基本的な話と、実装の際に調査したことをまとめてみました。PKCEとは?PKCEとは認可コード横取り攻撃の対策として提案された仕様です。Clients)として、2015年9月に発行されています。ID連携にもこのPKCEを導入することで、より安全にクライアントを実装していただけるような選択肢が増えました。認可コード横取り攻撃まずは認可コード横取り攻撃について説明します。Clientに対して発行された認可コードを取得し、それを用いてアクセストークンなどのトークンを不正に取得する攻撃です。クライアントと攻撃者は下記のような状況であることが前提です。攻撃対象のアプリのカスタムURIスキームを悪意あるアプリが登録し起動可能な状態カスタムURIスキームはブラウザーで開くとOSを介してアプリが起動しますが、アプリ実装者はスキームとして任意の値が指定できます。したがって複数のアプリで同じスキームを利用することが可能です。これにより、悪意のあるアプリは被害アプリが起動するはずのタイミングで自分のアプリを起動させるように仕向けます。このような状況だと、認可レスポンスが悪意のあるアプリにわたり、認可コードを取得されてしまいます。ネイティブアプリではクライアント認証ができないため、悪意のあるアプリはトークンエンドポイントへ認可コードをわたすことで、アクセストークンやリフレッシュトークンなどを不正に発行することができてしまいます。この問題を解決する仕様がこのPKCEです。次に、基本的なPKCEの動作を説明します。PKCEでは認可エンドポイントと、トークンエンドポイントに下記のパラメータが追加されています。認可エンドポイントcode_challengecode_challenge_methodトークンエンドポイントcode_verifierこれらのパラメータを利用した認可コードフローを順番に追ってみていきます。クライアントはcode_verifierを生成し保存します。code_challenge_methodで指定した方法でcode_verifierからcode_challengeを生成します。code_challenge_methodにはplainとS256という値が利用可能です。plainの場合はcode_verifierをそのままcode_challengeとして利用し、S256の場合はcode_verifierをSHA256で計算した値をcode_challengeとして利用します。認可リクエストではこのcode_challengeとcode_challenge_methodを送信します。送信されたリクエストが正しければ、認証・認可後に認可サーバーは認可コードを発行します。この際に認可サーバーは、認可コードとひもづけてcode_challengeとcode_challenge_methodを保存します。保存したら認可レスポンスとして認可コードを返却します。クライアントは認可レスポンスをうけとったら、トークンリクエスト時にcode_verifierと認可コードを一緒に送ります。認可サーバーは送られてきた認可コードにひもづけて保存しているcode_challenge_methodの方式でトークンリクエストで送信されたcode_verifierを変換し、その結果が認可コードにひもづけて保存しているcode_challengeと一致することを確認します。以上の確認から、認可リクエストの送信者とトークンリクエストの送信者が一致していることを保証しています。悪意あるアプリは認可コードの取得までは行えますが、一緒に送らなくてはならないcode_verifierの値がわからないので、トークンリクエストに失敗します。以上のような仕組みでPKCEは認可コード横取り攻撃を防ぎます。PKCEに対する攻撃PKCEの仕組みがある上で攻撃者ができうる攻撃は、不正に取得した認可コードに対応するcode_verifierを推測し、トークンリクエストに送信することです。PKCEの仕様上では攻撃者が可能な行動によって、2種類の攻撃者を想定しています。認可レスポンスはカスタムURIスキームの乗っ取りなど、認可リクエストはOSのログなどから観測可能である場合があると例では挙げられています。1の場合は攻撃者はオンライン攻撃のみを行うことができます。2の場合はオフライン攻撃が可能なので、1の場合よりもより強固な対策が必要です。それぞれの攻撃方法についてみていきます。オンライン攻撃オンライン攻撃は一番単純な方法で、認可コードを取得した悪意あるクライアントが、その認可コードとひもづくcode_verifierを推測し、トークンエンドポイントにリクエストをする方法です。この攻撃はcode_challenge_methodの値がplainとS256の両方の場合で防ぐことができます。ヒントのない状態から認可コードと対応するcode_verifierの値を当てなければいけないため総当たり攻撃をするしかありません。答え合わせは実際にトークンエンドポイントへ認可コードとcode_verifierを投げつけてアクセストークンが取得できるかを確認するしかないため、認可コードが有効である期間内に行える試行回数としてはそこまで多くはありません。Section-4.1.2では認可コードの有効期間は最大10分とすることが推奨されています。RECOMMENDED.オフライン攻撃オフライン攻撃はオンライン攻撃より高度な攻撃方法で、認可リクエストと認可レスポンスの両方を観測できる場合にのみ実行できます。この場合、code_challengeとcode_challenge_methodを観測できているため、code_challenge_methodで変換するとcode_challengeとなるような値をトークンエンドポイントへリクエストすることなく、手元で探索することが可能です。code_verifierとなるため、この攻撃は防ぐことはできません。ここまでの攻撃を想定するのであれば、code_challenge_methodをS256とするべきです。オフライン攻撃はオンライン攻撃に比べ格段に総当たり攻撃の試行回数を増やすことができてしまいます。そこでcode_challenge_methodをS256とすることで、code_challengeが観測できたとしてもSHA256を用いて計算されたcode_challengeの値からcode_verifierを探り当てることは困難なため、オフライン攻撃も防ぐことができるというわけです。以上を踏まえて、S256を実装できる場合は必ずcode_challenge_methodをS256として実装するようにしましょう。ここまでで、認可コード横取り攻撃の説明、それを防ぐPKCEの説明、PKCEの仕様に対する攻撃についてみていきました。次にcode_verifierの仕様について詳しくみていきたいと思います。code_verifierとはcode_verifierとはPKCEの鍵となるパラメータで、PKCEはcode_verifierが推測・計算されないという事実を元にしたセキュリティモデルとなっています。Section-4.1では、code_verifierの説明がされています。characters.code_verifierは43文字以上、128文字以下の文字列で、構成文字は[A-Z]/[a-z]/[0-9]/"-"/"."/"_"/"~"となっています。verifier.また、クライアントは推測されないほど十分なエントロピーを持ったcode_verifierを生成すべきで、推奨される生成方法も記載されています。生成したオクテット列をBase64URLエンコードちなみにRFCでよく出てくるオクテットというのは8ビットの事です。Section-7.1を見てみると下記のような記述があります。entropy.クライアントは少なくとも256ビットのエントロピーを満たすcode_verifierを生成すべきとの記述があります。まとめるとPKCEではcode_verifierを推測されてはいけないそのためにcode_verifierは下記の要件を満たす必要があるということです。例としてSwiftでのcode_verifier生成方法を載せておきます。"")さらに調べてみましたいくつか疑問が湧いたので1つずつ解消していきます。code_verifierはなぜ43~128文字なのか?まず43という数字が中途半端に感じ、なぜ43文字以上なのかを調査しました。code_verifierは43文字以上、128文字以下の文字列で、構成文字は[A-Z]/[a-z]/[0-9]/"-"/"."/"_"/"~"となっています。クライアントは少なくとも256ビットのエントロピーを満たすcode_verifierを生成すべきとの記述があります。整理すると、code_verifierは66種類の文字から構成された文字列で、かつ少なくとも256ビットのエントロピーとすべきものです。256ビットのエントロピーとは、すなわち組み合わせが2^256通りになるという事です。計算してみると下記のようになります。66^43というのが成り立つので、code_verifierは最低43文字あればよいと定義されているのです。次になぜ128文字以下となっているのかを調べました。これはメーリングリストを追ってみると記述があります。future.認可サーバーのストレージ負荷を制限するためと、URIが長くなることを防ぐためだそうです。128文字以上にしても特に効果は得られないとのことです。まとめると、下記の理由から43~128文字となっているようです。66種類の文字で構成された文字列のエントロピーを2^256以上にするためには少なくとも43文字以上必要だからcode_verifierの文字列長は128文字以上長くしても特に効果がない32オクテットをBase64URLエンコードしたら43文字になるのか?Base64URLエンコードはBase64エンコードでパディングとして追加した"="を取り除く処理が入るため、本当に32オクテットのビット列をエンコードした結果が43文字以上になるのでしょうか。実際に社内での開発時にコードレビューで質問を受けた際に説明したので、確認方法を記しておきます。Base64URLエンコードのエンコード方式は下記のような順番で行われます。ビット列を6ビットずつのかたまりでわけるビット数が6で割り切れるように0でパディングする6ビットのかたまりを変換表を元に変換していく変換し終わった文字列を4文字ごとのかたまりにわける文字数が4で割り切れるように"="でパディングする"+"を"-"に、"/"を"_"に置き換え、"="を削除するでは1つずつ工程を見ていきます。下記のようなビット列を生成したとします。10001010256個の0と1になりました。ビット数が6で割り切れるように0でパディングする2ビット足りないので、0を2つ付け足します。6ビットのかたまりを変換表を元に変換していくこれを変換表に従って変換していくと、[A-Z]/[a-z]/[0-9]/"+"/"/"のいずれかの文字が43個となります。文字数が4で割り切れるように"="でパディングする1つ足りないので"="でパディングをします。DIo=これでbase64の文字列が完成します。"+"を"-"に、"/"を"_"に置き換え、"="を削除するjS_f15S9JJ_ZNwpwB_LtAX6VrfZQ91p5uLZhSK9TDIo以上の工程を振り返ると、3の工程の時点で必ず43文字は確保できるため、32オクテットをBase64URLエンコードすると必ず43文字になることがわかりました。余談ですが、PKCEのドラフト段階のドキュメントでは、バージョン9まではcode_verifierは42文字以上となっており、その後の指摘でバージョン10に43文字以上と修正されました。Section-7.1では、code_verifierのエントロピーについて書かれており、256ビット以上のエントロピーにすべきとされています。これについては、前述したオンライン攻撃とオフライン攻撃について分けて考えていきます。オンライン攻撃を想定する場合、認可コードが有効な期間内でサーバーにどれだけ問い合わせられるかを考慮してエントロピーを決める必要があります。したがって、認可コードのエントロピーと同じ考慮をすれば問題ないかと思います。10.10には認可コードのエントロピーについての記載があります。http://tools.ietf.org/html/draft-ietf-uta-tls-bcp-07ここでは少なくとも128ビットとなっています。256ビットではありませんでした。256ビットはどこから出てきたのでしょうか?次にオフライン攻撃についてみていきます。こちらは10分間に手元でSHA256の計算をして攻撃をします。メーリングリストの下記言及では、オンライン攻撃は128ビットで十分、オフライン攻撃はSHA256に合わせてcode_verifierも256ビットでよいが、code_challenge_methodの値によってcode_verifierの長さ制限が変わることは実装上の混乱を招くとのことで、長い方に合わせて256ビットとされたようです。S256.まとめPKCEの概要から、細かい疑問の調査結果までをご紹介しました。間違いや補足がありましたらコメントいただけるとうれしく思います。RFCを隅々まで読んで実装するのがベストではありますが、すこしでもこの記事で仕様に理解を深め、実装の手助けになれば幸いです。SDKをOSSとして公開したので、ぜひこちらも利用してフィードバックをいただければと思います。ID連携をよろしくお願いします。ID連携のサーバーサイド・iOSの開発、兼務ではアプリWGで全社の課題解決をしています。JAPANでは情報技術を駆使して人々や社会の課題を一緒に解決していける方を募集しています。詳しくは採用情報をご覧ください。JAPANのサービス開発を支える技術やデザイン、文化を発信しています。Reserved.このページの先頭へ。