CTAP2 お勉強メモ#6 - Use Case

#はじめに
これはCTAPのお勉強をしたメモです。
**WebAuthn(ウェブオースン)ではなく、CTAP(シータップ)であります。**

随時修正します。

- [CTAP2 お勉強メモ#1](https://qiita.com/gebo/items/d2ffbd4fcf7d75e21b63)
- [CTAP2 お勉強メモ#2](https://qiita.com/gebo/items/e0bd197d607312dcf4fb)
- [CTAP2 お勉強メモ#3](https://qiita.com/gebo/items/2cfc3202cd88a59b24ba)
- [CTAP2 お勉強メモ#4](https://qiita.com/gebo/items/634aa39b0e08d8258682)
- [CTAP2 お勉強メモ#5](https://qiita.com/gebo/items/84454583daeaf6711fd0)

最後にYubikeyを使った認証のユースケース検討とrk,uvオプションのまとめをしたいと思います。

#目次

1. [前提](https://qiita.com/gebo/items/cfc6ceb1c7f9aa5fdad6#1-%E5%89%8D%E6%8F%90)
2. [ユースケース1](https://qiita.com/gebo/items/cfc6ceb1c7f9aa5fdad6#%E3%83%A6%E3%83%BC%E3%82%B9%E3%82%B1%E3%83%BC%E3%82%B91)
3. [ユースケース2](https://qiita.com/gebo/items/cfc6ceb1c7f9aa5fdad6#%E3%83%A6%E3%83%BC%E3%82%B9%E3%82%B1%E3%83%BC%E3%82%B92)
4. [ユースケース3](https://qiita.com/gebo/items/cfc6ceb1c7f9aa5fdad6#%E3%83%A6%E3%83%BC%E3%82%B9%E3%82%B1%E3%83%BC%E3%82%B93)
5. [rk,uvについて](https://qiita.com/gebo/items/cfc6ceb1c7f9aa5fdad6#5-rkuv%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)

#1. 前提

- **業務システム**の想定です。(大事なところ)
- ユーザー個人に業務システム専用Yubikeyが支給され、Yubikeyを使ってシステム認証をします。
- 記憶、所持、生体のいずれかの2つを組み合わせた2要素認証とし、Yubikeyは所持要素とします。
- 実際のシステムはWebシステム、または、Client-Serverシステムの想定ですが、シンプルに表現するためにActorをUser/Yubkey/Client/Databaseだけにしています。
- rpはシステムのIDという意味なので、常に同じものを使います。
1ユーザー=１Yubikey。Yubikeyのユーザー間共有はしない。


#ユースケース1

<details><summary>**登録時のフロー**</summary><div>

![usecase_01_create.png](https://qiita-image-store.s3.amazonaws.com/0/252291/5063b431-bcbc-d3e6-e560-8110ecfa5e1d.png)

</div></details>

<details><summary>**認証時のフロー**</summary><div>

![usecase_01_authentication.png](https://qiita-image-store.s3.amazonaws.com/0/252291/ff2f3020-8ddd-0234-b7e9-640a353e0821.png)

</div></details>

**2要素認証**
- 1要素目=パスワード(記憶)
- 2要素目=**Yubikey(所持)**

Yubikeyを2要素目にした2要素認証ユースケースです。
2要素認証では1要素目を生体とするのは、他人受け入れ率が0にならない、という理由でNGなので、1要素目は記憶にします。

**Yubikeyの使い方**
[#2](https://qiita.com/gebo/items/e0bd197d607312dcf4fb)と[#3](https://qiita.com/gebo/items/2cfc3202cd88a59b24ba)で勉強したケースです。
- 登録：YubikeyからCredentialIDとPublicKeyを取得、保存
- 認証：YubikeyにrpidとCredentialIDを送り付ける

Client側のDatabaseではUserIDをキーにしてCredentialIDとPublicKey（とパスワード）を管理します。

#ユースケース2

<details><summary>**登録時のフロー**</summary><div>

![usecase_02_create.png](https://qiita-image-store.s3.amazonaws.com/0/252291/9fe56b18-c3d0-e657-80a0-003117b49a7d.png)

</div></details>

<details><summary>**認証時のフロー**</summary><div>

![usecase_02_authentication.png](https://qiita-image-store.s3.amazonaws.com/0/252291/56c39e61-be2c-5482-b6de-9c6dbf437392.png)

</div></details>

**2要素認証**
- 1要素目=**Yubikey(所持)**
- 2要素目=パスワード(記憶) or 顔/指紋(生体)

Yubikeyを1要素目にした2要素認証のユースケースです。
2要素目はパスワード、あるいは 顔/指紋などの生体とすることができます。

**Yubikeyの使い方**
ResidentKey(rk)を使い、CredentialIDを使わない、という部分が前のケースとは異なります。
- 登録=Yubikeyにはユーザー情報を保存。YubikeyからPublicKeyを取得、保存。
- 認証=Yubikeyにはrpidを送り付ける。

~~Yubikey(あるいはCTAP)の仕様上、ResidentKeyを利用するにはPINの設定が必要となります。
本ケースではユーザーが特定されていない1要素目にYubikeyを使いたいので、PINをユーザーに紐づけるわけにはいきません。したがって、~~PINはシステム固定とします。（プログラムで内部的にpinAuthを生成し、Yubikeyに送り付けます）
というわけで、Client側のDatabaseではUserIDをキーにしてPublicKey（とパスワード）を管理します。

#ユースケース3

<details><summary>**登録時のフロー**</summary><div>

![usecase_03_create.png](https://qiita-image-store.s3.amazonaws.com/0/252291/3e5c8975-aa4d-9a5e-5a26-e8a3dfedeedd.png)

</div></details>

<details><summary>**認証時のフロー**</summary><div>

![usecase_03_authentication.png](https://qiita-image-store.s3.amazonaws.com/0/252291/74139410-2b93-4ea6-d7c9-ae84077433e8.png)

</div></details>

**2要素認証**
- PIN(記憶) & Yubikey(所持)

いわゆるパスワードレスです。
**(PINはパスワードではないのであります)**

所持しているYubikeyのPINが通れば(pinTokenがGETできれば)記憶要素が満たされ、YubikeyからGETしたAssertionのVerifyで所持要素が満たされます。
明確に2段階になっていないのですが、2要素ではあると考えます。

本ケースはサーバーにPINを持たないため、システム管理者にとっての情報漏洩に対するリスクは他ケースに比べて低くなります。

#5. rk,uvについて

authenticatorMakeCredential(0x01) コマンド、authenticatorGetAssertion(0x02) コマンドで出てくる`rk`、`uv`オプションについて、やっとわかってきたので、ここでまとめたいと思います。

####authenticatorMakeCredential(0x01) コマンド
- options(0x07)で`rk`,`uv`を指定する(true/false)
- 未指定の場合falseとなる
- YubikeyにPINが設定されていない＆PINを指定しないとき
	- rk=true ⇒ そのまま正常終了。ユーザー情報は保存される。
	- uv=true ⇒ 正常終了するが、[AuthData.flags.Bit 2: User Verified (UV)](https://qiita.com/gebo/items/2cfc3202cd88a59b24ba#authdataauthenticator-data) はFalseとなる。つまり無視される。
- YubikeyにPINが設定されている＆PINを指定しないとき
	- rk=true ⇒ PIN指定しろと怒られる。0x36 CTAP2_ERR_PIN_REQUIRED
	- uv=true ⇒ PIN指定しろと怒られる。0x36 CTAP2_ERR_PIN_REQUIRED

####authenticatorGetAssertion(0x02) コマンド
- options(0x07)で`uv`を指定する(true/false)
- 未指定の場合falseとなる

####rkとは何なのか？
- **ResidentKey**である。
- ResidentKeyとは何なのか? こちらのサイトでわかりやすい解説があります⇒[Webauthn における ResidentKey と UserVerification について](https://blog.haniyama.com/2018/10/19/webauthn-residentkey/)
- ざっくりいうと、Yubikey内の保存領域にuser情報を保存することである。
- userとは[これ](https://qiita.com/gebo/items/2cfc3202cd88a59b24ba#user)のこと。
- Yubikeyというデバイスの中に保存することになるので、たくさんは入れられないっぽい。
- 今どのくらい保存されているとか、あとどのくらい保存できるとかは見れません。
- 保存できる上限を超えたときの挙動と対応はこちらのサイトで分かりやすい解説があります⇒[まんぷくになった YubiKey を YubiKey Manager でリセットしたメモ](https://ritou.hatenablog.com/entry/2018/11/02/021028)
- そもそもResidentKeyの機能があるかどうかはauthenticatorGetInfoコマンド(0x04)で分かる⇒[#2のメモ](https://qiita.com/gebo/items/e0bd197d607312dcf4fb#%E5%BF%9C%E7%AD%94%E3%83%91%E3%82%B1%E3%83%83%E3%83%88-1)

####uvとは何なのか？
- **User Verification**である。
- **User Verification**とは何なのか？「ユーザーを認証する」ということ。
- つまり、uv=trueで、**ユーザーが本当にAuthenticatorの所有者かどうか認証する**、ということ。
- Yubikeyでは**PINでユーザー認証する**、ということになります。
- したがって、uv=trueにする、ということは[pinAuth(0x08)](https://qiita.com/gebo/items/2cfc3202cd88a59b24ba#pinauth0x08)が必須、ということになります。

ここまでで、`rk`,`uv`については比較的スッキリ説明がつくのですが、ここからが仕様的に難解なところです。

####rkで保存したuserの取り出し方
ResidentKey(rk)はauthenticatorMakeCredentialで指定します。userがYubikeyに保存されます。**どうやって取り出すのか？**

- authenticatorGetAssertionすると勝手に取れます。
- Assertionの中のpublicKeyCredentialUserEntity(0x04)がそれです。

```:publicKeyCredentialUserEntityがとれたときのAssertion
{
  1: {"id": h'BBF556121B298B05DC4D1312C8A21D46', "type": "public-key"}, 
  2: h'BE17B417F210B91E276D243C3106CE19EF65F194BFB88C370BADC7D6A2B6CFA9040000006F', 
  3: h'304402201F6FBAA0ACA84F7F239B8F10D050495ECFF056DD9BF7E1A8331F6CB1ABB69EA2022028FA65C5D908287AF2E4205E25198801D51A1DBC1840D5CCD7CE8D5F13367098', 
  4: {"id": h'313233343536373800', "name": "user_gebo", "displayName": "user_DISP_gebo"}
}
```

- ただし、上記のようにid,name,displayNameがすべて取れる場合と、そうでない場合があります。**ここがンンン？？？なところ**⇒詳細は[後述](https://qiita.com/gebo/items/cfc6ceb1c7f9aa5fdad6#idnamedisplayname%E3%81%8C%E3%81%99%E3%81%B9%E3%81%A6%E5%8F%96%E3%82%8C%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%A8%E3%81%9D%E3%81%86%E3%81%A7%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88%E3%81%AE%E8%A9%B1)
- ~~また、rk=trueで登録した場合、authenticatorGetAssertionで[allowList](https://qiita.com/gebo/items/634aa39b0e08d8258682#allowlist)にクレデンシャルIDを指定する方法ではAssertionが取れなくなります。 `FIDO_ERR_INVALID_CREDENTIAL (0x22)` となります。~~ ←間違いでした・・・そんなことはありません。
- rk=trueで登録したあと、Asssertionを取りたいのであれば、allowListは指定せずに、uv=trueとするだけでAsssertionがとれます。

####MakeCredential : rkでのuser保存は同一rpで複数可能
- つまり、一つのrp.idに複数のuserを保存できる。
- 保存するときのユーザーのキーはuser.id。このuser.idが違っていると別のユーザーとみなされる。
- 複数のuserを保存する、ということですが、それぞれ別のクレデンシャル(秘密鍵)です。クレデンシャルを共有する、ということではありません。

####GetAssertion : rpに対して複数のuserが登録されている場合、どのような挙動になるのか？
- ~~前述のように、rk=trueで登録した場合、クレデンシャルIDを指定することはできません。~~ まちがい
- ~~~つまり、rp情報だけでAssertionをGETすることになります。~~ 間違い
- rpに対して複数のクレデンシャルが登録ができる、というこであれば、複数のAssertionがとれるのか？ということです。
- **そうです。Assertionが複数個取れる**のです。
- **authenticatorGetNextAssertion (0x08)コマンド**というのがあるのです。

```:Assertionが複数個取れるとき

// ◆authenticatorGetAssertion(0x02) コマンド
// allowListは指定せず、uv=true
{
  1: "gebo7.com", 
  2: h'6B14FC9289B0DD6C6DA60EFEF105C2C5E7A22F7DC855D8207C862002372C1940', 
  5: {"up": false, "uv": true}, 6: h'BD9E1897A09F329D871DFA864CCF6C0F', 
  7: 1
}

// Response(Assertion)
{
  1: {"id": h'CA260112057A29ECE17D35C1EB5E2582', "type": "public-key"}, 
  2: h'BE17B417F210B91E276D243C3106CE19EF65F194BFB88C370BADC7D6A2B6CFA9040000006E', 
  3: h'3045022100DBA12EA384F9CDA425780006B2E86A5413541A3891D50404D139E4F6B32C9B93022033E61CB120EF88380EA1FF9183763766CA666819836E9561C8728255C1BF2D7F', 
  // user_gebo2
  4: {"id": h'31323334353637383900', "name": "user_gebo2", "displayName": "user_DISP_gebo2"}, 
  // ★0x05:numberOfCredentials　これがあると、Assertionがまだある、という意味
  5: 2
}

// authenticatorGetNextAssertion (0x08)コマンド
// パラメタ無し

// Response(Next Assertion)
{
  1: {"id": h'BBF556121B298B05DC4D1312C8A21D46', "type": "public-key"}, 
  2: h'BE17B417F210B91E276D243C3106CE19EF65F194BFB88C370BADC7D6A2B6CFA9040000006F', 
  3: h'304402201F6FBAA0ACA84F7F239B8F10D050495ECFF056DD9BF7E1A8331F6CB1ABB69EA2022028FA65C5D908287AF2E4205E25198801D51A1DBC1840D5CCD7CE8D5F13367098', 
  // user_gebo
  4: {"id": h'313233343536373800', "name": "user_gebo", "displayName": "user_DISP_gebo"}
}
```

####GetAssertion : id,name,displayNameがすべて取れる場合と、そうでない場合の話
**Assertionが1個の場合はuser.idしかとれません。Assertionが複数あるときだけuser.name,user.displayNameが取れます**

```:Assertionが1個の場合
{
  1: {"id": h'5674B3BE66FD4A14485AAE76648D0080', "type": "public-key"}, 
  2: h'0FA85A9CAE4160AFBCC898FC3A43010427EA3C6EC6CC07AA64424B31634C002E0400000071',
  3: h'3045022100AB1FB333AA1F9EE5462F08CEE64112CD701FA6416AF4278F17D822812D3C39210220375DB0A46EFCB6E938BECB6F7E66AB74254786B65A6C33F167B359578FAFEFFC', 
  // user.idしか取れない
  4: {"id": h'6765626F696400'}}
}
```

- これは**userが複数個あったときに画面に表示して選択させる**というシナリオを想定している、と思われます。
- つまり、user名を表示して、どっちでログインするの？と。idだけ表示しても人間は判断できないでしょ、ってこと。
- つまりつまりバグではなく、そのように作られている、と思われます。(あるいはCTAPの仕様？)
- *いやしかし、Assertion1個でもname,displayName返してもいいと思うけど・・・*

#おつかれさまでした
2018年末、FIDO2/WebAuthnが非常にホットですが、あえてCTAPを調べてみました。

CTAP仕様はセキュリティについては徹底的に追及されています。
また、HIDだけでなく、BLE、NFCも定義されており、幅広いシナリオ、ユースケースが想定されています。
それでいて[仕様](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#usb-hid-implementation)は無償で全公開。Yubicoも[GitHubでソース全公開](https://github.com/Yubico/libfido2)。ガラパゴスな日本とは大違いです。

このままFIDO2が世界標準となり、各社からCTAP2のAuthenticator製品が発売され大ブレイクすることを期待します。


#参考
- [Client to Authenticator Protocol (CTAP) Implementation Draft, February 27, 2018](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#usb-hid-implementation)
- [Web Authentication: An API for accessing Public Key Credentials Level 1
W3C Candidate Recommendation, 7 August 2018](https://www.w3.org/TR/webauthn/)
- [Yubico/libfido2](https://github.com/Yubico/libfido2)
- [CBOR playground](http://cbor.me/)