#はじめに
これはCTAPのお勉強をしたメモです。
WebAuthn(ウェブオースン)ではなく、CTAP(シータップ)であります。
随時修正します。
ここまではサンプルプログラムを使ってCTAPのHIDコマンド仕様を理解しました。
- HID通信のパケットフォーマット
- CTAPHIDコマンド仕様
- CID取得シーケンス
今回はクレデンシャルを生成するコマンドのお勉強メモです。
※今回からOpenSSLを使っています。
OpenSSL 1.1.0h 27 Mar 2018
https://slproweb.com/products/Win32OpenSSL.html
#環境
- OS=Windows10 1803
- 開発環境=Visual Studio Pro 2017(15.8.5)
- Microsoft Visual C++ 2017
- Yubico セキュリティキー(青)
※キーはリセットしてPINが設定されていないものを使います、PINを設定してしまうと、pinAuthの話をしないといけなくて、その話をするとすごく長くなるのでやめています。pinAuthの話は #5 - PIN をみてください。
#クレデンシャルの生成
- webauthn でいうところの navigator.credentials.create() がこのコマンドに対応しています。
- CTAP仕様書は 5.1 authenticatorMakeCredential(0x01)、6.1.Commands あたりを参照。
※絵はこちらからピックアップさせていただきました。
https://developer.mozilla.org/ja/docs/Web/API/Web_Authentication_API
目次
- サンプルプログラム概要
- authenticatorMakeCredential(0x01) コマンド
- Attestation(Response)
- Verify(検証)
- エクスポート
- authenticatorMakeCredentialオプション
1. サンプルプログラム概要(cred.exe)
cred.exeの処理シーケンスです。
GitHubで公開されているソースはこちら
2. authenticatorMakeCredential(0x01) コマンド
クレデンシャル生成コマンドです。
サンプルプログラムでは 以下のパラメータを指定しています。
- clientDataHash
- rp
- user
- pubKeyCredParams
{
// 0x01 : clientDataHash
1: h'F96457E72D97F6BBDDD7FB063762EA2620448E697C03F2312F99DCAF3E8A916B',
// 0x02 : rp
2: {
"id": "localhost",
"name": "sweet home localhost"
},
// 0x03 : user
3: {
"id": h'781C7860AD88D26332622AF1745DEDB2E7A42B44892939C5566401270DBBC449',
"name": "john smith",
"displayName": "jsmith"
},
// 0x04 : pubKeyCredParams
4: [{"alg": -7, "type": "public-key"}]
}
これらのパラメータには何を指定すればいいのでしょうか。
clientDataHash
clientDataHashとは何か?
仕様書でみると・・・
5.1. authenticatorMakeCredential (0x01)
Hash of the ClientData contextual binding specified by host. See WebAuthN.
⇒WebAuthNの仕様を見ろ
でWebAuthNのリンクを見ると
5.1.3. Create a new credential - PublicKeyCredential’s [[Create]](origin, options,sameOriginWithAncestors) method
15.Let clientDataHash be the hash of the serialized client data represented by clientDataJSON.
⇒clientDataHash とは clientDataJSONのハッシュ である
clientDataJSONとは何か?
5.10.1. Client data used in WebAuthn signatures (dictionary CollectedClientData)
JSON-serialized client data
This is the result of JSON stringifying and UTF-8 encoding to bytes a CollectedClientData dictionary.Hash of the serialized client data
This is the hash (computed using SHA-256) of the JSON-serialized client data, as constructed by the client.
⇒clientDataJSON とは JSONにしたClient Dataである
でClient Dataとは?
5.10.1. Client data used in WebAuthn signatures (dictionary CollectedClientData)
dictionary CollectedClientData {
required DOMString type;
required DOMString challenge;
required DOMString origin;
TokenBinding tokenBinding;
};
- type : "webauthn.create" 、 "webauthn.get" のいずれか
- challenge : RPで生成したチャレンジ(乱数)をbase64urlでエンコードしたもの
- origin : いわゆるオリジン。プロトコル+ドメイン+ポート番号。例=https://example.com:80
- tokenBinding : オプション と書いてあるので無視しよう(ちゃんと理解するのはめんどくさそうだ)
要するに、こういうもの?
{
"type": "webauthn.create"
"challenge": "IHWmZ1OkS2t6KhvX-koNxutkYuMVEunCjYNSXXgAxvU",
"origin": "https://gebogebo.com:8080",
}
つまりこのJSONをSHA-256のハッシュしたものなのだろうか・・・
・・・いまひとつ理解できないので、サンプルソースを見ると
static const unsigned char cdh[32] = {
0xf9, 0x64, 0x57, 0xe7, 0x2d, 0x97, 0xf6, 0xbb,
0xdd, 0xd7, 0xfb, 0x06, 0x37, 0x62, 0xea, 0x26,
0x20, 0x44, 0x8e, 0x69, 0x7c, 0x03, 0xf2, 0x31,
0x2f, 0x99, 0xdc, 0xaf, 0x3e, 0x8a, 0x91, 0x6b,
};
固定値を送信している
clientDataHashについてまとめると
- いつ(challenge)、だれが(origin)、何の目的で(type)という情報を詰めたコマンドのIDみたいなもの。
- clientDataHashはAttestationの署名(sig)対象。なので、Verify(真正性)を確認するためのもの。
- とりあえず仕様書に書いてある通りのJSONのハッシュをいれとけ。
- でもハッシュなんで、SHA256のハッシュだったら元データはなんでもいいですね。自己責任で。
rp
ここで指定している rp.id(このサンプルでは localhost
)が生成されるクレデンシャルに紐づけられます。
- このサンプルではidとnameを指定していますが、idだけがYubikeyで使われて、nameは捨てられているようです。(仕様ではnameはOptionです)
- ここで指定したidのハッシュがAttestationに含まれます。
- すでに登録済みのrp.idを指定しても、特にエラーにはなりませんでした。
user
生成するクレデンシャルに紐づけられるUser情報を指定します。
クレデンシャルに紐づけられるので、Yubikey内のメモリに保存される情報です。
ただし、保存させるためには条件があって、以下のオプションを指定する必要があります。
- optionsでrk=trueとする
- resident key:Instructs the authenticator to store the key material on the device
- pinAuthを指定する
- pinAuth:HMAC-SHA-256(pinToken, clientDataHash).
※オプションの詳細は⇒6.authenticatorMakeCredentialオプション
上記設定をして、YubikeyのメモリにUserDataを保存すると、認証に(GetAssertionで)取り出すことができます。⇒CTAP2 お勉強メモ#4 - 認証(Authentication)
このuserというパラメータが曲者で、仕様書的には Required となっており、user.idが指定されていないと必ずコマンドエラーになります。
で、ありますため、UserDataを保存する必要がないケースが多いとは思いますが、userパラメータは必ず指定する必要があります。
※このサンプルではidとname,displayNameを指定していますが、idだけあればいいようです。
pubKeyCredParams
生成するクレデンシャルのパラメータ
- type : "public-key" を指定する(固定値)
- alg : Attestationに入る公開鍵の暗号化アルゴリズムを指定する。IANA-COSE-ALGS-REG で定義されている値。このサンプルでは -7 なので ES256 という意味。
3. Attestation
コマンドのResponseのことです。仕様書等を見るとAttestationと名付けられています。
Attestationは以下のデータから構成されます。
- fmt
- authData
- attStmt
CBORデコードしたAttestation
{
// 0x01 : fmt
1: "packed",
// 0x02 : authData
2: h'49960DE5880E8C687434170F6476605B8FE4AEB9A28632C7995CF3BA831D97634100000062F8A011F38C0A4D15800617111F9EDC7D0040A6678646A6EA606666BE1B62607A6690CB5EBDA029F5431214B113E2DC6F3C0DF69E2042E76954A7C2233D7DC0CB6E3097B944E20782C3961C654E784F157EC0A501020326200121582003F4209E13CB29D16C86D90200C66E1CD7FFC1000D01E9FE74E95F9EC4BD08F1225820BF6AAFF13F6D6B0EE187C0BCE01A471239ECC87715C68270C95CAF61528C8826',
// 0x03 : attStmt
3: {
"alg": -7,
"sig": h'3045022100F425430C22FAB29DE711FEF031F07B8A4024D39328BF7741BCD3E9004506B4560220403933D7937F5181DA26E277D350C1846E87F7A05B267216A4B8BEF67004FD29',
"x5c": [h'308202BE308201A6A00302010202047486FDC2300D06092A864886F70D01010B0500302E312C302A0603550403132359756269636F2055324620526F6F742043412053657269616C203435373230303633313020170D3134303830313030303030305A180F32303530303930343030303030305A306F310B300906035504061302534531123010060355040A0C0959756269636F20414231223020060355040B0C1941757468656E74696361746F72204174746573746174696F6E3128302606035504030C1F59756269636F205532462045452053657269616C20313935353030333834323059301306072A8648CE3D020106082A8648CE3D03010703420004955DF3ADF7247D3175EFFD9CC4F31A4E878EBAE18109566150FB388B2E5F6527BF57409AA581A50D0AC52F18445C0A13548A1353C8A4E59A704E523BC04DEBEDA36C306A302206092B0601040182C40A020415312E332E362E312E342E312E34313438322E312E313013060B2B0601040182E51C0201010404030205203021060B2B0601040182E51C01010404120410F8A011F38C0A4D15800617111F9EDC7D300C0603551D130101FF04023000300D06092A864886F70D01010B05000382010100315C4880E69A527E386689BD69FD0AA86F49EB9E4E854541556FAAD00B3A008A1DDC01F96C76F668361A91E232C810A79C63074C9B6E7A46EB1DB5D85C44489F868A7643D22A5C862EC03F03E5848BE3807D7ACD55F8E1AE1EE213AC73AB4B20E3FBD5268CB07B8780271D1F4BE0E5DDAC734D3A5897BD4D73BA7F357EA208C99D8A4D2902E6097A005C4DC904DC0A18120E0AF7D00CFC969A2886E5B1B161F3EDCBC677A678D7FB53039CCDA186BE34BA53319523439D7FD94A70F230621B93C4CE4268D3174D943BC6AE3FC937C2DE43D6B44E21153DF850925F9590622EBC46E0EB18C641F0FE7E6F2A09A9B2907719F62E6135A19032A213C098B7283CEE']
}
}
##Attestationについて
かなり複雑なので、重要なことを最初にまとめます。
- コマンドで指定されたrpidをキーにしてクレデンシャルが生成されます。
- つまり、rpid毎にクレデンシャルが生成されていることになります。
- クレデンシャルとは、つまり認証用の秘密鍵のことです。秘密鍵はAuthenticator(Yubikey)内に保管され、取り出すことはできません。
- 認証用秘密鍵に対応する**認証用の公開鍵(Public Key)**がAttestationで返ってきます。Public KeyはauthDataに入っています。
- クレデンシャルのキーとなる情報がCredentialIDです。CredentialIDはauthDataに入っています。
- これらCredentialID、認証用公開鍵は、以降の認証で利用するために保管すべきものです。
- Attestationには、署名(Sig)が含まれています。これはAttestationをVerifyする(真正性を確認する)ためのデータです。
####重要なデータ
- CredentialID
- Credential Public Key
- Sig
###fmt
attStmt 部のフォーマット定義
webauthn仕様によると
packed , tpm , android-key , android-safetynet , fido-u2f , none
があります。
Yubikeyはpackedとなるようです。
###authData(Authenticator data)
フォーマットは以下
※詳細は Webauthn 仕様書 6.1. Authenticator data
Authenticator data
定義 | byte | 説明 | サンプル |
---|---|---|---|
rpIdHash | 32 | コマンドで指定したrp-IDのSHA-256ハッシュ。応答の検証用 | 49960DE5880E8C687434170F6476605B8FE4AEB9A28632C7995CF3BA831D9763 つまり localhost という文字列の SHA-256ハッシュ。コチラで確認できます。 |
flags | 1 | ※flagsについて を参照 | 0x41 -> bit:0100-0001 |
signCount | 4 | カウンタ。なんかのタイミングでカウントアップしていきます。 | 0x00000062 -> dec:98 |
ここから Attested credential data | |||
aaguid | 16 | デバイス(Yubikey)のID。authenticatorGetInfo (0x04)でも取れる | F8A011F38C0A4D15800617111F9EDC7D |
credentialIdLength | 2 | credentialIdフィールドのバイト数 | 0x0040 -> dec:64 |
credentialId | credentialIdLength | 重要:生成されたクレデンシャルのID。これを保管しておいて認証のときに指定する | A6678646A6EA606666BE1B62607A6690CB5EBDA029F5431214B113E2DC6F3C0DF69E2042E76954A7C2233D7DC0CB6E3097B944E20782C3961C654E784F157EC0 |
credentialPublicKey | x | ※credentialPublicKeyについて を参照 |
※flagsについて
- Bit 0: User Present (UP) result.1 means the user is present.0 means the user is not present.
- Bit 1: Reserved for future use (RFU1).
- Bit 2: User Verified (UV) result.1 means the user is verified.0 means the user is not verified.
- Bits 3-5: Reserved for future use (RFU2).
- Bit 6: Attested credential data included (AT).Indicates whether the authenticator added - attested credential data.
- Bit 7: Extension data included (ED).Indicates if the authenticator data has extensions.
####※credentialPublicKeyについて
- 認証用公開鍵。
- CBORデコードすると見えるようになります。
- 公開鍵なのですが、
COSE
というフォーマットになっています。 -
COSE
とは JWK(暗号鍵を表現するためのJSON)をCBORエンコードするフォーマットのようです。
// CBORエンコードされたサンプル
A501020326200121582003F4209E13CB29D16C86D90200C66E1CD7FFC1000D01E9FE74E95F9EC4BD08F1225820BF6AAFF13F6D6B0EE187C0BCE01A471239ECC87715C68270C95CAF61528C8826
// CBORデコードした結果
{
1: 2,
3: -7,
-1: 1,
-2: h'03F4209E13CB29D16C86D90200C66E1CD7FFC1000D01E9FE74E95F9EC4BD08F1',
-3: h'BF6AAFF13F6D6B0EE187C0BCE01A471239ECC87715C68270C95CAF61528C8826'
}
あたりを参考にしてJWKにしてみます。
{
"kty":"EC",
"alg":"ES256",
"crv":"P-256",
"x":"03F4209E13CB29D16C86D90200C66E1CD7FFC1000D01E9FE74E95F9EC4BD08F1",
"y":"BF6AAFF13F6D6B0EE187C0BCE01A471239ECC87715C68270C95CAF61528C8826"
}
これを見るとYubiKey から受け取った PublicKey(認証用公開鍵) は ECという楕円曲線暗号鍵 であることがわかります。
JWK-EC鍵の取り扱いは情報が少なく、取り扱い方法がよくわかりません…
サンプルプログラムではエクスポートしていますが、どうやっているのか理解できません。
認証用公開鍵のエクスポートについては後述。
###attStmt(attestation statement)
生成されたAttestationをVerifyするためのデータです。
3: {
"alg": -7,
"sig": h'3045022100F4...',
"x5c": [h'308202BE308201...']
}
alg
A COSEAlgorithmIdentifier containing the identifier of the algorithm used to generate the attestation signature.
- sigの暗号化アルゴリズム。
- -7 なので ES256 という意味。⇒IANA-COSE-ALGS-REG
sig
A byte string containing the attestation signature.
- 署名
- Verifyで使います
x5c
The elements of this array contain attestnCert and its certificate chain, each encoded in X.509 format. The attestation certificate attestnCert MUST be the first element in the array.
JWKの仕様に詳しい説明あり⇒[4.7. "x5c" (X.509 Certificate Chain) Parameter](https://openid-foundation-japan.github.io/rfc7517.ja.html#x5cDef)
4.7. "x5c" (X.509 Certificate Chain) Parameter
x5c (X.509 certificate chain) パラメータは, 1つ以上の PKIX 証明書 [RFC5280] からなるチェーンを示す.
証明書チェーンは証明書文字列の JSON 配列として表現される.
それぞれの証明書文字列は base64 エンコード ([RFC4648] Section 4 参照, base64url エンコードではない) された DER 形式の [ITU.X690.1994] PKIX 証明書である.
当該 JWK の鍵を含む PKIX 証明書は先頭の証明書でなければならない (MUST).
その後には証明書を続けることもでき (MAY), その場合, 各証明書はその一つ前の証明書を証明する.
先頭の証明書に含まれる鍵は当該 JWK の示す公開鍵と一致すること (MUST).
この要素の利用は OPTIONAL である.
x5u 同様, x5c とともに鍵用途やアルゴリズムといったその他の情報も同じ JWK に含める事ができる (MAY).
他の要素が含まれる場合, それらは先頭の証明書との整合性をとること. 詳細は Section 4.6 の最後の一文を参照.
- X.509証明書チェーン(配列)
- ANSI X9.62 Public Key Format というバイナリ形式
- PEMではなく、DER 形式
- 詳細はこちら
- これもVerifyで使います
4. Verify(検証)
Attestationが問題ないか真正性を確認する必要があります。ちゃんと検証しましょう。
仕様書による検証方法(Verification procedure)を見ると...
- Verification procedure
- x5cが存在する場合、これはアテステーションタイプがECDAAではないことを示します。この場合:
- algで指定されたアルゴリズムを使用して、attestnCertのアテステーション公開鍵を使用して、
sig
がauthenticatorData
とclientDataHash
の連結に対して有効なシグネチャであることを確認します。 - attestnCertが§8.2.1パックドアテステーションステートメントの証明書要件の要件を満たしていることを確認します。
- attestnCertにOID 1.3.6.1.4.1.45724.1.1.4(id-fido-gen-ce-aaguid)の拡張子が含まれている場合、この拡張子の値がauthenticatorDataのaaguidと一致することを確認します。
- 成功した場合は、アテステーションタイプBasicとアテステーショントラストパスx5cを返します。
- algで指定されたアルゴリズムを使用して、attestnCertのアテステーション公開鍵を使用して、
###Verification procedureをgoogle翻訳しても全く分からない!
のでサンプルプログラムをひたすら読んだ結果。
- Verify-1.Attestationに含まれるflagsを検証
- Verify-2.送信したRPIDとAttestationに含まれるRPIDハッシュを検証
- Verify-3.署名(sig)検証
##Verify-1
User Verificationのチェックを行います。
送信したコマンドのオプションUVとAttestationに含まれるUVフラグ(flags.Bit2)が一致するかチェックします。
- コマンドのオプションUVについては仕様書のこの辺に書いてあります。
- サンプルcred.exeではデフォルトオプションなしですが、-vオプションでuvを有効にして実行することができます。
- 私の環境では-vでUser Verificationを有効にして実行してもflags.Bit2がtrueになることはありませんでした。PINの設定とかが必要なのでしょうか。またの機会に調査とします。
##Verify-2
送信したRPIDとAttestationに含まれるRPIDハッシュを検証します。
authData(Authenticator data)のrpIdHash(32byte)。これはコマンドで指定したrpのid値のSHA256ハッシュです。
コチラのサイトで確認すると確かにOKです。
rpIdHash = 49960DE5880E8C687434170F6476605B8FE4AEB9A28632C7995CF3BA831D9763
"localhost" ⇒ SHA256 ⇒ 49960DE5880E8C687434170F6476605B8FE4AEB9A28632C7995CF3BA831D9763
-->OK!
##Verify-3
署名(sig)検証です。大仕事です。
SigVerifyに必要なデータは
- Sig
- Verify用Public key
- 署名対象データ
この3つがそろえばVerifyできます。
サンプルプログラムではOpenSSLのSHA256_系関数を使っていますが、同じ事をコマンドでやってみてお勉強したいと思います。
###◆Sig
Sigをファイルに保存します。
####Sig-1. ダンプログからSigを取り出しテキストファイルに保存します。
sig.hexの内容
30 45 02 21 00 f4 25 43 0c 22 fa b2 9d e7 11 fe
f0 31 f0 7b 8a 40 24 d3 93 28 bf 77 41 bc d3 e9
00 45 06 b4 56 02 20 40 39 33 d7 93 7f 51 81 da
26 e2 77 d3 50 c1 84 6e 87 f7 a0 5b 26 72 16 a4
b8 be f6 70 04 fd 29
####Sig-2. certutilコマンドでバイナリファイルに変換します。
C:\work>certutil -f -decodehex sig.hex sig.bin 4
入力長 = 216
出力長 = 71
CertUtil: -decodehex コマンドは正常に完了しました。
###◆Verify用Public key
Verify用Public keyを証明書から取り出します。
証明書とはattStmt.x5c[0]のことです。
x5cはX509.DER証明書の配列です。レスポンスにあるx5cは要素1個のHEXデータ配列です。この要素一つ分がDER証明書です。
####Public Key-1. x5cからDER証明書1個分を取り出してテキストファイルに保存します。
x5c.hexの内容
30 82 02 be 30 82 01 a6 a0 03 02 01 02 02 04 74
86 fd c2 30 0d 06 09 2a 86 48 86 f7 0d 01 01 0b
05 00 30 2e 31 2c 30 2a 06 03 55 04 03 13 23 59
75 62 69 63 6f 20 55 32 46 20 52 6f 6f 74 20 43
41 20 53 65 72 69 61 6c 20 34 35 37 32 30 30 36
33 31 30 20 17 0d 31 34 30 38 30 31 30 30 30 30
30 30 5a 18 0f 32 30 35 30 30 39 30 34 30 30 30
30 30 30 5a 30 6f 31 0b 30 09 06 03 55 04 06 13
02 53 45 31 12 30 10 06 03 55 04 0a 0c 09 59 75
62 69 63 6f 20 41 42 31 22 30 20 06 03 55 04 0b
0c 19 41 75 74 68 65 6e 74 69 63 61 74 6f 72 20
41 74 74 65 73 74 61 74 69 6f 6e 31 28 30 26 06
03 55 04 03 0c 1f 59 75 62 69 63 6f 20 55 32 46
20 45 45 20 53 65 72 69 61 6c 20 31 39 35 35 30
30 33 38 34 32 30 59 30 13 06 07 2a 86 48 ce 3d
02 01 06 08 2a 86 48 ce 3d 03 01 07 03 42 00 04
95 5d f3 ad f7 24 7d 31 75 ef fd 9c c4 f3 1a 4e
87 8e ba e1 81 09 56 61 50 fb 38 8b 2e 5f 65 27
bf 57 40 9a a5 81 a5 0d 0a c5 2f 18 44 5c 0a 13
54 8a 13 53 c8 a4 e5 9a 70 4e 52 3b c0 4d eb ed
a3 6c 30 6a 30 22 06 09 2b 06 01 04 01 82 c4 0a
02 04 15 31 2e 33 2e 36 2e 31 2e 34 2e 31 2e 34
31 34 38 32 2e 31 2e 31 30 13 06 0b 2b 06 01 04
01 82 e5 1c 02 01 01 04 04 03 02 05 20 30 21 06
0b 2b 06 01 04 01 82 e5 1c 01 01 04 04 12 04 10
f8 a0 11 f3 8c 0a 4d 15 80 06 17 11 1f 9e dc 7d
30 0c 06 03 55 1d 13 01 01 ff 04 02 30 00 30 0d
06 09 2a 86 48 86 f7 0d 01 01 0b 05 00 03 82 01
01 00 31 5c 48 80 e6 9a 52 7e 38 66 89 bd 69 fd
0a a8 6f 49 eb 9e 4e 85 45 41 55 6f aa d0 0b 3a
00 8a 1d dc 01 f9 6c 76 f6 68 36 1a 91 e2 32 c8
10 a7 9c 63 07 4c 9b 6e 7a 46 eb 1d b5 d8 5c 44
48 9f 86 8a 76 43 d2 2a 5c 86 2e c0 3f 03 e5 84
8b e3 80 7d 7a cd 55 f8 e1 ae 1e e2 13 ac 73 ab
4b 20 e3 fb d5 26 8c b0 7b 87 80 27 1d 1f 4b e0
e5 dd ac 73 4d 3a 58 97 bd 4d 73 ba 7f 35 7e a2
08 c9 9d 8a 4d 29 02 e6 09 7a 00 5c 4d c9 04 dc
0a 18 12 0e 0a f7 d0 0c fc 96 9a 28 86 e5 b1 b1
61 f3 ed cb c6 77 a6 78 d7 fb 53 03 9c cd a1 86
be 34 ba 53 31 95 23 43 9d 7f d9 4a 70 f2 30 62
1b 93 c4 ce 42 68 d3 17 4d 94 3b c6 ae 3f c9 37
c2 de 43 d6 b4 4e 21 15 3d f8 50 92 5f 95 90 62
2e bc 46 e0 eb 18 c6 41 f0 fe 7e 6f 2a 09 a9 b2
90 77 19 f6 2e 61 35 a1 90 32 a2 13 c0 98 b7 28
3c ee
####Public Key-2. certutilコマンドでバイナリに変換します。こうするとDER形式の証明書になります。
C:\work>certutil -f -decodehex x5c.hex x5c.der 4
入力長 = 2161
出力長 = 706
CertUtil: -decodehex コマンドは正常に完了しました。
####Public Key-3. opensslコマンドでPEM形式に変換します
C:\work>openssl x509 -in x5c.der -inform DER -out x5c.pem -outform PEM
####Public Key-4. public keyを取り出します
C:\work>openssl x509 -in x5c.pem -pubkey -noout>x5c_pub_key.pem
x5.pemがどんな証明書なのか見てみる
opensslコマンドでテキスト形式に変換します
Issuer,Subjectを見てみるとそれっぽいですね。
c:\work>openssl x509 -text -fingerprint -noout -in x5c.pem > x5c.txt
c:\work>type x5c.txt
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 1955003842 (0x7486fdc2)
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN = Yubico U2F Root CA Serial 457200631
Validity
Not Before: Aug 1 00:00:00 2014 GMT
Not After : Sep 4 00:00:00 2050 GMT
Subject: C = SE, O = Yubico AB, OU = Authenticator Attestation, CN = Yubico U2F EE Serial 1955003842
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:95:5d:f3:ad:f7:24:7d:31:75:ef:fd:9c:c4:f3:
1a:4e:87:8e:ba:e1:81:09:56:61:50:fb:38:8b:2e:
5f:65:27:bf:57:40:9a:a5:81:a5:0d:0a:c5:2f:18:
44:5c:0a:13:54:8a:13:53:c8:a4:e5:9a:70:4e:52:
3b:c0:4d:eb:ed
ASN1 OID: prime256v1
NIST CURVE: P-256
X509v3 extensions:
1.3.6.1.4.1.41482.2:
1.3.6.1.4.1.41482.1.1
1.3.6.1.4.1.45724.2.1.1:
...
1.3.6.1.4.1.45724.1.1.4:
.......
M........}
X509v3 Basic Constraints: critical
CA:FALSE
Signature Algorithm: sha256WithRSAEncryption
31:5c:48:80:e6:9a:52:7e:38:66:89:bd:69:fd:0a:a8:6f:49:
eb:9e:4e:85:45:41:55:6f:aa:d0:0b:3a:00:8a:1d:dc:01:f9:
6c:76:f6:68:36:1a:91:e2:32:c8:10:a7:9c:63:07:4c:9b:6e:
7a:46:eb:1d:b5:d8:5c:44:48:9f:86:8a:76:43:d2:2a:5c:86:
2e:c0:3f:03:e5:84:8b:e3:80:7d:7a:cd:55:f8:e1:ae:1e:e2:
13:ac:73:ab:4b:20:e3:fb:d5:26:8c:b0:7b:87:80:27:1d:1f:
4b:e0:e5:dd:ac:73:4d:3a:58:97:bd:4d:73:ba:7f:35:7e:a2:
08:c9:9d:8a:4d:29:02:e6:09:7a:00:5c:4d:c9:04:dc:0a:18:
12:0e:0a:f7:d0:0c:fc:96:9a:28:86:e5:b1:b1:61:f3:ed:cb:
c6:77:a6:78:d7:fb:53:03:9c:cd:a1:86:be:34:ba:53:31:95:
23:43:9d:7f:d9:4a:70:f2:30:62:1b:93:c4:ce:42:68:d3:17:
4d:94:3b:c6:ae:3f:c9:37:c2:de:43:d6:b4:4e:21:15:3d:f8:
50:92:5f:95:90:62:2e:bc:46:e0:eb:18:c6:41:f0:fe:7e:6f:
2a:09:a9:b2:90:77:19:f6:2e:61:35:a1:90:32:a2:13:c0:98:
b7:28:3c:ee
SHA1 Fingerprint=B5:31:20:2B:8F:88:8D:97:36:08:3E:40:F8:07:85:47:36:E1:A2:7D
x5c_pub_key.pemがどんな公開鍵なのか見てみる
c:\work>openssl ec -pubin -in x5c_pub_key.pem -text -noout >x5c_pub_key_ec.txt
read EC key
c:\work>type x5c_pub_key_ec.txt
Public-Key: (256 bit)
pub:
04:95:5d:f3:ad:f7:24:7d:31:75:ef:fd:9c:c4:f3:
1a:4e:87:8e:ba:e1:81:09:56:61:50:fb:38:8b:2e:
5f:65:27:bf:57:40:9a:a5:81:a5:0d:0a:c5:2f:18:
44:5c:0a:13:54:8a:13:53:c8:a4:e5:9a:70:4e:52:
3b:c0:4d:eb:ed
ASN1 OID: prime256v1
NIST CURVE: P-256
###◆署名対象データ
署名対象データは
Attestationに含まれるauthData(Authenticator data)
と
送信コマンドに含まれるclientDataHash
を
連結したデータ
です。
つまり
authenticatorData + clientDataHash ⇒ 署名対象データ
署名対象データ-1.
ダンプログからauthData(196 byte)とclientdataHash(32 byte)を取り出しテキストファイルに保存します。
テキストファイルの内容
49 96 0d e5 88 0e 8c 68 74 34 17 0f 64 76 60 5b
8f e4 ae b9 a2 86 32 c7 99 5c f3 ba 83 1d 97 63
41 00 00 00 62 f8 a0 11 f3 8c 0a 4d 15 80 06 17
11 1f 9e dc 7d 00 40 a6 67 86 46 a6 ea 60 66 66
be 1b 62 60 7a 66 90 cb 5e bd a0 29 f5 43 12 14
b1 13 e2 dc 6f 3c 0d f6 9e 20 42 e7 69 54 a7 c2
23 3d 7d c0 cb 6e 30 97 b9 44 e2 07 82 c3 96 1c
65 4e 78 4f 15 7e c0 a5 01 02 03 26 20 01 21 58
20 03 f4 20 9e 13 cb 29 d1 6c 86 d9 02 00 c6 6e
1c d7 ff c1 00 0d 01 e9 fe 74 e9 5f 9e c4 bd 08
f1 22 58 20 bf 6a af f1 3f 6d 6b 0e e1 87 c0 bc
e0 1a 47 12 39 ec c8 77 15 c6 82 70 c9 5c af 61
52 8c 88 26 f9 64 57 e7 2d 97 f6 bb dd d7 fb 06
37 62 ea 26 20 44 8e 69 7c 03 f2 31 2f 99 dc af
3e 8a 91 6b
署名対象データ-2.certutilでバイナリファイルに変換します。
c:\work>certutil -f -decodehex dgst.hex dgst.bin 4
入力長 = 697
出力長 = 228
CertUtil: -decodehex コマンドは正常に完了しました。
###◆Verify!
Sig、Verify用Public key、署名対象データ がそろったらいよいよVerifyです。
コマンド1発です。
c:\work>openssl dgst -sha256 -verify x5c_pub_key.pem -signature sig.bin dgst.bin
Verified OK
以下の方法で検証しています。
- 署名対象データ(dgst.bin)のSHA256ダイジェストを取得(x)
- 秘密鍵で暗号化されているSig(sig.bin)をVerify用Public key(x5c_pub_key.pem)で復号する(y)
- xとyが同じであればVerifyOK
5. エクスポート
##認証用Public Keyのエクスポート
cred.exe の -k [保存先パス&ファイル名] で認証用Public Keyをエクスポートします。
-----BEGIN PUBLIC KEY-----
MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA
AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA////
///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd
NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5
RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA
//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABKBFivh7MFMtngVl0AEPqxg8
VSl542R1CLxfAq2J31Rf50dbB8oCvmeLd9RMNS4uQYOlIdfbTmqOYJWZcnWvvUA=
-----END PUBLIC KEY-----
中を見てみます
c:\work>openssl ec -pubin -in pubkey.pem -text
read EC key
Public-Key: (256 bit)
pub:
04:a0:45:8a:f8:7b:30:53:2d:9e:05:65:d0:01:0f:
ab:18:3c:55:29:79:e3:64:75:08:bc:5f:02:ad:89:
df:54:5f:e7:47:5b:07:ca:02:be:67:8b:77:d4:4c:
35:2e:2e:41:83:a5:21:d7:db:4e:6a:8e:60:95:99:
72:75:af:bd:40
Field Type: prime-field
Prime:
00:ff:ff:ff:ff:00:00:00:01:00:00:00:00:00:00:
00:00:00:00:00:00:ff:ff:ff:ff:ff:ff:ff:ff:ff:
ff:ff:ff
A:
00:ff:ff:ff:ff:00:00:00:01:00:00:00:00:00:00:
00:00:00:00:00:00:ff:ff:ff:ff:ff:ff:ff:ff:ff:
ff:ff:fc
B:
5a:c6:35:d8:aa:3a:93:e7:b3:eb:bd:55:76:98:86:
bc:65:1d:06:b0:cc:53:b0:f6:3b:ce:3c:3e:27:d2:
60:4b
Generator (uncompressed):
04:6b:17:d1:f2:e1:2c:42:47:f8:bc:e6:e5:63:a4:
40:f2:77:03:7d:81:2d:eb:33:a0:f4:a1:39:45:d8:
98:c2:96:4f:e3:42:e2:fe:1a:7f:9b:8e:e7:eb:4a:
7c:0f:9e:16:2b:ce:33:57:6b:31:5e:ce:cb:b6:40:
68:37:bf:51:f5
Order:
00:ff:ff:ff:ff:00:00:00:00:ff:ff:ff:ff:ff:ff:
ff:ff:bc:e6:fa:ad:a7:17:9e:84:f3:b9:ca:c2:fc:
63:25:51
Cofactor: 1 (0x1)
Seed:
c4:9d:36:08:86:e7:04:93:6a:66:78:e1:13:9d:26:
b7:81:9f:7e:90
-----BEGIN PUBLIC KEY-----
MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA
AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA////
///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd
NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5
RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA
//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABKBFivh7MFMtngVl0AEPqxg8
VSl542R1CLxfAq2J31Rf50dbB8oCvmeLd9RMNS4uQYOlIdfbTmqOYJWZcnWvvUA=
-----END PUBLIC KEY-----
##CredentialIDのエクスポート
cred.exe の -i [保存先パス&ファイル名] でクレデンシャルIDをエクスポートします。
中身はバイナリデータです。
#6. authenticatorMakeCredentialオプション
今回のサンプルでは使っていませんが、いろいろオプションがあります。
⇒5.1. authenticatorMakeCredential (0x01)
###excludeList(0x05)
A sequence of PublicKeyCredentialDescriptor structures, as specified in [WebAuthN]. The authenticator returns an error if the authenticator already contains one of the credentials enumerated in this sequence.
このオプションで指定したcredentialがすでにauthenticatorに登録されていたらエラーにする、ということらしいのですが、どうゆうケースを想定して、どんな形式で指定したらいいのか・・・
###extensions(0x06)
These parameters might be authenticator specific.
メーカー個別の拡張パラメータのようです。
###options(0x07)
こんな感じで指定します。
// 0x07 : options
7: {
"rk": true,
"uv": true
}
rk=trueでuser情報をauthenticatorに保存します。
uv=trueの場合、ユーザー認証が必須、という意味になります。Yubikeyの場合PIN認証してないとダメ、ということになります。つまり、この後に記載されているpinAuth、pinProtocolも必須になります。
###pinAuth(0x08)
HMAC-SHA-256(pinToken, clientDataHash)
PINそのものではなく、PIN認証したよ、という証拠です。
詳細は CTAP2 お勉強メモ#5 - PIN を参照ください。
###pinProtocol(0x09)
1固定です。
// 0x08:pinAuth
8: h'1592DA60B160AAD3450DC0A89F60B03B',
// 0x09:pinProtocol
9: 1
###補足
Yubikeyだけの仕様かもしれませんが
rk=tureにした場合、uv=trueしないとエラーになります。FIDO_ERR_PIN_REQUIRED (0x36)
で、uv=trueのときはpinAuth、pinProtocolを指定しないとエラーになります。FIDO_ERR_PIN_REQUIRED (0x36)
結局rk,uv,PINはセットのオプションみたいです。
上記記載は誤り、正しくは
- YubikeyにPINを設定すると、CreateではPIN(pinAuth、pinProtocol)指定が必須となり、PINを指定しないと
FIDO_ERR_PIN_REQUIRED (0x36)
エラーになる。
です。
#おつかれさまでした
むずかしい・・・
#参考
- Client to Authenticator Protocol (CTAP) Implementation Draft, February 27, 2018
- Yubico/libfido2
- CBOR playground
- Web Authentication: An API for accessing Public Key Credentials Level 1
W3C Candidate Recommendation, 7 August 2018 - SHA256ハッシュ生成ツール
- Web Authentication API で FIDO U2F(YubiKey) 認証
- Qiita:OpenSSLコマンドによる公開鍵暗号、電子署名の方法
- Qiita:RSA鍵、証明書のファイルフォーマットについて
- マイクロソフト系技術情報 Wiki Web Authentication API