nRF52840-DKにOpenSKを入れてUSB FIDOキーにしてみたの続きです。
2020/08/20 追記
本投稿8/16で同日に修正をOpenSKにプルリクエストしたところ即座にマージされたのでこの現象は発生しなくなりました。めっちゃ早い対応!
OpenSKを使って認証するときにセキュリティキーのボタンを押しても反応しない以下の謎がありました。
「セキュリティキーにタッチしてください」のポップアップ画面でキー(nRF52840-DK)のボタンを押しても反応せずキャンセルするしかない
調査してみると以下のような状況です。
-
Windowsでのみ発生する。WindowsであればChromeでもEdgeでも発生する。
-
WebAuthnのデモサイトで現象を確認できる。どこか特定のサイトだけで発生するわけではなくどこでも現象を確認できる。
-
登録は成功するんだけど、認証するときにボタンを押しても反応せず、キャンセルするしかない。
-
OpenSKのissueにもBugで上がっているようだ、英語力がアレなんですがたぶん↓
バグならば仕方ない、そのうち修正されるだろうと思っていたのですが、夏休み暇なので自分で調べてみたという話です。
環境
- ビルド環境
- Mac OS X Catalina 10.15.5
- vscode 1.48.0
- 実行環境
- Windows 10 Pro 64bit 1909
- セキュリティキー
- nRF52840-DK
- OepnSK
どこが怪しいのか
Macだと正常に動作して、Winだとおかしい、ということはOpenSKの問題ではないようにも思います。Webサーバ(RP)かブラウザ(Platform)の問題?。「セキュリティキーにタッチしてください」のポップアップはWebAuthnを実装したブラウザの機能で表示しているのでブラウザが怪しい。OpenSKから受け取ったの認証結果(Assertion)の検証でブラウザが内部的にコケてしまっているのかも知れません。
OpenSKとブラウザでどんなやりとりがされているのか調べてみます。
どこが怪しいのか2
ユーザープログラムがコールするJavaScriptのWebAuthnの認証APIはnavigator.credentials.get()
です。ブラウザはこのAPIを受けて内部でCTAPコマンドをセキュリティキー(OpenSK)に放り投げます。認証で使うCTAPコマンドはauthenticatorClientPIN
,authenticatorGetAssertion
,authenticatorGetNextAssertion
です。今回の問題はPINを検証した後のUserPresenceフェーズで発生しているのでauthenticatorGetAssertion
で何か起こっているようです。
CTAPコマンドの通信内容をトレースする
authenticatorGetAssertion
を見てみましょう。以下のツールを使って通信内容を見ることができます。
- HIDTest01.exeを立ち上げる
- 接続確認
- セキュリティキーをUSBに挿して
GetInfoボタン
をクリック→ログにそれっぽいのが出たら接続OK
- セキュリティキーをUSBに挿して
- 登録
- RPIDは test.com のまま、PINを入力、ResidentKeyは チェックOFF で
MakeCredentialボタン
をクリック→セキュリティキーが光ったらボタンを押す→成功するとCredentialID
が取れる
- RPIDは test.com のまま、PINを入力、ResidentKeyは チェックOFF で
- 認証
- そのまま
GetAssertionボタン
をクリック→セキュリティキーが光ったらボタンを押す - ここでログにでてくる CTAP ResponseDataJson が authenticatorGetAssertion の応答なので、これを調査します。
- OpenSK、Yubikey、BioPassのログを比べてみます。
- そのまま
- CTAP ResponseDataJson =
{
"1":
{ "id":"LiSHLJSRyKpyEUlCqCwmo99VZCoYjfGnsWNn2RPitrcYP80L_HFb1_DY4doGnAfkp738pWbX_yCPXnGZ6r0Vt6b0MAmVy6vV-RUtMWAEQZkpsBHRpEWmX4kIgJZi3LT02CacCHktzRcLuxvU_P8tuA",
"type":"public-key"
},
"2":
"matxXYSjvF4OkqpQ5npYE2N_0XRL0wGrCPhxkd24FuAFAAAAGQ",
"3":
"MEUCIQCD0jAejoDvRtKIZ7ZRBikQ6Yma51Ib7KK5Mt-SctHrKgIgS5ujml4W28-66Xq50UVsZl97rKhTz5jgAfViJqs8Bqc",
"4":
{
"id":""
}
}
- CTAP ResponseDataJson =
{
"1":
{
"id":"KL-rWIRGn26kZxtQXasrN4TI7vsnkvb3xAu_3huZ-nxF-wk-aOWtsXeP2f3rZiU-EBYjHcPJoQDE0KyOFLc9BA",
"type":"public-key"
},
"2":
"matxXYSjvF4OkqpQ5npYE2N_0XRL0wGrCPhxkd24FuAFAAAA3w",
"3":
"MEUCIQDsA3Fr3LPapPUWBn4m56QTfvNvIxMmLKqKsjBe4YRDWAIgPUdmgwcn0P7h-PxR2fPDYXcBsLYFRvQdxzpIrYllkoc"
}
- CTAP ResponseDataJson =
{
"1":
{
"id":"HpVOwMsjnnLFXVPNTszaMxzbO16pm7FQn8E3Gk4bnI-NoNrwp5-UVvq4dcp1AYdPCwh_Al3YkBAqZvDF8zV81rnvIuROH9b_pkUL1K6epYdINUwfIiCOJcZwYcqyoE9S",
"type":"public-key"
},
"2":
"matxXYSjvF4OkqpQ5npYE2N_0XRL0wGrCPhxkd24FuAFAAAAHQ",
"3":
"MEUCIBY7hxOq8lc-RoUUKL5MuKf7QyhHyCL7DXhU78QawymzAiEA-FbLH9iBW12WaocTr3NxXJMMxbCnmW2LgJLWFoZQzWA"
}
わかりにくいようでわかりやすいのですが、OpenSKだけ"4"があります。
"4":
{
"id":""
}
これは何なんでしょうか?
"4"って何?
CTAPの仕様書によると、user (0x04) - PublicKeyCredentialUserEntity というものです。
PublicKeyCredentialUserEntity
PublicKeyCredentialUserEntity structure containing the user account information. User identifiable information (name, DisplayName, icon) MUST not be returned if user verification is not done by the authenticator.
ユーザーアカウント情報を含むPublicKeyCredentialUserEntity構造体。ユーザ認証が認証者によって行われていない場合、ユーザを特定できる情報 (name, DisplayName, icon) は返されてはなりません[MUST]。
var user = {
id: Uint8Array.from(window.atob("MIIBkzCCATigAwIBAjCCAZMwggE4oAMCAQIwggGTMII="), c=>c.charCodeAt(0)),
icon: "https://pics.example.com/00/p/aBjjjpqPb.png",
name: "johnpsmith@example.com",
displayName: "John P. Smith"
};
id , icon ,name ,displayNameのメンバを持つ構造体で、要するにユーザー情報ですね(雰囲気理解)。確かに登録時にユーザー名は入力してますが、ログを見る限り空っぽです。YubikeyやBioPassでは存在すらしていない。何でなんでしょうか?
PublicKeyCredentialUserEntityとは
FIDO2にはResidentKeyという機能がありまして、セキュリティキーの中にユーザー情報を格納することができます。登録時にユーザー情報をキーの中に保管しておいて、認証時にキーの中にあるユーザー情報を取り出しそのユーザーでログインするなんてことを想定した機能です。ResidentKey機能によってユーザーは自分自身が誰かをわざわざ入力しなくてもキー(所持)とPIN(記憶)または指紋(生体)による2要素認証を実現できます。このキーの中に格納されるユーザー情報が PublicKeyCredentialUserEntity構造体 です。便利なんですけどキーのメモリを使うので格納できる量には限界があるという問題もあります。ResidentKeyを使っているRPはあんまりいないんじゃないかと思います。
さて、今の使い方ではResidentKey機能は使っておりません。なので、PublicKeyCredentialUserEntityは無い、あるいはPublicKeyCredentialUserEntityの中身が空、というセキュリティキー側の動きは仕様的には問題なさそうです。しかしブラウザは空のPublicKeyCredentialUserEntityを受け付けないようで、エラーにするわけでもなくシカト(無視)するようです。つまり、OpenSKはちゃんとボタンプッシュを検知して応答しているんだけども、ブラウザにシカトされている状況、というわけです。YubikeyもBioPassもPublicKeyCredentialUserEntityが無い応答をしているのでブラウザ側はOpenSKのような応答に対応していないのか、CTAP仕様がそうなのかもしれません。
とりあえず、OpenSKもPublicKeyCredentialUserEntityを無しにするようにすればブラウザは反応してくれるかもしれませんのでやってみます。
OepnSKを修正する
ソースをみてみます。Rustはよくわからないしデバック実行もできないのでとにかくコードを舐めるようにみます...
半日ほどかかってようやく該当の場所を見つけました。
let user = if flags & UV_FLAG != 0 {
Some(PublicKeyCredentialUserEntity {
user_id: credential.user_handle.clone(),
user_name: None,
user_display_name: credential.other_ui.clone(),
user_icon: None,
})
} else {
Non
};
このifの中に入っているのにもかかわらず、credential.user_handleが空なので空のPublicKeyCredentialUserEntityが生成されてしまうんでしょう。登録時にResidentKeyをfalseにしているのでcredential.user_handleが空なのは正常だと思います。つまり、この判定を修正する必要があるのかもしれません。
修正しました(1行!)。
これで空のPublicKeyCredentialUserEntityを生成することはなくなるはずです。
let user = if (flags & UV_FLAG != 0) && (credential.user_handle.len() > 0) {
Some(PublicKeyCredentialUserEntity {
user_id: credential.user_handle.clone(),
user_name: None,
user_display_name: credential.other_ui.clone(),
user_icon: None,
})
} else {
None
};
テスト
認証するときにボタンを押したらちゃんと認証できるようになりました。
しかし修正方法がこれでいいのか、OpenSKはあえて対応していないのかもしれません...
環境 | YubiOn | fido.identityserver.com | webauthn.io |
---|---|---|---|
Windows 10 Pro 1909 Chrome 84.0.4147.125 |
OK | OK | OK |
Windows 10 Pro 1909 Edge 84.0.522.59 |
OK | OK | OK |
Mac OS X Catalina 10.15.5 Chrome 84.0.4147.125 |
OK | OK | OK |
おつかれさまでした
Rustの言語仕様は非常に興味深く面白い!