FIDOのPIN認証のフローを理解する

本記事は [Digital Identity技術勉強会 #iddance Advent Calendar 2020](https://qiita.com/advent-calendar/2020/iddance) の3日目の記事です。

# なんの話ですか？

FIDOの話です。
<br>FIDOの話なんですが、WebAuthnっぽい入り口で、中身はCTAPの話です。<br>WebサイトでFIDOキーでログインする時のPINの検証プロトコルがどうなっているのかという話です。

PINの検証プロトコルはCTAPで定義されていて、これがやたらめんどくさくてすぐ迷い込んでしまうので少し整理してみました。

![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/f817381a-6577-b5ab-ca8c-2b6ca0f3214c.png)



**雰囲気理解で書いていますので 間違いなどあれば ご指摘、コメントいただければ嬉しいです**



# 0.人 - ログイン

画面は[WebAuthnのデモサイト](https://webauthn.io/)です。

- ログインします

![06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/9e807dd2-e32d-6e84-c6c1-c1aefda1d2e4.png)

- ブラウザ『FIDOキーを挿入してください』

![03_Insert.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/e1dba6ff-a5c1-be5a-93c8-d7d2662a97ab.png)

- FIDOキーをPCのUSBに刺します

![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/bb491601-c22c-044f-6f16-77e435e367fb.png)

<br/><br/><br/>
**ここからはじまります。**

<br/><br/><br/>

# 1.FIDOキー - パワーON!

FIDOキーが刺さりました。FIDOキーのパワーONです。

### Authenticator KeyAgreementキー生成

- FIDOキーの中でキーペアが生成されます
- **ECDH**という形式の秘密鍵(**a**)と公開鍵(**aG**)です
- このキーを**Authenticator KeyAgreementキー**といいます

### PINトークン生成

- FIDOキーの中でランダムな16byteのデータが生成されます
- このデータを**PINトークン**といいます

![05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/19797ade-4bdc-bc5b-d23e-c905637c7d6a.png)

<br/><br/><br/>

**このPINトークンをブラウザに引き渡すことがPIN認証となり、今回の話のゴールです。**

<br/><br/><br/>


# 2.ブラウザ - 共有鍵を作る

ブラウザはFIDOキーが刺されたことを検知しました。

### Platform KeyAgreementキー生成

- FIDOキーと同じ**ECDHのキーペア**を生成します
  - このキーを**Platform KeyAgreementキー**といいます
  - 秘密鍵(**b**)と公開鍵(**bG**)と表記します

![07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/6b1e42de-9ca7-0967-deca-8289a23d1126.png)




### 共有鍵生成

- FIDOキーから **KeyAgreementキーの公開鍵(aG)** を取り出します
  - CTAPの [authenticatorClientPIN - getKeyAgreement](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorClientPIN) というAPIです

![08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/757f8cf4-56b1-bcf0-14f5-a5a54d57af60.png)




- **b**と**aG**を使って**ECDH鍵共有プロトコル**というおまじないで計算します
- 計算=`SHA-256((baG).x)`
  - このデータを**SharedSecret**(共有鍵)といいます

![09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/161f912d-cb8d-eeff-eafa-b4adf31601e1.png)



# 3.人 - PINを入力する

- ブラウザ『PINを入力してください』
- PINを入力します

![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/e6467b20-6c0c-6c46-dd4f-ad1a8542d1f9.png)



# 4.ブラウザ - PINをバラして暗号化する

PINが入力されました

- PIN文字列をbyte配列にしてからハッシュを取ります
- 先頭の16byteを取り出します
  - `LEFT(SHA-256(PIN),16)`

- 取り出した16byteのデータを暗号化します

  - 暗号化方式はAESです
    - 一言でいうと `AES-256-CBC`
  - 暗号化キーはさっき作った**SharedSecret**です
- このデータを**pinHashEnc**といいます

  - **pinHashEncはPINをバラしたものなので元のPIN文字列にモドすことはできません**

![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/e40f73ad-336b-5abd-f32b-f025396ffc11.png)



- ブラウザは**Platform KeyAgreementキーの公開鍵(bG)**と**pinHashEnc**をFIDOキーに送りつけます

  - CTAPの [authenticatorClientPIN - getPINToken](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorClientPIN) というAPIです

![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/e9edd73d-944b-7125-92cd-1a73fa91b9ad.png)



# 5.FIDOキー - 検証

FIDOキーは送り付けられたデータを検証します

### 共有鍵生成

- ブラウザから送り付けられた **Platform KeyAgreementキーの公開鍵(bG)** を取り出します

- **a**と**bG**を使って**ECDH鍵共有プロトコル**というおまじないで計算します
  - 計算=`SHA-256((abG).x)`
  - **この計算結果が、なんと！ブラウザが作ったSharedSecretと同じになります**

![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/17eb2d9b-f591-bd87-7962-335a44061379.png)

### pinHashEnc生成

- ブラウザと同じ方法でFIDOキーに記録されているPINと先ほど生成したSharedSecretを使って**pinHashEnc**を生成します

![14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/e7ed0a92-2a63-1b20-ae1b-fdbd45c802f8.png)



### 検証

- `ブラウザから送られてきたpinHashEnc` = `FIDOキーが生成したpinHashEnc` ならば**検証OK!**

![15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/c849341b-29a1-7fcd-285e-bface8781bbb.png)



### PINトークン引き渡し

- 一番最初に生成した**PINトークン**を暗号化します
  - 暗号化方式は一言でいうと`AES256-CBC`
  - 暗号化キーはさっき作った**SharedSecret**です
- このデータを**pinTokenEnc**(暗号化されたPINトークン)といいます

![16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/ec662240-05e8-997d-c01b-e4f26e5677df.png)

- **pinTokenEnc**をブラウザに送り返します
  - CTAPの [authenticatorClientPIN - getPINToken](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorClientPIN) の戻り値です

![17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/c18b11e3-4c42-730b-c8df-cd66ba7ab5fa.png)



# 6.ブラウザ - PINトークンをGET

ブラウザは**pinTokenEnc**(暗号化されたPINトークン)をGETしました。

これをブラウザ自身が持つ**SharedSecret**で復号してついに**PINトークン**をGETすることができました。PINトークンは **PIN認証OK** という証明書です。

![18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/d2564a2f-c8eb-7113-dc97-2e2b042b5ab5.png)



- FIDOキーにPINトークンを提示して、認証の証明書(Assertion)の要求をします
  - 単にPINトークンを提示するのではなく**pinAuth**という形式に変換して利用します
  - pinAuth= `LEFT(HMAC-SHA-256(pinToken, clientDataHash), 16)`
  - CTAPの [authenticatorGetAssertion](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetAssertion) というAPIです

![20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/2834635d-6519-5fbe-0002-be7d2383e8ff.png)



# 7.人 - ログイン

- ブラウザ『キーをタッチしてください』
- キーをタッチします

![19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/8d2d313a-a949-0a7e-ad61-39c7bf63f005.png)

- FIDOキーから本人認証の証明書(Assertion)をGETし、Assertionをサーバーで検証してOKであればログインします
  - AssertionはCTAPの [authenticatorGetAssertion](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetAssertion) の戻り値です

![21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/37a799bd-98a0-5afe-60c4-466a4762df63.png)

![22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/d2c8e6dc-b67b-f683-8e37-58b84e05f2dd.png)

<br/><br/><br/>
**おしまい。**

<br/><br/><br/>


# まとめ

- PINを検証するやりとりでサーバーは関与しない
- ブラウザとFIDOキーの間で**PINそのものの送信は行わない**
- ブラウザとFIDOキーの間ではPINを使って暗号化したデータを送るが、**暗号化したデータからPINを復元することはできない**
- 通信には共通の暗号化キー(SharedSecret)を使うが、**SharedSecretを通信で交換しない**
- **SharedSecretはECDH鍵共有プロトコルを使うことでブラウザ/FIDOキーで同じ値を生成する**
- いったんPIN認証したら引き渡される**PINトークン**を使う
- **PINトークンはFIDOを刺した時に毎回生成される**





# おつかれさまでした

このプロトコルを実装したライブラリはちゃんと動いているので 言葉よりソースコードで という方はこちらをご覧ください。

- ctap-hid-fid2(Rust)
  - https://crates.io/crates/ctap-hid-fido2
- CTAPcs(C#)
  - https://github.com/gebogebogebo/CTAPcs





# 参考

- **FIDO**
  - FIDOの仕組み
        - https://fidoalliance.org/fido%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF/?lang=ja
  - FIDO2: WebAuthn & CTAP
        - https://fidoalliance.org/fido2
  - Client to Authenticator Protocol (CTAP)
    Proposed Standard, January 30, 2019
        - https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html
- **SHA-256,AES,HMAC,ECDH(楕円曲線ディフィー・ヘルマン鍵共有)などの暗号について**
  - 暗号技術入門 第3版　秘密の国のアリス
        - https://www.hyuki.com/cr