これは ビットバンク株式会社 Advent Calendar 2020 の 11 日目の記事です。
はじめに
こんにちは。ビットバンクでセキュリティを担当している pumpkin_head と申します。
今回は、私が個人的興味のある認証認可のお話をしたいと思います。
突然ですが、皆さん OAuth・OpenID Connect をご存じでしょうか?
一言で言えば、認可・認証連携の標準技術仕様です。
身近なところだと、アプリケーションやサービスのデータやアカウントを連携しようとすると
「◯◯ アプリケーションが以下の権限を求めています。許可しますか?」や「◯◯ アカウントでログイン」
という画面を目にしませんか? アレに使われている技術仕様です。
その OAuth/OIDC ですが、ここ数年、追加仕様の策定が盛んに行われており、その中心が英国のオープンバンキング界隈からの要請で検討されている FAPI(Financal-grade API)という金融 API 向けのセキュリティ関連要求事項です。
今回は、OAuth/OIDC の基本〜FAPI の全体像について、セキュリティの話を中心に紹介したいと思います。
OAuth 入門
OAuth とは、権限の認可を行うための標準仕様で、RFC6749で規定されている OAuth2.0 が現在の最新仕様です。
まず、OAuth における認可の流れを、例を用いて説明します。
今、あなたは「最近、運動不足だな」と思い、スマートフォンにフィットネスアプリケーション A をインストールし運動の計画を立てたとします。そして、忘れないようカレンダーサービス B に運動の日時を登録したいと考えます。
大前提として、アプリケーション A とサービス B は別モノですから、アプリケーション A が勝手にサービス B のカレンダーにデータを登録することはできません。そこで、アプリケーション A はサービス B のカレンダー登録の許可をとった上で、データ登録することにします。
許可をとる相手は、カレンダーサービス B の所有者(サービスそのものではなく、カレンダーに登録された情報の所有者という意味)である"あなた"です。
以下のような流れでデータ登録するしくみを考えます。
(表現を簡単にするため、役所の窓口業務にたとえて説明します)
ステップ | ステップ内容 |
---|---|
ⅰ | アプリケーション A はサービス B の許可申請窓口に「カレンダー登録権限をください」と認可の要求をする |
ⅱ | サービス B は、サービス B の所有者であるあなたに「アプリケーション A にカレンダー登録権限を与えてよい?」と確認 |
ⅲ | あなたが承認すると、サービス B はアプリケーション A に対して整理券を払い出して「この整理券を許可証発行窓口で許可証に交換してください」と返す |
ⅳ | アプリケーション A は、サービス B の許可証発行窓口に、整理番号を渡して許可証発行の要求する |
ⅴ | サービス B は、アプリケーション A に許可証を発行する |
ⅵ | アプリケーション A は、サービス B のカレンダー登録窓口に許可証を渡してカレンダー登録をリクエストする |
ⅶ | サービス B は、許可証が本物かどうか、同じサービス B の発行済許可証確認窓口に確認する |
ⅷ | 許可証が正当なものだと確認できたら、アプリケーション A からのリクエスト内容に従って、カレンダー登録させる |
このステップ ⅰ,ⅲ〜ⅴ の「認可要求と許可証のやりとりの方法」を標準化したものが OAuthです。
OAuth は認可連携の標準仕様ですので、ステップ ⅱ の認可処理は OAuth には含まれません。
全体像
いま説明した内容を OAuth の仕様・用語で図示すると、以下のようなフローになります。
登場するオブジェクト
オブジェクト | 具体例 |
---|---|
リソースオーナー | アプリケーション A とサービス B の所有者であるあなた |
アプリケーション A(Client App) | スマートフォンのネイティブアプリケーション、またはブラウザで動く JavaScript |
アプリケーション A(Client) | アプリケーション A のサーバ(アプリケーション A がネイティブアプリケーションのみで完結する場合はアプリA(Cient App) と同じスマートフォンのネイティブアプリケーション) |
サービス B 認可画面 | サービスB 認可サーバー が、ユーザーに認可の承認/拒否を求める画面(アプリケーション A の実装次第だが、アプリケーション A(Client App)内蔵/スマートフォン標準のブラウザで表示) |
サービス B 認可サーバ | サービス B の認可サービスのサーバ |
サービス B リソースサーバ | サービス B のカレンダーサービスのサーバ(サービス B の実装次第では、サービスB 認可サーバー と同一の場合もあり) |
補足1: アプリケーション A は、スマートフォン上で動くネイティブアプリケーションと SaaS サービスが連携している想定で、 アプリA(Client App)
と アプリA(Client)
の2つに分けています。
ネイティブアプリケーションのみの場合は、アプリA(Client)
=アプリA(Client App)
(スマートフォン上で完結)とお考えください。
補足2: サービス B において、本来のカレンダーサービスと認可サービスは機能的に別モノですので、本来のサービス用のサーバをリソースサーバー
、
認可サービス用のサーバを認可サーバー
として分けているものとお考えください。
認可フロー
フロー | フロー内容 |
---|---|
1 | アプリA(Client App) がリソースオーナー に対し、サービス B にアクセス権を要求するかを確認(リソースオーナーは確認に対して、Yes を選択) |
2 | アプリA(Client App) から、サービスB 認可サーバー の認可エンドポイントに、認可リクエスト |
3 | サービスB 認可サーバー から、認可画面を返却 |
4 | アプリA(Client App) で認可画面をリソースオーナー に表示 |
5 | 認可リクエストを承認し、サービスB 認可サーバー の認可決定エンドポイントに結果を返却 |
6 | サービスB 認可サーバー から アプリA(Client App) に認可レスポンスを返却(認可レスポンス内に認可コードが含まれている) |
7 | 認可レスポンスがリダイレクトされ、アプリA(Client) が認可コードを取得 |
8 | アプリA(Client) がサービスB 認可サーバー のトークンエンドポイントに、トークンリクエスト(認可コードをリクエストに含めている) |
9 | サービスB 認可サーバー のトークンエンドポイントはアクセストークンを発行し、アプリA(Client) がアクセストークンを取得 |
10 | アプリA(Client) からサービスB リソースサーバー へ、リソース要求リクエスト(アクセストークンをリクエストに含めている) |
11 | サービスB リソースサーバー はサービスB 認可サーバー のイントロスペクションエンドポイントへ、アクセストークンの情報を要求 |
12 | サービスB 認可サーバー は当該トークンの認可情報をサービスB リソースサーバー に返却 |
13 | サービスB リソースサーバー は、アクセストークンを検証し問題がなければ、アプリA(Client) に要求リソースを返却 |
フロー 2〜3, 6〜9 までが OAuth の認可フロー、フロー 10 以降は、リソース要求・取得のフローとなり、OAuth のスコープ外です。
正確には、フロー11,12 でアクセストークンの検証のために呼び出す認可サーバのエンドポイントなどはOAuth の仕様として定義されていますが、アクセストークンを受け取った後の処理は基本的にサービス B の実装次第となります。
細かい仕様はまだありますが、タイトルにあるとおり「入門」ということでこのあたりにとどめます。
OpenID Connect 入門
OAuth と並んで取り上げられるのが、OpenID Connect です。
一言で言えば、OpenID Connect は OAuth で認証情報を扱えるように定義された拡張仕様です。
どう拡張されたのかというと、認可サーバから返されるレスポンスの種類(response_type
)を拡張し、ID トークンという認証情報(id_token
)を返せるようにしたのです。
ID トークン
OAuth では認可情報をアクセストークンとして表現されていましたが、OIDC で追加された認証情報は ID トークンと呼ばれます。
ID トークンは JWT 形式(JSON Web Token 、通称ジョット)で表現されます。
クレームと呼ばれるユーザー認証結果に関する情報を複数まとめたものを BASE64URL エンコードし署名を付与した JWS(JSON Web Signature)、または、その JWS に暗号化を施した JWE(JSON Web Encryption)が ID トークン(JWT)です。
実態はいずれも、BASE64URL エンコードされた文字列です。
以下のような構造になっています。
上記からもわかるように ID トークンは、以下いずれかの構造になります。
形式 | 構造 | 説明 |
---|---|---|
JWS | ヘッダー .ペイロード(クレーム) .署名 |
署名付き JSON |
JWE | ヘッダー .Encrypted Key .初期ベクター .暗号文 .認証タグ |
暗号化済み署名付き JSON |
ここでは、JWS 形式の ID トークンの中身をもう少し具体的に見ていきましょう。
ヘッダ:
{
"kid":"k2bdc",
"alg": "HS256"
}
内容としては、Key ID(kid)と、署名アルゴリズム(alg)が指定されています。
ペイロード(クレーム):
{
"sub": "248289761001",
"name": "Jane Doe",
"given_name": "Jane",
"family_name": "Doe",
"preferred_username": "j.doe",
"email": "janedoe@example.com",
"picture": "http://example.com/janedoe/me.jpg"
}
JWS の定義的にはペイロードと呼ばれる部分ですが、ID トークンにおけるその内容は、クレームと呼ばれる認証対象のユーザー名や ID トークンの有効期限などの集合になります。
これが実質的な認証情報に該当します。
署名:
上記のヘッダー
.ペイロード(クレーム)
を base64Url エンコードすると、以下のようになります。
eyJraWQiOiJrMmJkYyIsImFsZyI6IkhTMjU2In0.
eyJzdWIiOiIyNDgyODk3NjEwMDEiLCJuYW1lIjoiSmFuZSBEb2UiLCJnaXZlbl9u
YW1lIjoiSmFuZSIsImZhbWlseV9uYW1lIjoiRG9lIiwicHJlZmVycmVkX3VzZXJu
YW1lIjoiai5kb2UiLCJlbWFpbCI6ImphbmVkb2VAZXhhbXBsZS5jb20iLCJwaWN0
dXJlIjoiaHR0cDovL2V4YW1wbGUuY29tL2phbmVkb2UvbWUuanBnIn0
ヘッダの署名アルゴリズム(alg)で指定されている HS256
で、上記エンコード後の文字列の署名を作成します。
HtPXqlUCNh3sxIoFvh5Y2_K5PmksMFvMS-1dXd_7CzM
JWT(JWS)は ヘッダー
.クレーム
.署名
という構成ですので、生成される ID トークンは以下のようになります。
eyJraWQiOiJrMmJkYyIsImFsZyI6IkhTMjU2In0.
eyJzdWIiOiIyNDgyODk3NjEwMDEiLCJuYW1lIjoiSmFuZSBEb2UiLCJnaXZlbl9u
YW1lIjoiSmFuZSIsImZhbWlseV9uYW1lIjoiRG9lIiwicHJlZmVycmVkX3VzZXJu
YW1lIjoiai5kb2UiLCJlbWFpbCI6ImphbmVkb2VAZXhhbXBsZS5jb20iLCJwaWN0
dXJlIjoiaHR0cDovL2V4YW1wbGUuY29tL2phbmVkb2UvbWUuanBnIn0.
HtPXqlUCNh3sxIoFvh5Y2_K5PmksMFvMS-1dXd_7CzM
詳細は割愛しますが、この JWS 形式のものを暗号化すると、JWE 形式の ID トークンになります。
JWS のエンコード/デコードサイトなどで実際に生成してみるとイメージしやすいかもしれません。
認可レスポンスのバリエーション
OIDC の仕様において、OAuth からもう一つ大きく変わったことがあります。
OAuth では、認可リクエストをした際に返却される認可レスポンスは、認可コード(code
)かアクセストークン(token
)のどちらかでした。
OIDC では、code
, token
, id_token
の任意に組み合わせと再定義されました。
response_type の変更内容
仕様 | response_type の取りうる値 |
パターン数 |
---|---|---|
OAuth | code , token のいずれか1つ |
2 |
OIDC | code , token , id_token , none(無し) の任意のリスト |
8 |
そのため、OAuth に比べ、OIDC は認可サーバから返却される内容のパターンが複雑です。
また、認可リクエスト時に指定するscope
パラメータというものがあるのですが、その値によっても認可エンドポイントとトークンエンドポイントから返却されるものが変わります。
レスポンスのパターン別に、各エンドポイントで返却されるものを整理した表が以下になります。
パターンは増えますが、OAuth の認可フローに従ってid_token
が、認可サーバから払い出されるという基本的なフローは変わりません。
OAuth と違うのは、フロー3〜5で、ユーザーに許可の承認を求めるのではなく、ユーザーの認証を行います。その際の認証方法は、OIDC のスコープ外となっており、認可サーバの実装に委ねられています。
また、OIDC では認証情報も扱うため、登場するオブジェクトの認可サーバー
などの呼び方も変わります。
OAuth/OIDC でのオブジェクト名称
OAuth | OIDC |
---|---|
認可サーバ | OP (OpenID Provider) |
クライアント | RP (Relying Party) |
OIDC の概要は以上です。
認証リクエストのパラメータを JWT で指定できるようになったり、ユーザー情報を問い合わせるエンドポイントが増えたりと細かい追加仕様はありますが、ここでは割愛します。
FAPI(Financial-grade API)とは
現在、銀行のオープン API が推進されています。つまり、銀行が 3rd パーティに対して金融データの利用を認めることによる、利便性向上や競争の活発化などを図る動きが広まっています。
英国は、この銀行オープン API 分野で世界をリードしており、その推進は政府主導で行われています。実際に、 Open Banking Implementation Entity (OBIE) という団体が設立され、銀行 API の共通化の議論が行われています。
OBIE では、API 実装の技術仕様として OAuth/OIDC を採用。さらに、標準の OAuth/OIDC よりも高いセキュリティを求め、 Financial-grade API (FAPI、通称ファピ)を策定しました。
FAPI は全 5 パートで構成されており、実際の仕様検討作業は OpenID Foundation の ワーキンググループ によって行われています。
現在は Part1,Part2 が公開され、その内容は、OAuth/OIDC に対するセキュリティ要求事項群となっています。
Part | Title |
---|---|
Part1 | Read Only API Security Profile |
Part2 | Read & Write API Security Profile |
Part3 | Open Data API |
Part4 | Read Only API |
Part5 | Read & Write API |
FAPI でのセキュリティ要求事項
FAPI では、暗号化アルゴリズムの指定や TLS バージョン、HTTP ヘッダなど実装上のセキュリティ要求事項が列挙されています。
細かいもの挟まざまありますが、今回はその中から比較的大きなトピックを取り上げ、FAPI で求められるセキュリティ対策の概要を紹介します。
LoA3 以上のユーザー認証(ユーザー詐称の対策)
ユーザー認証の仕様は含まない OAuth/OIDC ですが、FAPI Part1 ではX.1254の LoA2 以上、Part2 では LoA3 以上のユーザー認証であることが求められます。
LoA は Level of Assurance の略で、ユーザー認証の確からしさをどこまで保証するかのレベルを表したものです。
本人確認の確からしさをあげようとすれば確認プロセスのコストが掛かります。そのため、本人でなかった(詐称されていた)場合のリスクに応じて、LoA のレベルを決定する(=認証の仕様を決定する)、という判断基準として使われます。
それぞれの認証保証レベルは、おおむね以下のようなイメージです。
Level | 概要 |
---|---|
LoA1 | ユーザー自身が登録したユーザー名・パスワードを使った認証。本人確認を必要としない Web サイトなど。 |
LoA2 | ユーザーの身元情報をもとに安全なプロトコルを使った認証。本人確認書類で本人確認を必要とする。 |
LoA3 | ユーザーの身元情報の検証、多要素認証(複数の本人確認情報)を必要とし、暗号化保護されたプロトコルを使った認証。本人確認書類の発行機関への問い合わせ確認を必要とする。 |
LoA4 | LoA3 に加え、対面の身元証明が必要かつ、否認防止のための本人生体情報などの登録が必要。 |
Detached Signature(認可レスポンス改ざんの対策 - その1)
FAPI Part2 では、response_type
パラメータの値は code id_token
もしくは code id_token token
でなければなりません。
これは、ID トークンに Detached Signature として用いるためです。
Detached Signature とは、認可レスポンスパラメータのstate
のハッシュ値s_hash
を埋め込んだ ID トークンです。
ID トークンは署名されるため、レスポンスを受け取ったクライアント側で検証することで、認可レスポンスが改ざんされていないことを確認できます。
そのため、ID トークンを必要としていなくても、ID トークンの発行しなければならないというものです。
JARM(認可レスポンス改ざんの対策 - その2)
正式名称は JWT Secured Authorization Response Mode for OAuth 2.0(JARM) です。
この仕様は、認可リクエスト時の response_mode
パラメータの値として、新たに以下 4 つを指定できるようにします。
これにより、認可レスポンスのパラメータ群を 1 つのJWTとして返却できます。
response_mode | 挙動 |
---|---|
query.jwt | URL のクエリ部に JWT として格納して返却 |
fragment.jwt | URL のフラグメント部に JWT として格納して返却 |
form_post.jwt | HTTP レスポンスボディ内に JWT として格納して返却 |
jwt | URL のクエリ部/フラグメント部を適宜判断し、JWT として格納して返却 |
FAPI Part2 では、この JARM 形式の認可レスポンスを返すことが可能になります(必須ではない)。
これにより、認可レスポンスパラメータに署名付与・暗号化できることになるので、送信者認証・リプレイ攻撃回避や資格情報の漏洩への保護になります。
そのため、JARM が使われている(上記4つのうちいずれかの response_mode
が指定されている)場合には、認可レスポンスの改ざん検知ができるため、1つ前で紹介した Detached Signature が必要がありません。
よって、response_type
パラメータに id_token
を含めなくてもよいことになっています。
PKCE(ユーザーエージェント乗っ取り/認可コード窃取の対策)
OAuth では認可コードが発行された際、ユーザーのデバイス内の悪意あるアプリケーションが、認可コードを横取りして勝手にアクセストークンを発行する認可コード横取り攻撃というものがあります。
この攻撃への対策が、PKCE (Proof Key for Code Exchange by OAuth Public Clients) (通称ピクシー)です。
FAPI Part1,2 では、この PKCE の実装が必須となっています。
PKCE は、
(code_verifier)
-(input)-> 【計算方法:(code_challenge_method)】
-(output)-> (code_challenge)
という関係の3つのパラメータを使用して、フローの途中で以下のような処理をします。
- 認可リクエスト時に、クライアントアプリケーションは認可サーバへ
code_challenge
とcode_challenge_method
を送っておきます。 - トークンリクエスト時に、
code_verifier
を送り、認可サーバは上記の式を使ってcode_challenge
を算出し、認可リクエスト時に受け取った値と一致するか確認します。
これにより、認可コードをリクエストしたクライアント以外による認可コードの不正利用を防ぐことができます。
リダイレクトの https スキーム必須(ユーザーエージェント乗っ取りの対策)
フロー7で、認可サーバ B からアプリケーション A(Client)に返却される認可レスポンスの内容は、基本的にはリダイレクトによってアプリケーション A(Client App)を経由して渡されます。
その際、ネイティブアプリケーションで完結するようなアプリケーションであれば、カスタムスキームを使ってスマートフォン内のアプリケーションにリダイレクトされます。
FAPI Part1 では、このリダイレクトスキームをhttps
しか許可しません。
そのため、モバイルデバイス用 OS に用意されているカスタム URL スキームなどで、アプリケーション固有スキームではなく、アプリケーション固有 URL(例:https://app-a.me/
)からアプリケーションを起動するよう設定する必要があります。
クライアント認証(クライアント詐称の対策)
認可サーバに接続するクライアントには、Conficential と Public の2種類の分類が存在します。
種別 | 具体例 |
---|---|
Conficential | 資格情報へのアクセス制限がされた安全なサーバ上のアプリケーション |
Public | スマートフォンのネイティブアプリケーション、ブラウザベースアプリケーションなど |
FAPI Part2 では、認可サーバのトークンエンドポイントにおいて、Conficential クライアントを以下のいずれかの方法でクライアント認証する必要があります。
- private_key_jwt
- 公開鍵暗号を使ったクライアント認証方式です。
- クライアント認証用クレーム群(JSON)を、クライアントの秘密鍵で署名して JWT(JWS)を作成します。この JWT はクライアントアサーションと呼ばれます。
- クライアントアサーションを認可サーバに渡し、認可サーバがクライアントの公開鍵を使いクライアントアサーションを検証することで、クライアントの詐称を防ぎます。
- MTLS
- 認可サーバにクライアント証明書のメタデータを事前登録しておきます。
- クライアントがトークンエンドポイントに通信してきた際に TLS 通信確立のために渡してくるクライアント証明書と、事前に登録されたメタデーがを一致することを確認することで、クライアントの詐称を防ぎます。
Holder of Key(トークン不正利用の対策)
OAuth のトークンは Bearer Token(ベアラトークン)です。
Bearer Token は、"誰の"トークンかという所持者情報を持たないため、本来の所持者以外の第三者でもトークンを使用できるという課題がありました。
Holer-of-Key Token は、この課題を解決するためのトークン所持者証明のメカニズムです。
FAPI(Part2)では、指定されている2つの方法のうちいずれかでトークン送信者の認証をする必要があります。
-
MTLS(Mutual-TLS Client Authentication and Certificate-Bound Access Tokens)
- クライアント - 認可サーバ間で TLS 通信を確立する際に、クライアント認証を行い、認可サーバはそこで使ったクライアント証明書と、払い出すトークンを紐付けて記憶しておきます。
- リソースサーバがトークンを検証する際に、接続してきたクライアントのクライアント証明書と、トークン紐付いたクライアント証明書が一致するか確認をすることでトークンの不正利用を防ぐことができます。
- もともと TLS 通信に用いているクライアント認証を利用するため、後述の OAUTB に比べれば、適用のハードルは容易です。
-
- トークン送信時に、トークンバインディング ID をトークンと一緒に送付し、受け取った側が検証することでトークンの不正利用を防ぎます。
- ただし、トークンバインディング ID は拡張仕様であるため、トークンを送受信するすべてのサービス(認可サーバおよびリソースサーバ)が本仕様に対応している必要があり、適用のハードルが高いです。
終わりに
今回は、OAuth/OIDC と FAPI の概要についてセキュリティの話を中心に紹介させていただきましたが、いかがでしたでしょうか。
私個人の解釈も含まれるため、公式の見解の違いなどがありましてもご容赦ください。
本記事執筆時点(2020.11)の最新動向としては、2020.12末で FIAPI 1.0というかたちで一度ファイナライズされて、新たに FAPI 2.0として仕様検討が行われていくようです。
もっと詳しく知りたいという方は、RFC や IETF のドラフトを読むことをお勧めします。
また、Authlete 社代表取締役の川崎さんがたいへん丁寧な記事を投稿されているので、併せて読むと理解の助けになると思います。
銀行のオープン API が広く普及し Fintech サービスがより活発化していく上で、セキュアなプロトコルは欠かせません。
日本でもさらに議論が活発化していくと嬉しいですね。