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

CTAP2 お勉強メモ#5 - PIN

はじめに

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

本投稿は以下の投稿の続きです。
- CTAP2 お勉強メモ#1
- CTAP2 お勉強メモ#2
- CTAP2 お勉強メモ#3
- CTAP2 お勉強メモ#4

以下の資料を教科書にしてお勉強しています。
- CTAP仕様
- WebAuthn仕様
- Yubicoが公開しているサンプルプログラム

環境

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

目次

1.概要
2.authenticatorClientPIN(0x06)コマンド
3.Response
4.先に知っておいたほうがいいこと
5.PINロックアウトのリトライ回数問い合わせ
6.初期PINを設定する
7.PINをYubikeyに送りつけて合っているかどうかチェックする
8.Yubikeyをリセットする
- 参考

1. 概要

PIN周りのシーケンス図

引用元5.5.8.3. 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

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)

3. Response

Member Name Key 説明
keyAgreement 0x01 getKeyAgreement応答
pinToken 0x02 getPINToken応答
retries 0x03 getRetries応答

4. 先に知っておいたほうがいいこと

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

  • authenticatorKeyAgreement
  • pinToken
  • sharedSecret

(authenticator)KeyAgreement(Key)とは

image.png

  • Authenticatorに電源が投入されたときに内部で生成される鍵ペアのこと。
  • 電源が投入されたとき、ということなので、Yubikeyを挿すたびに作り直される、ということなのだろう。
  • ECDH P-256鍵ペアで、(a, aG)で表記されている。(a=秘密鍵、aG=公開鍵)
  • このaG(公開鍵)がPlatform(クライアント)に渡される。
  • Platform(クライアント)はaG(公開鍵)を使って sharedSecret を生成する。
  • 仕様書 ⇒ 5.5.2. Authenticator Configuration Operations Upon Power Up

pinTokenとは

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

sharedSecretとは

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

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

image.png

sharedSecretの作り方

仕様書では5.5.4. Getting sharedSecret from Authenticatorに書いてあります。

  • Authenticator側:ECDH P-256 キーペアを生成します。(AuthenticatorKeyAgreementKey)
  • Platform側:ECDH P-256 キーペアを生成します。(PlatformKeyAgreementKey)
  • AuthenticatorKeyAgreementKeyの公開鍵aGとPlatformKeyAgreementKeyの公開鍵bGを互いに交換します。(両者aGbGを持つ)
  • 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

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

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

(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" 
}

(2)PlatformKeyAgreementKeyを生成する

前述の方法で自分自身のKeyAgreement(キーペア)を生成します。
ここで生成したキーペアの公開鍵を次に送るauthenticatorClientPINコマンドの0x03:keyAgreementに設定します

(3)sharedSecretを生成する

前述の方法で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とする

パラメータ 0x05:newPinEnc

pinを暗号化したもの。

5.5.5. Setting a New PIN

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

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) であり、サンプルreset.exeもあるのですが、私の持っている青いYubikeyには効きませんでした。

fido_reset: FIDO_ERR_NOT_ALLOWED (0x30)

途方に暮れてwebを彷徨っていると素晴らしい情報が
まんぷくになった YubiKey を YubiKey Manager でリセットしたメモ

Yubikey Manager なる公式ツールでリセットすると初期化できました。

おつかれさまでした

shredSecretのところでかなりハマった・・・

参考

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
ユーザーは見つかりませんでした