CTAP2 お勉強メモ#5 - PIN

#はじめに
これは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)

以下の資料を教科書にしてお勉強しています。
- [CTAP仕様](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#usb-hid-implementation)
- [WebAuthn仕様](https://www.w3.org/TR/webauthn/)
- [Yubicoが公開しているサンプルプログラム](https://github.com/Yubico/libfido2)


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

<img src="https://qiita-image-store.s3.amazonaws.com/0/252291/4725b5c4-05df-ba4b-30e5-81458dd634f0.png" width=50%>

#目次
[１.概要](#1-%E6%A6%82%E8%A6%81)
[２.authenticatorClientPIN(0x06)コマンド](#2-authenticatorclientpin0x06%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89)
[３.Response](#3-response)
[４.先に知っておいたほうがいいこと](#4-%E5%85%88%E3%81%AB%E7%9F%A5%E3%81%A3%E3%81%A6%E3%81%8A%E3%81%84%E3%81%9F%E3%81%BB%E3%81%86%E3%81%8C%E3%81%84%E3%81%84%E3%81%93%E3%81%A8)
[５.PINロックアウトのリトライ回数問い合わせ](#5-pin%E3%83%AD%E3%83%83%E3%82%AF%E3%82%A2%E3%82%A6%E3%83%88%E3%81%AE%E3%83%AA%E3%83%88%E3%83%A9%E3%82%A4%E5%9B%9E%E6%95%B0%E5%95%8F%E3%81%84%E5%90%88%E3%82%8F%E3%81%9B)
[６.初期PINを設定する](#6-%E5%88%9D%E6%9C%9Fpin%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B)
[７.PINをYubikeyに送りつけて合っているかどうかチェックする](#7-pin%E3%82%92yubikey%E3%81%AB%E9%80%81%E3%82%8A%E3%81%A4%E3%81%91%E3%81%A6%E5%90%88%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%E3%81%8B%E3%81%A9%E3%81%86%E3%81%8B%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF%E3%81%99%E3%82%8B)
[８.Yubikeyをリセットする](#8-yubikey%E3%82%92%E3%83%AA%E3%82%BB%E3%83%83%E3%83%88%E3%81%99%E3%82%8B)
[- 参考](https://qiita.com/gebo/items/84454583daeaf6711fd0#%E5%8F%82%E8%80%83)
 
#1. 概要
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-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#without-pinToken-in-authenticatorGetAssertion)

#2. authenticatorClientPIN(0x06)コマンド
コマンドパラメータ 

|Parameter Name | Key |説明
|:-:|:-:|:---|
|pinProtocol|0x01|1固定。必須
|subCommand|0x02|必須。**subCommand Number**。下表参照
|keyAgreement|0x03|自身が生成したキーペアの公開鍵。setPIN、changePINで指定する。
|pinAuth|0x04|**sharedSecret**によって暗号化されたPIN。setPIN、changePINで指定する。
|newPinEnc|0x05|**sharedSecret**によって暗号化されたPIN。setPIN、changePINで指定する。
|pinHashEnc|0x06|**sharedSecret**によって暗号化されたPIN。getPINTokenで指定する。
※詳細 ⇒　[6.1. Commands](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#commands)

##subCommand Number
コマンドパラメータsubCommandには以下のいずれかのsubCommand Numberを指定します。

|subCommand Name | subCommand Number|説明|
|:-:|:-:|:-:|
|getRetries|0x01|PINロックアウトのリトライ回数を問い合わせる|
|getKeyAgreement|0x02|**KeyAgreement** を取得する|
|setPIN|0x03|PINを設定する|
|changePIN|0x04|PINを変更する|
|getPINToken|0x05|**PINTokenを取得する**|
※詳細　⇒　[5.5. authenticatorClientPIN (0x06)](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#authenticatorClientPIN)

#3. Response
|Member Name| Key|説明|
|:-:|:-:|:-:|
|keyAgreement|0x01|getKeyAgreement応答|
|pinToken|0x02|getPINToken応答|
|retries|0x03|getRetries応答|

#4. 先に知っておいたほうがいいこと
PINコマンドのところではここでだけ登場するワードがあります。
先にまとめて書いておきます。

- authenticatorKeyAgreement
- pinToken
- sharedSecret

###(authenticator)KeyAgreement(Key)とは

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/cd3affe8-7099-f194-6a7d-4b663d70d74e.png)

- Authenticatorに電源が投入されたときに内部で生成される鍵ペアのこと。
- 電源が投入されたとき、ということなので、Yubikeyを挿すたびに作り直される、ということなのだろう。
- ECDH P-256鍵ペアで、(a, aG)で表記されている。(a=秘密鍵、aG=公開鍵)
- この`aG(公開鍵)`がPlatform(クライアント)に渡される。
- Platform(クライアント)は`aG(公開鍵)`を使って **sharedSecret** を生成する。
- 仕様書　⇒　[5.5.2. Authenticator Configuration Operations Upon Power Up](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#authenticator-power-up-configuration)

###pinTokenとは
- KeyAgreementと同様にYubikeyに電源が投入されたときに生成されるもの。
- pinTokenはランダムな16byte値。

### sharedSecretとは
sharedSecretとはPINを暗号する際の共通鍵です。
つまり、送信側(Platform-クライアント)と受信側(Authenticator-Yubikey)で同じ鍵を持ち、エンコードとデコードでは同じ鍵を使います。
CTAPではECDHの鍵共有プロトコルで共通鍵を生成し、これをsharedSecretとして、PINを暗号化して交換します。

- [ECDHの難しい説明](https://ja.wikipedia.org/wiki/%E6%A5%95%E5%86%86%E6%9B%B2%E7%B7%9A%E3%83%87%E3%82%A3%E3%83%95%E3%82%A3%E3%83%BC%E3%83%BB%E3%83%98%E3%83%AB%E3%83%9E%E3%83%B3%E9%8D%B5%E5%85%B1%E6%9C%89)
- [ECDHのややわかりやすい説明](https://qiita.com/tomoyukilabs/items/eac94fdb2d0ca92f443a)
- [ECDHの一見すごくわかりやすそうで途中で理解することをあきらめる説明](http://d.hatena.ne.jp/rikunora/20120514/p1)

要は、**共通鍵のしくみだけれども、共通鍵自体の交換はしないのでセキュアである**、ということのようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/0fe4eb71-5b7c-d329-288c-86aba93db8c1.png)

####sharedSecretの作り方
仕様書では[5.5.4. Getting sharedSecret from Authenticator](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#gettingSharedSecret)に書いてあります。

- Authenticator側：ECDH P-256 キーペアを生成します。（AuthenticatorKeyAgreementKey)
- Platform側：ECDH P-256 キーペアを生成します。（PlatformKeyAgreementKey)
- AuthenticatorKeyAgreementKeyの公開鍵`aG`とPlatformKeyAgreementKeyの公開鍵`bG`を互いに交換します。（両者`aG``bG`を持つ）
- Platform側：**SHA-256((baG).x)**してsharedSecretを生成します。
- Authenticator側：同様に**SHA-256((baG).x)**してsharedSecretを生成します。
- この方法により生成されたsharedSecretは不思議なことに同じ32byteデータとなり、このデータを共通鍵として以降利用します。

####sharedSecretの使い方
- Set New Pin(初期PIN設定),Change PIN(PIN変更)コマンド：sharedSecretでPINを暗号化して指定する。
- Get PIN Tokenコマンド：PINのハッシュをsharedSecretで暗号化してAuthenticatorに送信してpinTokenを取得する。（取得したpinTokenはPIN認証した証拠です。makeCredential,GetAssertionコマンドにはPINではなくpinTokenを指定します)


#5. PINロックアウトのリトライ回数問い合わせ
[Yubikoのサンプル:retries.exe](https://github.com/Yubico/libfido2/blob/master/examples/retries.c)

```dos:retries.exeを使ってみる
c:\work>retries.exe \\?\hid#vid_1050&pid_0120#6&1b5e4874&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
5 ← 5回、という意味
```

authenticatorClientPIN(0x06)コマンドでsubCommandに`getRetries(0x01)`でPINロックのリトライ回数を得ることができます。

※PINの入力を間違うとカウンタが-1されて、0になるとAuthenticatorがロックされるやつです。PIN入力に成功するとカウンタはMAXまで回復ますが、いったんロックされるとリセットしないとダメです。

コマンドは以下の通り。

```:送信コマンド-getRetries(0x01)
{
  // 0x01:pinProtocol = 1固定
  1: 1,
  // 0x02:subCommand = 0x01:getRetries
  2: 1
}
```

```:Response
{
  // 0x03:retries = 5回
  3: 5
}
```

#6. 初期PINを設定する
[サンプル:setpin.exe](https://github.com/Yubico/libfido2/blob/master/examples/setpin.c)

```dos:setpin.exeを使ってみる
// PINが設定されていないAuthenticatorに対してPIN=1234にセットする
c:\work>setpin.exe 1234 \\?\hid#vid_1050&pid_0120#6&1b5e4874&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
```

##処理概要
(1) AuthenticatorKeyAgreementKeyを取得する
(2) PlatformKeyAgreementKeyを生成する
(3) sharedSecretを生成する
(4) PIN設定
![setPIN.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252291/49ef2c20-aabb-480d-eaa7-5c2ebc89cb90.png)

### (1)AuthenticatorKeyAgreementKeyを取得する
Authenticatorから公開鍵を取得します。
authenticatorClientPIN(0x06)コマンド、subCommandには0x02:getKeyAgreementを指定します。

```:送信コマンド
{
  // 0x01:pinProtocol = 1固定
  1: 1,
  // 0x02:subCommand = 0x02:getKeyAgreement
  2: 2
}
```

```:Response
{
  // 0x01 : keyAgreement (Authenticator public key in COSE_Key format. )
  1: {
      1: 2,
      3: -25,
      -1: 1,
      -2: h'73F9BE8101091A83CA35550D9E1BE509B43855F51E72B545137DCCFDEE6A4F98',
      -3: h'6B60E0BF603FA18D934B65F9D6D09390734BC55F3DB12DFB0FA8213E54738C7F'
    }
}
```

keyAgreementとして、COSEエンコードされた鍵情報が返ってきます。
これをデコードするとこうなります

```:KeyAgreement(COSE-KEY)
{
  "kty":"EC",
  "alg": ECDH-ES + HKDF-256,
  "crv":"P-256",
  "x":"73F9BE8101091A83CA35550D9E1BE509B43855F51E72B545137DCCFDEE6A4F98"
  "y":"6B60E0BF603FA18D934B65F9D6D09390734BC55F3DB12DFB0FA8213E54738C7F" 
}
```

- kty:EC ⇒ [JWK](https://techinfoofmicrosofttech.osscons.jp/index.php?JWK)
- alg:-25 ⇒ [IANA-COSE-ALGS-REG](https://www.iana.org/assignments/cose/cose.xhtml#algorithms)

### (2)PlatformKeyAgreementKeyを生成する
[前述の方法](#sharedsecret%E3%81%A8%E3%81%AF)で自分自身のKeyAgreement(キーペア)を生成します。
ここで生成したキーペアの公開鍵を次に送るauthenticatorClientPINコマンドの`0x03:keyAgreement`に設定します

### (3)sharedSecretを生成する
[前述の方法](#sharedsecret%E3%81%A8%E3%81%AF)でsharedSecretを生成します。

### (4)PIN設定
authenticatorClientPIN(0x06)コマンド、subCommandには0x03:setPINを指定します。
色々パラメータがあります。

```:送信コマンド
{
  // 0x01:pinProtocol : 1固定
  1: 1,
  // 0x02:subCommand :  0x03:setPIN
  2: 3,
  // 0x03:keyAgreement : COSE_Key
  3: {
    1: 2,       // "kty":"EC",
    3: -7,      // "alg":"ES256",
    -1: 1,      // "crv":"P-256",
    -2: h'72F1CED77FE97771967B06E0D75E2D0FB82CC6F45E55C26DEA6A54C403826D77',
    -3: h'EF9A28BC5914B936FC7BA8E4E2DAC07ACFDB0E6970B278FC5975EB4C3033F004'
    },
  // 0x04:pinAuth
  4: h'E1938FC8AB5567C2A1D7AF14D327C1F9',
  // 0x05:newPinEnc
  5: h'B59D2E29072575DBD1AD433F1D99E0FA69EE7135C31B30AC1F693B2DD3EBA7EB10D6C03B168EC7239650921FDEB7C09D050A0BFF9AF435410F4C697F29DCFAAB'
}
```

```:Response
00　←　データなし、ステータスコードのみで00であれば成功
```

####パラメータ 0x03:keyAgreement
先ほど生成したPlatformKeyAgreementKeyの公開鍵を設定します。

####パラメータ 0x04:pinAuth

**LEFT(HMAC-SHA-256(sharedSecret, newPinEnc), 16)**
*newPinEnc: AES256-CBC(sharedSecret, IV=0, newPin)*

だそうで...
訳すと

1. newPinを暗号化する。
	- pinは64byteになるように後ろに0x00を詰める(パティングする)
	- sharedSecretで暗号化する
	- 暗号化方式は `AES256 CBC`
	- ブロックサイズは128ビット
	- 初期化ベクタは0x00 × 16byte
	- パディング無し
2. sharedSecretと暗号されたPINのHMAC-SHA256を得る(32byte)
3. このHMACの先頭16byteをpinAuthとする

- 詳細　⇒　[5.5. authenticatorClientPIN (0x06)](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#authenticatorClientPIN)

####パラメータ 0x05:newPinEnc
pinを暗号化したもの。

[5.5.5. Setting a New PIN](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#settingNewPin)
>newPinEnc: Encrypted newPin using sharedSecret: AES256-CBC(sharedSecret, IV=0, newPin).

>During encryption, newPin is padded with trailing 0x00 bytes and is of minimum 64 bytes length. This is to prevent leak of PIN length while communicating to the authenticator. There is no PKCS #7 padding used in this scheme.

訳すと

- pinは64byteになるように後ろに0x00を詰める(パティングする)
- sharedSecretで暗号化する
- 暗号化方式は `AES256 CBC`
- ブロックサイズは128ビット
- 初期化ベクタは0x00 × 16byte
- パディング無し

#7. PINをYubikeyに送りつけて合っているかどうかチェックする
[サンプル:cred.exe](https://github.com/Yubico/libfido2/blob/master/examples/cred.c)

```dos:例
cred.exe -P 1111 -i C:\work\cred_out\creid.dat -k C:\work\cred_out\pubkey.pem \\?\hid#vid_1050&pid_0120#6&1b5e4874&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
```

処理概要
1. KeyAgreementを取得する ⇒ 省略
2. pinTokenを取得する

###pinTokenを取得する
暗号化したPINを送りつけてpinTokenがGETできればPINがあっていたということになります。

```:送信コマンド
{
  // 0x01:pinProtocol : 1固定
  1: 1,
  // 0x05:subCommand : 0x05 getPINToken
  2: 5,
  // 0x03:keyAgreement
  3: {
    1: 2,
    3: -7,
    -1: 1,
    -2: h'D74E96BB2DCC3AD363487B07FA0498F8C78A6D8E67B2C1E9D0D76A8C8CEF24C6',
    -3: h'A740AA0127727A3F453D4F7494D2EC4413CE5ADB577EC7C3586B1B1C87EA795C'
    },
  // 0x06 : pinHashEnc
  6: h'7C0D83AE025D2A7D9CEEFE5F132069F3'
}
```

```:Response
{
  // 0x02 : pinToken
  2: h'BF856405F09C7931A2C8AC868421666D'
}
```

####パラメータ 0x06 : pinHashEnc
PINを暗号した16byteのデータです。
**AES256-CBC(sharedSecret, IV=0, LEFT(SHA-256(PIN),16))**
ということなので、pinAuthとほぼ同じですね。

#8. Yubikeyをリセットする
PINを設定したり、変更したりしてるとロックアウトされたり、初期化したい時があります。
初期化用のCTAPコマンドは [5.6. authenticatorReset (0x07)](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#authenticatorReset) であり、[サンプルreset.exe](https://github.com/Yubico/libfido2/blob/master/examples/reset.c)もあるのですが、私の持っている青いYubikeyには効きませんでした。

```
fido_reset: FIDO_ERR_NOT_ALLOWED (0x30)
```

途方に暮れてwebを彷徨っていると素晴らしい情報が
⇒[まんぷくになった YubiKey を YubiKey Manager でリセットしたメモ](https://ritou.hatenablog.com/entry/2018/11/02/021028)

**Yubikey Manager** なる公式ツールでリセットすると初期化できました。
<img src="https://qiita-image-store.s3.amazonaws.com/0/252291/55ff724b-5f84-d772-70a6-7fa6104b1fcc.png" width=50%>

#おつかれさまでした
shredSecretのところでかなりハマった・・・

#参考
- [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)
- [Yubico/libfido2](https://github.com/Yubico/libfido2)
- [CBOR playground](http://cbor.me/)
- [Web Authentication: An API for accessing Public Key Credentials Level 1
W3C Candidate Recommendation, 7 August 2018](https://www.w3.org/TR/webauthn/)
- [SHA256ハッシュ生成ツール](https://www.milk-island.net/javascript/hashgenerator/sha2_256.html)
- [まんぷくになった YubiKey を YubiKey Manager でリセットしたメモ](https://ritou.hatenablog.com/entry/2018/11/02/021028)
- [Yubikey Manager](https://developers.yubico.com/yubikey-manager-qt/)