CTAP超ざっくりまとめ＆WindowsにFIDOキーでサインインする試み

#はじめに
本記事は[認証認可技術 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/identity)の22日目の記事です。
CTAPについて、自分の過去の投稿をまとめる感じで書いてみました。

#CTAPって何？
CTAP(**シータップ**)です。
認証のやつです。FIDOの関係です。
CTAPはFIDOアライアンスに策定されている仕様で、外部認証器（FIDOキー）のインタフェース仕様です。
YubikeyとかBioPassとかTitanとかはこの仕様に基づいて作られて、FIDOアライアンスの認定をとって「FIDO対応」として販売しています。

Webの人でプログラム作る人はWebAuthn-APIをJavaScriptからコールしてFIDOしているかと思います。
**- navigator.credentials.create()**
**- navigator.credentials.get()**
ですね。
で、これらのAPIをコールすると「セキュリティキーを挿入してください」とかメッセージボックスが出てきてFIDOキーと対話できますが、このときブラウザが内部でFIDOキーと通信するときにCTAPでやっています。
ですんで、WebでFIDOしている人はCTAPのことは知らなくても全く問題ありません。

とはいえ、物事を土台から理解することは大切なことです。また、興味本意でマニアックな領域を追求することって楽しいですよね。
ということで、ここではCTAPについて超ざっくり語って、CTAPを使ってWindowsのネイティブアプリで何かやってみたんだけど、という話を書きたいと思います。


#CTAPの位置付け
FIDO用語を使って言うと、
WindowsとかAndroidとかの`Platform`の中でChromeとかEdgeとかの`Browser`が動いていて、`Browser`は`WebAuthn`を実装していて、その`WebAuthn`の内部では`Authenticator(FIDOキー）`と**CTAP**で通信します。

![CTAP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/01014a7e-6511-9dd5-3bca-7f3c3323c3f6.png)


#CTAPスペック超ざっくり
CTAPの仕様はFIDOアライアンスで公開されています。
本投稿時点で2019/1/30版が最新です。
[CTAP仕様 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)

以降ではスペックに書いてある以下のことを超ざっくり説明していきます。
- CTAP1/U2FとCTAP2がある
- CBORでエンコーディングする
- CTAPauthenticatorコマンド
- USB HIDとNFCとBLEがある

##CTAP1/U2FとCTAP2がある
FIDOはFIDO1とFIDO2という仕様的なバージョンがあるんですが、CTAPにもCTAP1/U2FとCTAP2があります。
- FIDO1＝CTAP1/U2F
- FIDO2＝CTAP2
という理解でいいと思います。
CTAP2ではResidentKeyというキーの中にデータを保存する仕様が追加されていたりします。

ちなみに、FIDOキー欲しいなと思ってネットで製品を検索するとU2F対応とかFIDO2対応とか書いてありますが、それがこのことです。U2Fとしか書いていないものはFIDO2ではないと思った方がいいかと思います。

**本記事はCTAP2のことだけ書いています。CTAP1のことは書いていませんのでご注意ください。**


##CBORでエンコーディングする
以降で説明するCTAPauthenticatorコマンドをHID,NFC,BLEのプロトコルのPayloadに乗っけて送信してレスポンスをGETするんですが、CTAPauthenticatorコマンドの形式が`CBOR`というものになります。
**COBOL**ではありません,`CBOR(The Concise Binary Object Representation ※1）`です。
`CBOR`は`CBOR`です。**細かいことは知りません。**Windowsだったら[nugetにライブラリがあるんでそれ](https://www.nuget.org/packages/PeterO.Cbor)使えばいいです。

※１ シーボルと言う、たぶん。雰囲気jsonにちょっと似てます。
以下参考です。
[[CWT入門その1] CBORによるオブジェクトのバイナリ表現](https://qiita.com/ritou/items/f3eafedab038d17e3066)


##CTAPauthenticatorコマンド
CTAPコマンド群をざっくり紹介します。
- authenticatorMakeCredential
- authenticatorGetAssertion
- authenticatorGetNextAssertion
- authenticatorGetInfo
- authenticatorClientPIN
- authenticatorReset

###authenticatorMakeCredential(0x01)
WebAuthnでいうところの`navigator.credentials.create()`がこのコマンドに対応しています。
指定されたrp（Relying Party）情報をキーにしてクレデンシャル（秘密鍵と公開鍵のキーペア）を生成して秘密鍵はキー内部に保管し、公開鍵とクレデンシャルIDなどが入ったAttestation構造体をレスポンスしてきます。Attestationには署名も含まれていて、署名を検証するとちゃんとしたものだと確認することができます。
このコマンドを通すためには、PIN（記憶要素）か生体要素のいずれかでの本人確認-UV（UserVerification）が必要です（※2）。
PIN認証はパラメータにPINを文字列で指定するとかではなく、`authenticatorClientPIN`で求めた`pinAuth(0x08)`を指定します、これはいわゆるトークンみたいなものです。
生体認証する場合は`pinAuth(0x08)`は不要ですが`options(0x07)`のuvをtrueにします。こうすると生体認証をFIDOキーが勝手にやってくれます。
`options(0x07)`のrk(ResidentKey)オプションをtrueにしとくと`user(0x03)`の内容をFIDOキーに保存することができます。このrkがFIDO2の便利機能です。

ところで、このコマンドはキーペアを生成して秘密鍵をFIDOキーに保管すると言いましたが、不思議なことに、FIDOキーのメモリを消費することは無いです。
どうやらレスポンス（Attestation）に入っている`CredentialID`があれば秘密鍵を再生成できるようです。**「えーそれって秘密鍵を外に出しているじゃん！」**というツッコミがありそうなんですが、誰もツッコマないので問題ないんだろう（※3)。
そんなわけで、FIDOキーには何も書き込まないので、無限にMakeCredentialできます。ただし、これはrkしない場合（Non ResidentKey)。
rkすると、`user(0x03)`の内容と秘密鍵をFIDOキーに保管（Write）します。なのでrkしまくるとそのうちFIDOキーがパンクします（※4)。

ちなみに、uvとは別のバラメータでupっていうのがあり、最初はこんがらがるので注意です。
- uv=UserVerification=ユーザー本人かどうかPINとか生体とかで検証すること
- up=UserPresence=ユーザーがいるかどうかタッチで確認すること（本人かどうかは気にしない）
upはFIDOキーのタッチセンサー使います。Yubikeyはいかにも指紋認証してそうですが、upです、ただ接触をみているだけです。
upは3要素（記憶、所持、生体）にはカウントされないのでupだけで多要素認証とかいうとセキュリティおじさんに怒られます。

※2 FIDOキーにPINが未設定の場合（リセット直後とか初期状態のとき）はuv無しでもコマンドが通ったりします。
※3 問題あったら困るのでちゃんとしてると思います。
※4 パンクしたらどうなるのか、やってみましょう。

**参考**
[CTAP2 お勉強メモ#3 - 登録(Create)](https://qiita.com/gebo/items/2cfc3202cd88a59b24ba)

###authenticatorGetAssertion(0x02)
WebAuthnでいうところの`navigator.credentials.get()`がこのコマンドに対応しています。
rp情報、クレデンシャルID、チャレンジをパラメータで渡して認証します。`authenticatorMakeCredential`で登録したクレデンシャルがあればすれば認証OKとなりAsssertion構造体をレスポンスしてきます。
Assertionには秘密鍵で署名されたチャレンジが入っていますので、署名をRPサーバ側で保管されている公開鍵で検証することで登録したときのFIDOキーと同じものだよ、ということが確認できます。これが公開鍵暗号の仕組みです。

さて、このコマンドをパスするには`authenticatorMakeCredential`と同じくPINか生体のいずれかで本人確認します（※5)。
`authenticatorMakeCredential`でrkしてた場合はクレデンシャルIDを指定しないでもAssertionをGETでき、user情報もGETできます。この辺がFIDO２のええ感じなところです。

※5 `options(0x05)`でuvをFalseにするとPINも生体も無しでAssertionをGETできますが、Assertionの`authData(0x02)`にuvしたかどうかの結果（Flags）が返ってきます。ここをちゃんと見る必要があります。

