#はじめに
これはCTAPのお勉強をしたメモです。
WebAuthn(ウェブオースン)ではなく、CTAP(シータップ)であります。
随時修正します。
#3では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 をみてください。
#認証(Authentication)
- webauthn でいうところの
navigator.credentials.get()
がこのコマンドに対応しています。 - CTAP仕様書は 5.2. authenticatorGetAssertion (0x02)、6.1.Commands あたりを参照。
※絵はこちらからピックアップさせていただきました。
https://developer.mozilla.org/ja/docs/Web/API/Web_Authentication_API
#目次
#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}
#2. authenticatorGetAssertion(0x02) コマンド
Yubikey(Authenticator)内のクレデンシャル情報を取得するコマンドです。
クレデンシャル情報とは、秘密鍵を持っていることを証明する情報で、Assertionと名付けられています。
サンプルプログラムでは 以下のパラメータを指定しています。
- rpid
- clientDataHash
- allowList
- options
// 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}
###ざっくりコマンド解説
ここで書いたように、クレデンシャルは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を参照。
clientDataHash⇒いわゆるチャレンジです。クレデンシャルを特定するためのパラメータではなく、GETしたクレデンシャルをVerifyするためのパラメータです。
###rpid
Relying Party Identifierってことで、要はサーバーのドメインを指定します。
authenticatorMakeCredentialの時に指定したものを指定する必要があります。
試しに、"localhost"を"gebogebo.com"に変えてコマンドを送ってみると。
FIDO_ERR_INVALID_CREDENTIAL (0x22)
となり、エラーとなります。
rpidに問題がある、といった意味合いのエラーは定義されてないのか、ハマりそうだ・・・
###clientDataHash
これは #3 と同じ要領で作成するのである、という理解が正解なのだけれども、もっとシンプルに考えて、乱数をSHA256(32byte)したchallenge、ということでも問題ないんだろうと思います。(CTAP的には)。
それで、Assertionにはこのchallengeの署名が返ってくるので、それをVerifyして真正性を確認するのです。
###allowList
AllowListクレデンシャルを選択するためのパラメータです。
仕様書にはallowCredentialDescriptorList
と書いてあるのですが、どんな形式で何をしていするのか、明確な記載が見つかりません…
とりあえず仕様書のEXAMPLEによるとこんな感じ
で、サンプルプログラムでも
"type":"public-key"
⇒固定値
"id"
⇒CredentialIDのバイナリデータをHEX変換した文字列
をつっこんでいます。
配列で複数個指定可能なのはallowListという名前で分かるのですが、クレデンシャルIDを指定するのに、typeにpublic-keyを指定するのが理解不能…
###options
動作オプションです。
仕様書よりピックアップ
- サンプル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と名付けられています。
Assertionは以下のデータから構成されます。
- credential
- authData
- signature
CBORデコードしたAssertion
{
// 0x01:credential
1: {
"id": h'C52D9FDEBA951F4ACA94C2CB7DCFB47B13D0E4D676E2491F90D081D65E6995C74A77828BF8DFE239FC1D62D93DDB547827C524A2A3C035A27D4041DB8352F87E',
"type": "public-key"
},
// 0x02:authData
2: h'49960DE5880E8C687434170F6476605B8FE4AEB9A28632C7995CF3BA831D97630000000068',
// 0x03:signature
3: h'3044022065E5E681837AEDE320D601BE7F82533976F437A5A1066B12EB4D1CAE870E7C32022035E160160AE4B014B81EDA0C264F1F8863A5D5C600EE8B482AB10E52DAC690CB'
}
##Assertionについて
#3のAttestationに比べたらだいぶマシです。
取得したクレデンシャル情報です。
###credential
Credential ID。GetしたクレデンシャルのIDですね。
送信したコマンドのAllowListで指定したものと同じです。希望したものがとれたということで。
###authData
#3 とフォーマットは同じですが、Attested credential data部が無く、以下の情報のみです。
- rpIdHash
- flags
- signCount
###signature
署名です。Verifyに使います。
#4.Verify(検証)
さて、Verifyです。
Assertionが正しいものかどうか、Verifyします。
基本的には#3と同じです。安心しました(?)
- Verify-1.Assertionに含まれるflagsを検証
- Verify-2.送信したRPIDとAssertionに含まれるRPIDハッシュを検証
- Verify-3.署名検証
Verify-1とVerify-2は #3 と同じなので省略
##Verify-3.署名検証
※大まかには#3と同じです。
SigVerifyに必要なデータは
- Sig
- Verify用Public key
- 署名対象データ
この3つがそろえばVerifyできます。
今回もコマンドでVerifyをやってみてお勉強したいと思います。
以下の図はWebauthn仕様書。(こうやって署名されているよ、ってことです)
###◆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で作成したものです。
-
cred_pubkey.pem
というファイル名で持っています。
###◆署名対象データ
authenticatorData + clientDataHash ⇒ 署名対象データ
ですね。
#3と同じ要領で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
#おつかれさまでした
基本的なところはなんとなくひととおりわかったきがする。