デジタル認証アプリで学ぶOIDCの脆弱性対策

OpenID Connect(OIDC)をデジタル認証アプリを題材に説明してみようと思います。
また、デジタル認証アプリを使用したアプリケーション開発の事例が少なかったので、これからデジタル認証アプリを利用する方の参考にもなれたらと思います。

## 前提

- デジタル認証アプリの認証方式を採用しています
- アーキテクチャはネイティブアプリとBFFの構成としています
- OAuth2.0の認可コードフローに則っています

## 基礎知識

### デジタル認証アプリとは

> 「デジタル認証アプリ」は、マイナンバーカードを使った認証や署名を、安全に・簡単にするための、デジタル庁が提供するアプリです。行政機関や民間事業者は、デジタル認証アプリと連携するAPIを活用することで、マイナンバーカードを使った本人確認・認証や電子申請書類への署名機能を簡単に組み込むことができます。

（[行政機関等・民間事業者向け実装ガイドライン](https://developers.digital.go.jp/documents/auth-and-sign/implement-guideline/) より引用）

### ワ方式とは

> マイナンバーカードの署名用電子証明書を取得する手法。対象書類がマイナンバーカードに限られますが、ユーザーの操作時間が短い上に、偽造耐性が極めて高い特徴があります。また、電子証明書の失効情報を取得することで、最新の情報かどうかを確認することができます。

（[民間事業者向けデジタル本人確認ガイドラインについて](https://www.digital.go.jp/assets/contents/node/basic_page/field_ref_resources/7e954fba-2ee1-432b-aac8-e5312fb72bb4/7b2753c6/20230328_meeting_administrative_research_working_group_03.pdf)より引用）

### OpenID Connect(OIDC)とは

ここではOIDCの説明は省きます。
よく分からない方は[こちら](https://qiita.com/TakahikoKawasaki/items/498ca08bbfcc341691fe)がおすすめです。

### RPとは

本稿で度々登場するRPについての説明も載せておきます。
RPは「Relying Party」の略称で、OIDC(正確にいうとID Tokenやアイデンティティ情報)を提供される側のことを指します。
平たくいうと、皆さんが開発するサービスのことです。
それとは逆に、OIDCを提供する側(本稿でいうデジタル認証アプリサーバ)をOpenID Provider(OP)と呼びます。

## シーケンス図(全体)

認証開始からアクセストークンでデジタル認証アプリ(OP)のリソースへアクセスできるようになるまでのシーケンス図になります。
[デジタル認証アプリ公式が出しているシーケンス図](https://developers.digital.go.jp/documents/auth-and-sign/implement-guideline/)より具体性を持たせています。

```mermaid
sequenceDiagram
    participant RP_APP as RP<br>ネイティブアプリ
	participant OP_APP as デジタル認証アプリ<br>ネイティブアプリ
    participant RP_BE as RP<br>サーバ
    participant OP_BE as デジタル認証アプリ<br>サーバ

	RP_APP->>RP_BE: 認証開始API
	RP_BE->>RP_BE: state,code_verifier,nonce を発行
	RP_BE->>RP_BE: state,code_verifier,nonce をユーザ(もしくはセッション)と紐付けて保存
	RP_BE->>RP_BE: code_challengeを作成
	Note right of RP_BE: code_challengeはcode_verifierをcode_challenge_method(デジタル認証アプリの場合はSHA256)でハッシュ化したうえで、Base64URL形式にエンコードする
	RP_BE->>RP_BE: 認可エンドポイントにアクセスするURIを生成※state,code_challenge,nonce をクエリパラメータに含める
	RP_BE->>RP_APP: 認可エンドポイントにアクセスするURIをHTTPステータス302で返却

	RP_APP->>OP_APP: 認可エンドポイントにリダイレクト
    OP_APP-->>OP_APP: エンドユーザが認可承諾やICチップの読み取りを行う
    OP_APP->>RP_APP: RPネイティブアプリへリダイレクト with 認可コード,state etc.

	RP_APP->>RP_BE: 結果反映API with 認可コード,state
	RP_BE->>RP_BE: 認証開始APIで保存したstateを取得
	RP_BE->>RP_BE: stateの検証
	Note right of RP_BE: 引数のstateと認証開始APIで保存したstateが一致するか
	RP_BE->>RP_BE: 認証開始APIで保存したcode_verifierを取得
	RP_BE->>OP_BE: トークンエンドポイント叩く with 認可コード,code_verifier
	OP_BE-->>RP_BE: IDトークン,アクセストークン,リフレッシュトークン etc.
	RP_BE->>OP_BE: JWK Set公開エンドポイント叩く
	OP_BE-->>RP_BE: JWKの配列
	RP_BE->>RP_BE: IDトークンの署名検証
	Note right of RP_BE: 1. IDトークンの署名部分をJWKで検証する※JWKの配列の中でもIDトークンのヘッダー部のkidと一致している公開鍵を使用する<br>2. IDトークン内のヘッダー・ペイロードと1が一致するか
	RP_BE->>RP_BE: IDトークンの有効期限検証
	Note right of RP_BE: IDトークンのexpが現在時刻より未来か
	RP_BE->>RP_BE: IDトークンの発行者検証
	Note right of RP_BE: IDトークンのissがデジタル認証アプリのものか
	RP_BE->>RP_BE: IDトークンの受信者検証
	Note right of RP_BE: IDトークンのaudがデジタル認証アプリから払い出されたRPのclient_idと一致するか
	RP_BE->>RP_BE: 認証開始APIで保存したnonceを取得
	RP_BE->>RP_BE: IDトークンのnonce検証
	Note right of RP_BE: IDトークンのnonceと認証開始APIで保存したnonceが一致するか
	alt userInfoは必要に応じて叩く
		RP_BE->>OP_BE: userInfoエンドポイント叩く
		OP_BE-->>RP_BE: ユーザ情報
	end
	RP_BE->>RP_APP: 200OK
```

次の章で、シーケンス図に記載されている検証のうち、初見では理解が難しいであろう脆弱性対策に絞って説明します。

## 脆弱性対策

|                     | 関連するパラメータ                                             | 防ぎたい攻撃                       | 検証者                 | 検証方法                                                                                                                                |
| ------------------- | -------------------------------------------------------------- | ---------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| stateの検証         | ・state                                                        | CSRF                               | RP                     | RPが発行したstateとデジタル認証アプリ(OP)が返却した認可レスポンスのstateが一致するか                                                    |
| nonceの検証         | ・nonce                                                        | 認可コードインジェクション         | RP                     | RPが発行したnonceとデジタル認証アプリ(OP)がトークンエンドポイントで返却したIDトークンのnonceとが一致するか                              |
| code_verifierの検証 | ・code_verifier<br>・code_challenge<br>・code_challenge_method | CSRF<br>認可コードインジェクション | デジタル認証アプリ(OP) | RP(ネイティブアプリ)が認可エンドポイントにアクセスする際に使用したcode_challengeがRP(サーバ)で発行したcode_verifierから生成されたものか |

### stateの検証

CSRF攻撃は、攻撃者が正規ユーザにフィッシングなどの手段で攻撃者が発行した認可エンドポイントにアクセスさせ、正規ユーザに意図しない操作を行わせる攻撃です。
正規ユーザが知らずのうちに攻撃者のリソースにアクセスして、情報を流出させてしまう危険性があります。

```mermaid
sequenceDiagram
    participant RP_USER as RP<br>ネイティブアプリ<br>正規ユーザ
    participant RP_ATT as RP<br>ネイティブアプリ<br>攻撃者
	participant OP_APP as デジタル認証アプリ<br>ネイティブアプリ
    participant RP_BE as RP<br>サーバ
    participant OP_BE as デジタル認証アプリ<br>サーバ

    RP_ATT->>RP_BE: 認証開始API
    RP_BE->>RP_ATT: 認可エンドポイントにアクセスするURIをHTTPステータス302で返却
	RP_ATT->>OP_APP: 認可エンドポイントにリダイレクト
    OP_APP-->>OP_APP: エンドユーザが認可承諾やICチップの読み取りを行う
    OP_APP->>RP_ATT: RPネイティブアプリへリダイレクト with 認可コード etc.
    rect rgba(255, 0, 0, 0.1)
	RP_ATT->>RP_USER: 何らかの手段で結果反映APIのリダイレクトURLを踏ませる
	RP_USER->>RP_BE: 結果反映API with 認可コード
	RP_BE->>OP_BE: トークンエンドポイント叩く with 認可コード
	OP_BE-->>RP_BE: IDトークン,アクセストークン,リフレッシュトークン etc.
	Note right of RP_BE: 正規ユーザが知らずのうちに攻撃者のリソースにアクセスしてしまう
	end
```

上記の攻撃は、結果反映APIで認可コードの発行者を識別できないために発生します。
そしてstateは、認可コードの発行者を識別するためのものです。
具体的には、認証開始APIで発行したstateをユーザ(もしくはセッション)に紐付けて保存しておき、その後の認可レスポンスを受け取った際にstateが一致するか検証することで防ぐことができます。

```mermaid
sequenceDiagram
    participant RP_USER as RP<br>ネイティブアプリ<br>正規ユーザ
    participant RP_ATT as RP<br>ネイティブアプリ<br>攻撃者
	participant OP_APP as デジタル認証アプリ<br>ネイティブアプリ
    participant RP_BE as RP<br>サーバ
    participant OP_BE as デジタル認証アプリ<br>サーバ

    RP_ATT->>RP_BE: 認証開始API
	rect rgba(173, 216, 230, 0.5)
		RP_BE->>RP_BE: stateを発行
		RP_BE->>RP_BE: stateをユーザ(もしくはセッション)と紐付けて保存
		RP_BE->>RP_BE: 認可エンドポイントにアクセスするURIを生成※state をクエリパラメータに含める
	end
    RP_BE->>RP_ATT: 認可エンドポイントにアクセスするURIをHTTPステータス302で返却
	RP_ATT->>OP_APP: 認可エンドポイントにリダイレクト with state
    OP_APP-->>OP_APP: エンドユーザが認可承諾やICチップの読み取りを行う
    OP_APP->>RP_ATT: RPネイティブアプリへリダイレクト with 認可コード,state etc.
    rect rgba(255, 0, 0, 0.1)
		RP_ATT->>RP_USER: 何らかの手段で結果反映APIのリダイレクトURLを踏ませる
		RP_USER->>RP_BE: 結果反映API with 認可コード,state
		rect rgba(173, 216, 230, 0.5)
		RP_BE->>RP_BE: 認証開始APIで保存したstateを取得
		RP_BE->>RP_BE: stateの検証
		Note right of RP_BE: 認可コードを発行した本人ではないので、引数のstateと認証開始APIで保存したstateが一致しない！
		end
	end
```

### nonceの検証

認可コードインジェクションは、攻撃者が認可コードを横取りする攻撃です。
認可コードはアクセストークンを取得するためのものであり、アクセストークンを取得するためには認可コードをトークンエンドポイントに送信する必要があります。
そのため、認可コードを盗み出されると、攻撃者に正規ユーザのリソースにアクセスされる危険性があります。

```mermaid
sequenceDiagram
    participant RP_USER as RP<br>ネイティブアプリ<br>正規ユーザ
    participant RP_ATT as RP<br>ネイティブアプリ<br>攻撃者
	participant OP_APP as デジタル認証アプリ<br>ネイティブアプリ
    participant RP_BE as RP<br>サーバ
    participant OP_BE as デジタル認証アプリ<br>サーバ

    RP_ATT->>RP_BE: 認証開始API
    RP_BE->>RP_ATT: 認可エンドポイントにアクセスするURIをHTTPステータス302で返却
	RP_ATT->>OP_APP: 認可エンドポイントにリダイレクト
    OP_APP-->>OP_APP: エンドユーザが認可承諾やICチップの読み取りを行う
    OP_APP->>RP_ATT: RPネイティブアプリへリダイレクト with 認可コード etc.
    rect rgba(255, 0, 0, 0.1)
	RP_ATT->>RP_USER: 何らかの手段で認可コードを横取りする
	RP_ATT->>RP_BE: 結果反映API with 盗んだ認可コード
	RP_BE->>OP_BE: トークンエンドポイント叩く with 盗んだ認可コード
	OP_BE-->>RP_BE: IDトークン,アクセストークン,リフレッシュトークン etc.
	Note right of RP_BE: 攻撃者が正規ユーザのリソースにアクセスできてしまう
	end
```

上記の攻撃は、認可リクエストの送信者と認可コードの受信者を識別できないために発生します。
そしてnonceは、認可リクエストの送信者と認可コードの受信者の一致を確認するためのものです。
そのため、認証開始APIで発行したnonceをユーザ(もしくはセッション)に紐付けて保存しておき、その後のトークンエンドポイントを叩いた際にIDトークンに含まれるnonceと一致するか検証することで防ぐことができます。

```mermaid
sequenceDiagram
	participant RP_USER as RP<br>ネイティブアプリ<br>正規ユーザ
	participant RP_ATT as RP<br>ネイティブアプリ<br>攻撃者
	participant OP_APP as デジタル認証アプリ<br>ネイティブアプリ
	participant RP_BE as RP<br>サーバ
	participant OP_BE as デジタル認証アプリ<br>サーバ

	RP_ATT->>RP_BE: 認証開始API
	rect rgba(173, 216, 230, 0.5)
		RP_BE->>RP_BE: nonceを発行
		RP_BE->>RP_BE: nonceをユーザ(もしくはセッション)と紐付けて保存
		RP_BE->>RP_BE: 認可エンドポイントにアクセスするURIを生成※nonce をクエリパラメータに含める
	end
	RP_BE->>RP_ATT: 認可エンドポイントにアクセスするURIをHTTPステータス302で返却
	RP_ATT->>OP_APP: 認可エンドポイントにリダイレクト with nonce
	OP_APP-->>OP_APP: エンドユーザが認可承諾やICチップの読み取りを行う
	OP_APP->>RP_ATT: RPネイティブアプリへリダイレクト with 認可コード,state etc.
	rect rgba(255, 0, 0, 0.1)
		RP_ATT->>RP_USER: 何らかの手段で認可コードを横取りする
		RP_ATT->>RP_BE: 結果反映API with 盗んだ認可コード
		RP_BE->>OP_BE: トークンエンドポイント叩く with 盗んだ認可コード
		OP_BE-->>RP_BE: IDトークン,アクセストークン,リフレッシュトークン etc.
		rect rgba(173, 216, 230, 0.5)
			RP_BE->>RP_BE: 認証開始APIで保存したnonceを取得
			RP_BE->>RP_BE: IDトークンのnonce検証
			Note right of RP_BE: IDトークンのnonceと認証開始APIで保存したnonceが一致しない！
		end
	end
```

### code_verifierの検証

code_verifierの検証は、CSRF攻撃と認可コードインジェクションを防ぐために行います。
stateとnonceと異なる点は、検証者がデジタル認証アプリ(OP)であることです。
下記のシーケンス図は認可コードインジェクションの攻撃を想定しています。

```mermaid
sequenceDiagram
	participant RP_USER as RP<br>ネイティブアプリ<br>正規ユーザ
	participant RP_ATT as RP<br>ネイティブアプリ<br>攻撃者
	participant OP_APP as デジタル認証アプリ<br>ネイティブアプリ
	participant RP_BE as RP<br>サーバ
	participant OP_BE as デジタル認証アプリ<br>サーバ

	RP_ATT->>RP_BE: 認証開始API
	rect rgba(173, 216, 230, 0.5)
		RP_BE->>RP_BE: code_verifierを発行
		RP_BE->>RP_BE: code_verifierをユーザ(もしくはセッション)と紐付けて保存
		RP_BE->>RP_BE: code_challengeを作成
		Note right of RP_BE: code_challengeはcode_verifierをcode_challenge_method(デジタル認証アプリの場合はSHA256)でハッシュ化したうえで、Base64URL形式にエンコードする
		RP_BE->>RP_BE: 認可エンドポイントにアクセスするURIを生成※code_challenge をクエリパラメータに含める
	end
	RP_BE->>RP_ATT: 認可エンドポイントにアクセスするURIをHTTPステータス302で返却
	RP_ATT->>OP_APP: 認可エンドポイントにリダイレクト with code_challenge
	OP_APP-->>OP_APP: エンドユーザが認可承諾やICチップの読み取りを行う
	OP_APP->>RP_ATT: RPネイティブアプリへリダイレクト with 認可コード etc.
	rect rgba(255, 0, 0, 0.1)
		RP_ATT->>RP_USER: 何らかの手段で人kコードを横取りする
		RP_ATT->>RP_BE: 結果反映API with 盗んだ認可コード
		rect rgba(173, 216, 230, 0.5)
			RP_BE->>RP_BE: 認証開始APIで保存したcode_verifierを取得
			RP_BE->>OP_BE: トークンエンドポイント叩く with 盗んだ認可コード,code_verifier
			Note right of RP_BE: RP(ネイティブアプリ)が認可エンドポイントにアクセスする際に使用したcode_challengeが、<br>認可リクエスト時に送信したcode_challenge_methodがcode_verifierから生成されたものではないため、<br>デジタル認証アプリ(OP)は認可コードをアクセストークンに交換しない！
		end
	end
```

攻撃者が認可コードを盗み出しても、code_challengeがcode_verifierから生成されたものでないとOP(デジタル認証アプリサーバ)は認可コードをアクセストークンに交換しないため、攻撃を防ぐことができます。

## 参考文献

1. [デジタル認証アプリ公式](https://developers.digital.go.jp/documents/auth-and-sign/implement-guideline/)
2. [手を動かして理解する！OAuth2 / OpenID Connect の基礎と活用](https://www.udemy.com/course/oauth2-openid-connect/?couponCode=NEWYEARCAREERJP)