Help us understand the problem. What is going on with this article?

CTAP2 お勉強メモ#6 - Use Case

はじめに

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

随時修正します。

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

目次

  1. 前提
  2. ユースケース1
  3. ユースケース2
  4. ユースケース3
  5. rk,uvについて

1. 前提

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

ユースケース1

登録時のフロー

usecase_01_create.png

認証時のフロー

usecase_01_authentication.png

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

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

Yubikeyの使い方
#2#3で勉強したケースです。
- 登録:YubikeyからCredentialIDとPublicKeyを取得、保存
- 認証:YubikeyにrpidとCredentialIDを送り付ける

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

ユースケース2

登録時のフロー

usecase_02_create.png

認証時のフロー

usecase_02_authentication.png

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

登録時のフロー

usecase_03_create.png

認証時のフロー

usecase_03_authentication.png

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

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

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

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

5. rk,uvについて

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

authenticatorMakeCredential(0x01) コマンド

  • options(0x07)でrk,uvを指定する(true/false)
  • 未指定の場合falseとなる
  • YubikeyにPINが設定されていない&PINを指定しないとき
    • rk=true ⇒ そのまま正常終了。ユーザー情報は保存される。
    • uv=true ⇒ 正常終了するが、AuthData.flags.Bit 2: User Verified (UV) は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 について
  • ざっくりいうと、Yubikey内の保存領域にuser情報を保存することである。
  • userとはこれのこと。
  • Yubikeyというデバイスの中に保存することになるので、たくさんは入れられないっぽい。
  • 今どのくらい保存されているとか、あとどのくらい保存できるとかは見れません。
  • 保存できる上限を超えたときの挙動と対応はこちらのサイトで分かりやすい解説があります⇒まんぷくになった YubiKey を YubiKey Manager でリセットしたメモ
  • そもそもResidentKeyの機能があるかどうかはauthenticatorGetInfoコマンド(0x04)で分かる⇒#2のメモ

uvとは何なのか?

  • User Verificationである。
  • User Verificationとは何なのか?「ユーザーを認証する」ということ。
  • つまり、uv=trueで、ユーザーが本当にAuthenticatorの所有者かどうか認証する、ということ。
  • YubikeyではPINでユーザー認証する、ということになります。
  • したがって、uv=trueにする、ということはpinAuth(0x08)が必須、ということになります。

ここまでで、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がすべて取れる場合と、そうでない場合があります。ここがンンン???なところ⇒詳細は後述
  • また、rk=trueで登録した場合、authenticatorGetAssertionで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も定義されており、幅広いシナリオ、ユースケースが想定されています。
それでいて仕様は無償で全公開。YubicoもGitHubでソース全公開。ガラパゴスな日本とは大違いです。

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

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした