CTAP2 お勉強メモ#4 - 認証(Authentication)

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

随時修正します。

- [CTAP仕様](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html)
- [WebAuthn仕様](https://www.w3.org/TR/webauthn/)
- [サンプルプログラム](https://github.com/Yubico/libfido2)
- [CTAP2 お勉強メモ#1](https://qiita.com/gebo/items/d2ffbd4fcf7d75e21b63)
- [CTAP2 お勉強メモ#2](https://qiita.com/gebo/items/e0bd197d607312dcf4fb)
- [CTAP2 お勉強メモ#3](https://qiita.com/gebo/items/2cfc3202cd88a59b24ba)

[#3](https://qiita.com/gebo/items/2cfc3202cd88a59b24ba)ではauthenticatorMakeCredentialコマンドでYubikeyに登録、`クレデンシャルID(Credential ID)`と`認証用公開鍵(Credential Public Key)`をゲットしました。

今回はこれらの情報を使って認証をするお勉強メモです。


#環境
- OS=Windows10 1803
- 開発環境=Visual Studio Pro 2017(15.8.5)
	- Microsoft Visual C++ 2017
- Yubico セキュリティキー(青) 

**※キーはリセットしてPINが設定されていないものを使います、PINを設定してしまうと、pinAuthの話をしないといけなくて、その話をするとすごく長くなるのでやめています。pinAuthの話は [#5 - PIN](https://qiita.com/gebo/items/84454583daeaf6711fd0) をみてください。**

<img width="367" alt="キャプチャ2.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/252291/4725b5c4-05df-ba4b-30e5-81458dd634f0.png">


#認証(Authentication)
- webauthn でいうところの `navigator.credentials.get()` がこのコマンドに対応しています。
- CTAP仕様書は [5.2. authenticatorGetAssertion (0x02)](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#authenticatorGetAssertion)、[6.1.Commands](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#commands) あたりを参照。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/252291/d32c7d22-660a-9dd9-6a50-be57436d6487.png)

※絵はこちらからピックアップさせていただきました。
https://developer.mozilla.org/ja/docs/Web/API/Web_Authentication_API

#目次
1. [サンプルプログラム概要](https://qiita.com/gebo/items/634aa39b0e08d8258682#1-%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E6%A6%82%E8%A6%81assertexe)
2. [authenticatorGetAssertion(0x02) コマンド](https://qiita.com/gebo/items/634aa39b0e08d8258682#2-authenticatorgetassertion0x02-%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89)
3. [Assertion(Response)](https://qiita.com/gebo/items/634aa39b0e08d8258682#3assertionresponse)
4. [Verify(検証)](https://qiita.com/gebo/items/634aa39b0e08d8258682#4verify%E6%A4%9C%E8%A8%BC)

#1. サンプルプログラム概要（assert.exe）
assert.exeの処理シーケンスです。

- 認証用公開鍵とYubikeyのHIDパスの指定は必須
- -aオプションでauthenticatorMakeCredentialコマンドで作成したクレデンシャルIDファイル(creid.dat)を指定

```
>assert.exe -a c:\work\cred_out\creid.dat c:\work\cred_out\pubkey.pem \\?\hid#vid_1050&pid_0120#6&1b5e4874&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
```

[GitHubで公開されているソースはこちら](https://github.com/Yubico/libfido2/blob/master/examples/assert.c)
<img src="https://qiita-image-store.s3.amazonaws.com/0/252291/e7e646e4-56da-052d-96c5-bb9bb623a782.png" width=40%>

#2. authenticatorGetAssertion(0x02) コマンド
<img src="https://qiita-image-store.s3.amazonaws.com/0/252291/64032938-33bf-d0e1-683c-98e525502c6a.png" width=70%>

Yubikey(Authenticator)内のクレデンシャル情報を取得するコマンドです。
クレデンシャル情報とは、秘密鍵を持っていることを証明する情報で、**Assertion**と名付けられています。
サンプルプログラムでは 以下のパラメータを指定しています。

- rpid
- clientDataHash
- allowList
- options

```:authenticatorGetAssertionコマンドのパラメータを見やすく整形したもの
    // 0x01 rpid
    1: "localhost",
    
    // 0x02 clientDataHash
    2: h'EC8D8F78424A2BB78234AACA07A1F656421CB6F6B3008652352DA2624ABE8976', 

    // 0x03 allowList
    3: [{
        "type": "public-key",
        "id": h'C52D9FDEBA951F4ACA94C2CB7DCFB47B13D0E4D676E2491F90D081D65E6995C74A77828BF8DFE239FC1D62D93DDB547827C524A2A3C035A27D4041DB8352F87E'
        }],
        
    // 0x05 options
    5: {"up": false, "uv": false}

```

###ざっくりコマンド解説
[ここ](https://qiita.com/gebo/items/2cfc3202cd88a59b24ba#attestation%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)で書いたように、**クレデンシャルはrpidをキーにして保存されています。**

~~なので、rpidを指定さえすればAssertionをGETできるかというと、そうではないんです。
allowListでCredentialIDも指定しないといけません。
⇒rpidだけででAssertionをGETすることはできません。~~

~~別の方法として、options.uv=trueにしてさらにpinAuth、pinProtocolを指定すると、CredentialIDの指定無しでAssertionをGETできます。
ただし、これは登録時にrk=trueを指定してuser情報をYubikeyに保存していることが前提条件となります。（複雑！）~~

上記記載は誤り、正しくは
- allowListでCredentialIDを指定して取得する方法とallowList無しで取得する方法がある。
- allowListでCredentialIDを指定すると、CredentialIDに紐づいたAssertion(1個)をGETすることができる。
- allowList無しの場合は、登録時にrk=trueでuser情報をYubikeyに保存して登録されたAssertion(1個以上)をGETすることができる。
- allowList無しの場合はAssertionが複数個取れる場合があるので注意。詳細は[#6](https://qiita.com/gebo/items/cfc6ceb1c7f9aa5fdad6#getassertion--rp%E3%81%AB%E5%AF%BE%E3%81%97%E3%81%A6%E8%A4%87%E6%95%B0%E3%81%AEuser%E3%81%8C%E7%99%BB%E9%8C%B2%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%A9%E3%81%AE%E3%82%88%E3%81%86%E3%81%AA%E6%8C%99%E5%8B%95%E3%81%AB%E3%81%AA%E3%82%8B%E3%81%AE%E3%81%8B)を参照。


clientDataHash⇒いわゆるチャレンジです。クレデンシャルを特定するためのパラメータではなく、GETしたクレデンシャルをVerifyするためのパラメータです。

###rpid
[Relying Party Identifier](https://www.w3.org/TR/webauthn/#relying-party-identifier)ってことで、要はサーバーのドメインを指定します。
authenticatorMakeCredentialの時に指定したものを指定する必要があります。

試しに、"localhost"を"gebogebo.com"に変えてコマンドを送ってみると。
`FIDO_ERR_INVALID_CREDENTIAL (0x22)`
となり、エラーとなります。
*rpidに問題がある、といった意味合いのエラーは定義されてないのか、ハマりそうだ・・・*

###clientDataHash
これは [#3](https://qiita.com/gebo/items/2cfc3202cd88a59b24ba#clientdatahash) と同じ要領で作成するのである、という理解が正解なのだけれども、もっとシンプルに考えて、**乱数をSHA256(32byte)したchallenge**、ということでも問題ないんだろうと思います。(CTAP的には)。
それで、Assertionにはこのchallengeの署名が返ってくるので、それをVerifyして真正性を確認するのです。

###allowList
AllowListクレデンシャルを選択するためのパラメータです。
[仕様書](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#authenticatorGetAssertion)には`allowCredentialDescriptorList`と書いてあるのですが、どんな形式で何をしていするのか、明確な記載が見つかりません…

とりあえず仕様書のEXAMPLEによるとこんな感じ
![image.png](https://qiita-image-store.s3.amazonaws.com/0/252291/054e8ac1-beab-a74e-093d-c7076fc3519e.png)
で、サンプルプログラムでも
`"type":"public-key"`⇒固定値
`"id"`⇒CredentialIDのバイナリデータをHEX変換した文字列
をつっこんでいます。
*配列で複数個指定可能なのはallowListという名前で分かるのですが、クレデンシャルIDを指定するのに、typeにpublic-keyを指定するのが理解不能…*

###options
動作オプションです。
[仕様書よりピックアップ](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#authenticatorGetAssertion)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/252291/f33c4813-6bfe-e744-19c5-0ca00c981a04.png)

- サンプルasser.exeでは-pを指定するとup=trueで動作します。
- up=trueのときは、Yubikeyがピカピカ光ってタッチするとAssertionが返ってくるようになります。(ユーザーの存在確認）
- サンプルasser.exeでは-vを指定するとuv=trueで動作します。
- uvをtrueにしても、特に動作に変更はないのですが、後述のAssertionのFlagでuv結果を受け取ります。uvの指定値と結果値を見て、本当にuvされたかをチェックします。結果値のuvをtureにするためには、PIN認証が必要になり、芋づる式にpinAuth、pinProtocolの指定も必要になります。（ここはもしかしたらCTAPというよりもYubikeyの仕様なのかもしれません）


#3.Assertion(Response)
コマンドのResponseのことです。仕様書等を見るとAssertionと名付けられています。
<img src="https://qiita-image-store.s3.amazonaws.com/0/252291/74b33689-5832-f355-9ddd-930940d6b85c.png" width=70%>

Assertionは以下のデータから構成されます。

- credential
- authData
- signature

CBORデコードしたAssertion

```:Assertionを見やすく整形したもの
{
    // 0x01:credential
    1: {
        "id": h'C52D9FDEBA951F4ACA94C2CB7DCFB47B13D0E4D676E2491F90D081D65E6995C74A77828BF8DFE239FC1D62D93DDB547827C524A2A3C035A27D4041DB8352F87E', 
        "type": "public-key"
        }, 
        
    // 0x02:authData
    2: h'49960DE5880E8C687434170F6476605B8FE4AEB9A28632C7995CF3BA831D97630000000068', 

    // 0x03:signature
    3: h'3044022065E5E681837AEDE320D601BE7F82533976F437A5A1066B12EB4D1CAE870E7C32022035E160160AE4B014B81EDA0C264F1F8863A5D5C600EE8B482AB10E52DAC690CB'
}
```

##Assertionについて
[#3のAttestation](https://qiita.com/gebo/items/2cfc3202cd88a59b24ba#3-attestation)に比べたらだいぶマシです。
取得したクレデンシャル情報です。

###credential
Credential ID。GetしたクレデンシャルのIDですね。
送信したコマンドのAllowListで指定したものと同じです。希望したものがとれたということで。

###authData
[#3](https://qiita.com/gebo/items/2cfc3202cd88a59b24ba#authdataauthenticator-data) とフォーマットは同じですが、Attested credential data部が無く、以下の情報のみです。

- rpIdHash
- flags
- signCount

###signature
**署名**です。Verifyに使います。

#4.Verify(検証)
さて、Verifyです。
Assertionが正しいものかどうか、Verifyします。
基本的には[#3](https://qiita.com/gebo/items/2cfc3202cd88a59b24ba#4-verify%E6%A4%9C%E8%A8%BC)と同じです。安心しました（？）

<img src="https://qiita-image-store.s3.amazonaws.com/0/252291/d71726f5-443c-47d6-c6f9-f4b460e2d954.png" width=80%>

- **Verify-1.Assertionに含まれるflagsを検証**
- **Verify-2.送信したRPIDとAssertionに含まれるRPIDハッシュを検証**
- **Verify-3.署名検証**

Verify-1とVerify-2は [#3](https://qiita.com/gebo/items/2cfc3202cd88a59b24ba#verify-1) と同じなので省略

##Verify-3.署名検証
※大まかには[#3](https://qiita.com/gebo/items/2cfc3202cd88a59b24ba#verify-3)と同じです。

SigVerifyに必要なデータは

- Sig
- Verify用Public key
- 署名対象データ

この3つがそろえばVerifyできます。
今回もコマンドでVerifyをやってみてお勉強したいと思います。

以下の図は[Webauthn仕様書](https://www.w3.org/TR/webauthn/#fig-signature)。(こうやって署名されているよ、ってことです)
<img src="https://qiita-image-store.s3.amazonaws.com/0/252291/599a9379-4659-e893-c4e4-6c15ee2bdddd.png" width=70%>



###◆Sig
Attestationのsignatureです。

- ダンプログからsignatureを取り出してassert_sig.hexというファイルで保存する
- certutilでassert_sig.hexをバイナリ `assert_sig.bin` に変換しておく

```
c:\work\assert>certutil -f -decodehex assert_sig.hex assert_sig.bin 4
入力長 = 140
出力長 = 70
CertUtil: -decodehex コマンドは正常に完了しました。
```

###◆Verify用Public key
- 認証用公開鍵です。
- [#3で作成したもの](https://qiita.com/gebo/items/2cfc3202cd88a59b24ba#%E8%AA%8D%E8%A8%BC%E7%94%A8public-key%E3%81%AE%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88)です。
- `cred_pubkey.pem` というファイル名で持っています。

###◆署名対象データ
**authenticatorData + clientDataHash ⇒　署名対象データ**
ですね。
[#3](https://qiita.com/gebo/items/2cfc3202cd88a59b24ba#%E7%BD%B2%E5%90%8D%E5%AF%BE%E8%B1%A1%E3%83%87%E3%83%BC%E3%82%BF)と同じ要領で`assert_dgst.bin`を作成します。

```
c:\work\assert>certutil -f -decodehex assert_dgst.hex assert_dgst.bin 4
入力長 = 138
出力長 = 69
CertUtil: -decodehex コマンドは正常に完了しました。
```

###◆Verify
材料がそろったらopensslのコマンド一発です。

```
c:\work\assert>openssl dgst -sha256 -verify cred_pubkey.pem -signature assert_sig.bin assert_dgst.bin
Verified OK
```

#おつかれさまでした
基本的なところはなんとなくひととおりわかったきがする。