**参考**
[CTAP2 お勉強メモ#4 - 認証(Authentication)](https://qiita.com/gebo/items/634aa39b0e08d8258682)


###authenticatorGetNextAssertion(0x08)
`authenticatorMakeCredential`でrpをキーにしてクレデンシャルを生成するんですが、１個のrpにつき１個のクレデンシャルしか生成できないわけではなく複数のクレデンシャルを生成することが可能です。
`authenticatorGetAssertion`でrpとクレデンシャルIDを指定すれば引っ張ってくるクレデンシャルは必ず１個なんですが、rk(ResidentKey)して同じrp情報で`authenticatorGetAssertion`を何回か実行するとFIDOキーの中にはrpに対して複数のクレデンシャルが保存されます。
この状態で`authenticatorGetAssertion`でrpだけを指定して（クレデンシャルIDを指定しないで）GETするとrpに紐づくAssertionが複数ヒットし、レスポンスの`numberOfCredentials (0x05)`にAssertionの総数が入ってきてAssertion自体は1個だけ取れます。こんな時`authenticatorGetNextAssertion`で2つ目以降のAssertionをGETすることができます。

**参考**
[CTAP2 お勉強メモ#6 - Use Case - 5. rk,uvについて](https://qiita.com/gebo/items/cfc6ceb1c7f9aa5fdad6#5-rkuv%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)


###authenticatorClientPIN(0x06)
PINをアレコレするコマンドです。以下のようなサブコマンドで構成されています。
- getRetries(0x01)
- getKeyAgreement(0x02)
- setPIN(0x03)
- changePIN(0x04)
- getPINToken(0x05)

`getRetries`はPINのリトライ回数を問合せます。
MakeCredential、GetAssertion、GetNextAssertionではパラメータに`PinAuth`というものを指定して認証することができるのですが、この`PinAuth`を生成するのが`getPINToken`です。
PINを変更したりは`setPIN`、`changePIN`で行います。
重要なのが`getKeyAgreement`で、ECDHという超謎な方法でPINを暗号化する共通鍵（SharedSecret）をジェネレートするための`KeyAgreement`というパラメータを取得します（※6)。
そんで、`setPIN`、`changePIN`、`getPINToken`はパラメータには`KeyAgreement`を使って復元不可能な暗号化をしたPINを指定する必要があります。
`getPINToken`はレスポンスで`pinToken`をGETできます(※7)。

※6 説明が難しい！
※7 pinAuth=LEFT(HMAC-SHA-256(pinToken,clientDataHash),16)

百聞は一見にしかず、ということでこちら参照ください。
**PIN周りのシーケンス図**
<img src="https://qiita-image-store.s3.amazonaws.com/0/252291/6ddeb490-9f33-2807-4a48-1ec4ad2f3d8a.jpeg" width="70%">
引用元[5.5.8.3. Without pinToken in authenticatorGetAssertion](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#without-pinToken-in-authenticatorGetAssertion)

**参考**
[CTAP2 お勉強メモ#5 - PIN](https://qiita.com/gebo/items/84454583daeaf6711fd0)


###authenticatorGetInfo(0x04)
FIDOキーの基本情報をGETするコマンドです。
接続しているFIDOキーがどんな機能を持っているかがわかります。

**参考**
[CTAP2 お勉強メモ#2 - 接続(GetInfo)](https://qiita.com/gebo/items/e0bd197d607312dcf4fb)

###authenticatorReset(0x07)
FIDOキーをリセットするコマンドです。
パラメータが無いので`authenticatorGetInfo(0x04)`とほぼ一緒です。
コマンドは簡単ですがクレデンシャルやらPINやら綺麗さっぱりクリアされるので注意。

CTAP仕様を直訳するとこんな感じです。

>クライアントはこのメソッドを使用して認証システムを工場出荷時のデフォルト状態にリセットし、生成されたすべての資格情報を無効にします。 このメカニズムの偶発的なトリガーを防ぐために、何らかの形式のユーザー承認がオーセンティケーター自体で実行される場合があります。つまり、クライアントはリセットが実行されるまでデバイスをポーリングする必要があります。 リセットを実行する実際のユーザーフローは、オーセンティケーターとこの仕様の範囲外に応じて異なります。

**参考**
[5.6. authenticatorReset (0x07)](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorReset)


##USB HIDとNFCとBLEがある
さて、CBOR形式でCTAPauthenticatorコマンドを作りました。これをFIDOキーに送りつけてやるのですが、`HID`だとか、`NFC`リーダーで読み取るとか、`BLE`で無線でやるとかの仕様があります。
それぞれのインターフェース毎の低レベルのプロトコルがあり、そのPayloadにCBORエンコードしたCTAPauthenticatorコマンドを乗っけるイメージです。
送ったコマンドの応答もPayloadはCBORエンコードされています。

###USB HID(Human Interface Device)
FIDOセキュリティキーで世の中に一番出回っているタイプです。
このプロトコルがかなりめんどくさいです。

- HIDの通信は１回の送信で64byte単位なので、`初期パケット(initialization packets)`と`継続パケット(continuation packets)`に分けて送信する。
- このPacketの中に`CTAPHIDコマンド`なる形式のデータをつっこむ。
- `CTAPHIDコマンド`はいくつかあり、`CTAPHID_INIT(0x06)`、`CTAPHID_CBOR(0x10)`などある。
- 基本的には`CTAPHID_INIT`で`CID（Channel identifier-セッションIDみたいなもの）`を取得してそのほかのCTAPHIDコマンでは`CID`をヘッダに入れて投げるという方式。
- `CTAPHID_CBORコマン`ドにCBORエンコードしたCTAPauthenticatorコマンドを突っ込んで送る。

**参考**
[CTAP2 お勉強メモ#2 - 接続(GetInfo)](https://qiita.com/gebo/items/e0bd197d607312dcf4fb)

###NFC(Near Field Communication)
NFCは`APDU(Application Protocol Data Unit)`というICカードと通信する標準コマンドで通信しますが、この中にCBORエンコードしたCTAPauthenticatorコマンドを突っ込みます。
この通信で重要なのがFIDOアプリの**AID(Application ID)**です。

FIDOアプリのAID
`A0:00:00:06:47:2f:00:01:00`

通信はまずFIDOアプリに`SELECT FILE`してから、CBORエンコードしたCTAPauthenticatorコマンドを送信し、応答を受信します。

**詳細**
[CTAP2 お勉強メモ#7 - NFC](https://qiita.com/gebo/items/2c9d020c0768b95a01b0)


###BLE(Bluetooth Low Energy)
BLEはおなじみ非接触の無線通信です。これでFIDO認証できたらカッコいいですよね。
BLE通信は`GATT(Generic attribute profile-ガット)`です。この中にCBORエンコードしたCTAPauthenticatorコマンドを格納して通信します。
**PlatformとFIDOキーは予めペアリングしておく必要があります。**

**FIDO Service UUID**
`0000fffd-0000-1000-8000-00805f9b34fb`

**FIDO Characteristic UUID**

| Characteristic Name | UUID | Property | memo |
|:-:|:-:|:-:|:-:|
|FIDO Control Point|F1D0FFF1-DEAA-ECEE-B42F-C9BA7ED623BB|Write|デバイスへ送信バッファ|
|FIDO Status|F1D0FFF2-DEAA-ECEE-B42F-C9BA7ED623BB|Notify|デバイスからの応答バッファ|
|FIDO Control Point Length|F1D0FFF3-DEAA-ECEE-B42F-C9BA7ED623BB|Read|送信バッファの最大サイズ(2byte)|
|FIDO Service Revision Bitfield|F1D0FFF4-DEAA-ECEE-B42F-C9BA7ED623BB|Read/Write|通信プロトコルの種類(1byte)|
|FIDO Service Revision|00002A28-0000-1000-8000-00805F9B34FB|Read|リビジョン|

通信の手順としては、FIDOキーが発進する`アドバタイズパケット`をPlatformがキャプチャし、`Service UUID`で識別して接続します。その後、`FIDO Control Point Characteristic`に接続して送信すると`FIDO Status Characteristic`に応答が返ってきます。
通信でやりとりするPacketの中にCBORエンコードしたCTAPauthenticatorコマンドを格納します。

**参考**
[CTAP2 お勉強メモ#8 - BLE](https://qiita.com/gebo/items/f4dfedce907babb46241)


#WindowsにFIDOキーでサインインする試み
ここまでCTAPについて超ざっくり説明してきました。
この~~クソ複雑な~~CTAPを使うとどこでもFIDOキーとお話できるようになります。

- **ブラウザ君だけが持っているWebAuthnに頼らなくても、FIDOのセキュアな仕組みを実装することができます。**
- **AndroidやiOSのネイティブアプリでFIDOキーを使うこともできるし、Windows7など古いOSや、ラズパイでもFIDOキー使えます。**
- **PythonでもCでもVB6でもFIDOできますたぶん。**

というわけで、過去にFIDOキーを使った試みをやりました(※8)が、今回は**WindowsにFIDOキーでサインインする**サンプルを作ってみました。
（Windows Helloではありません）

※8 [FIDO2セキュリティキーでリモートデスクトップをする試み](https://qiita.com/gebo/items/01b14279849c5fd399f7),
[FIDO2セキュリティキーで電子署名をする試み](https://qiita.com/gebo/items/0a08bc0e1aea6bc12c96) ,
[ラズパイでYubikeyを光らせてみる](https://qiita.com/gebo/items/fdeaf11b6aefa67856e3)


##デモ動画

###1.USBとPINでログイン
Yubikeyの青いやつを使っています。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">CTAPデモ　USB x PIN <a href="https://t.co/7umhiYSuR6">pic.twitter.com/7umhiYSuR6</a></p>&mdash; gebo (@gebogebogeboge) <a href="https://twitter.com/gebogebogeboge/status/1207238328505946112?ref_src=twsrc%5Etfw">December 18, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

https://youtu.be/YEM_rRh8ybs


###2.USBと指紋でログイン
飛天のBioPassを使っています。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">CTAPデモ　USB x 指紋 <a href="https://t.co/2PmYvBQjYv">pic.twitter.com/2PmYvBQjYv</a></p>&mdash; gebo (@gebogebogeboge) <a href="https://twitter.com/gebogebogeboge/status/1207238692101742592?ref_src=twsrc%5Etfw">December 18, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

https://youtu.be/Fp_vV7LnpUo

###3.NFCとPINでログイン
YubiKeyの黒いやつを使っています。ICカードリーダーは定番PaSoRiです。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">CTAPデモ　NFC x PIN <a href="https://t.co/wbst00IyaS">pic.twitter.com/wbst00IyaS</a></p>&mdash; gebo (@gebogebogeboge) <a href="https://twitter.com/gebogebogeboge/status/1207238963276087296?ref_src=twsrc%5Etfw">December 18, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

https://youtu.be/AxPXJiWL29Y

###4.BLEと指紋でログイン
FeitianのAllinPassです。最高にトゥーマッチなFIDOキーで大好きです。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">CTAPデモ　BLE x 指紋 <a href="https://t.co/AhBN946rlN">pic.twitter.com/AhBN946rlN</a></p>&mdash; gebo (@gebogebogeboge) <a href="https://twitter.com/gebogebogeboge/status/1207239199755100160?ref_src=twsrc%5Etfw">December 18, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

https://youtu.be/we4LX0w9XDE


##解説
Windowsの認証レイヤ（ロック画面）は`ICredentialProvider`と`ICredentialProviderCredential`インタフェースを実装した`カスタムCredentialProvider`を実装することでオーバライドできます(※8)。
今回は`カスタムCredentialProvider`でCTAPを実装してWindowsにサインインさせるサンプルプログラムを作成しました。

仕組みはこんな感じ

- FIDOキーにWindowログイン情報を記録します。
    - RPをWindowsサインインユーザー、ResidentKeyでPasswordを書き込んでおく。
- ログインのときは、カスタムCredentialProviderでResidentKeyされているPasswordを取り出してWindowsにサインインする。
- AttestationのVerifyはやってません。

※8 クソニッチな領域です。

ソースは[GitHub](https://github.com/gebogebogebo/CTAPSignin)で公開しています。
細かい解説は今後の投稿でやっていきたいと思います。


#おつかれさまでした
以上、FIDOのCTAPについてでした。
CTAPを使うとFIDOをWeb意外の領域でも実装できます。
レガシーなシステムやIoTの分野でセキュリティが求められる要件などにFIDOが活用できるのでは、とか思っています。

**大切なこと**
**AttestationのVerifyはちゃんとやりましょう**