WebauthnのResident Keyについて自分が調べたことをつらつら書いた記事。執筆途中にGoogleから、Resident Keyとも関係が深い、Passkeyへの対応の発表があり、今後ますます熱い分野になると思われる。
Resident Keyという用語について
規格上はResident Keyという用語は非推奨とされており、現在はDiscoverable CredentialとかClient-side Credentialと呼ぶ。その反対はServer-side Credentialである。にもかかわらず、WebauthnのAPI上ではresidentKey
等の名前を昔のまま使っており、混乱を招く状況になっている。この記事内ではAPIに使われていておそらくより伝わりやすいResident Keyとその反対のNon-Resident Keyという用語を使う。
Resident Keyの目的
Resident Keyの目的はユーザーネームレス認証を可能にすることである。Webauthnの目的はパスワードレス認証だが、そこからさらに進めてユーザーネームすら不要にするのがResident Keyである。Non-Resident Keyの場合、ログイン時に最低でもユーザーIDにあたるものをユーザーに入力させねばならないが、Resident Keyならユーザー登録をするときはスマホの指紋認証を触るだけ、ログインする時も触るだけ、他には何も入力の必要がない、という体験を実現できる。
より実装に近い話をすると以下のようになっている。新しいキーをnavigator.credentials.create()
で作成するとCredential IDが返ってくる。このときNon-Resident Keyとして作成すると次にnavigator.credentials.get()
で認証するときにそのCredential IDを引数のallowCredentials
に入れて渡さねばならない。Resident Keyとして作成すればCredential IDを渡す必要はなく、allowCredentials
は空で良い。
よってNon-Resident Keyとして作成する場合はnavigator.credentials.get()
を呼び出す前にCredential IDが分かってなければならないということだ。Credential IDが分かっているためにはあらかじめユーザーにユーザーID等を入力させ、それに紐づいたCredential IDをデータベース等から取ってくるという流れが必要である。ユーザーネームレスにはならない。
Resident Keyとして作成すればCredential IDが必要ないのでユーザーID等を入力させる必要がない。クライアント側で登録された使用可能なキーの中からユーザーが選んで使うキーを決めるということになる。
なぜNon-Residentな方式が存在するのか?
入力する項目が少ないに越したことはないので、そもそもNon-Residentな方式は必要なのか?という疑問が出てくる。Resident Keyでもユーザーネームが欲しいなら追加で入力させればいいだけだし、キーはオリジンごとに格納されており他のオリジンのキーはどうやっても見れないわけだからセキュリティのレベルが下がるということもないと思われる。
ではなぜNon-Residentな方式が存在するのかというと、そちらの方が認証器の実装が簡単だからであリ、具体的にいうとNon-Residentな場合、認証器は自分が生成したキーに関する情報を一切自分で記憶しないステートレスな実装にできる。Webauthnの解説で「認証器では秘密鍵と公開鍵のペアを生成し、そのうち秘密鍵は認証器の中で保管します」という説明がされることがあるが、実はこの秘密鍵すら必ずしも認証器の中で保管されているとは限らないということだ。その場合認証器には鍵の情報を持っておくストレージが要らないので実装を相当単純にできる。ちなみにストレージが不要ということは無限に鍵を生成できるということでもある。
いやいや、認証器が自分で生成した秘密鍵を記憶しないでどうやって認証器を実装するねん...と思うかもしれないが、これはCredential IDを活用することで可能である。
Credential IDはIDという名のイメージと異なり、バイト列で表されしかも私の環境で80バイト近くあったりと結構長く取ることができる。よって生成した鍵ペアの秘密鍵やその他鍵の情報をCredential IDに含めてしまえば良いのである。そして当然それらの情報をそのままCredential IDとして返せば秘密鍵がバレてしまうので、認証器の中にある認証器しか知らないマスターキーで暗号化して含めれば良いのである。Credential IDはRelying Party側で保管されて認証時にまた与えられるので、認証器はそれをマスターキーで復号すれば秘密鍵等を取り出し認証作業ができる。
ちなみにこのようなステートレスな実装方法は規格書にも書かれていることである。
Resident Keyの対応状況
執筆時点(2022年10月)ではResident Keyはまだ主要なOS、ブラウザの全てで対応しているとは言えない。特にAndroidのChromeに対応していない。よって一般に公開されたウェブサイトに使うのは事実上無理である。
またResident Keyに対応していても本当に「使える」かは別問題だったりする。Resident Keyはユーザーのデバイス側に鍵の情報を持つので、鍵の情報を管理する機能がOSやブラウザ側に必要である。認証時に使える鍵が複数あった場合にユーザーに選んでもらうUIや、要らなくなった鍵を削除するUI等である。Non-Resident Keyと違い、こうした機能はOSやブラウザ側で実装してもらわなければ、我々アプリケーション開発者にはどうフォールバック実装をすることもできない(鍵はデバイスが持っていて、それにアクセスするAPIも存在しないので)。しかしこのような鍵の管理機能を一通りちゃんと実装しているOSやブラウザは現状ないと思われる。
Passkeyについて
この記事の執筆中にGoogleからAndroidでのPasskeyの対応について発表があった。Passkeyは端末を跨いでResident Keyを共有する仕組みだが、おそらくAndroidのChromeのResident Key対応はこのPasskey対応と統合されて実装されると思われる。Android以外のOSでもPasskey対応と一緒に鍵管理機能が搭載されることになるのだと思う。AndoridのPasskeyはまだ開発者向けだが、着実にパスワードレス、ユーザーネームレスの未来は来ている。