本記事の内容
AAGUIDを偽装した認証器でRPを騙せてしまう場合がある。なぜそれが可能で、どう対策するのか。
- AAGUIDを偽装した認証器をWebAuthn.ioに登録するデモ
- attestation証明書を検証することで認証器を信用できるようになる理由と対策
本記事は既知のプロトコル特性の実証であり、対策(MDSによるattestation証明書検証)の必要性をデモすることが目的で、プロトコルや製品の実装に問題があると主張するものでは一切ありません。
AAGUIDを偽装した認証器をWebAuthn.ioに登録するデモ
パスキー認証では、認証器の識別にAAGUIDを使用する場合があります。しかし、このAAGUIDの値は認証器で自称できるものです。
そのため、RPが認証器のAAGUIDを使用する際は認証器名の表示のような補助的な用途にのみ使用されるべきで、(後述するattestation証明書の検証をしない場合は)重要な処理には使うべきではないです。
Google Chromeのドキュメントでも、AAGUIDから認証器名を取得する方法を解説する一方で、AAGUIDが偽装されうることを警告しています。
https://web.dev/articles/webauthn-aaguid?hl=ja
警告: AAGUID は、ユーザーのパスキー管理をサポートする目的でのみ使用してください。構成証明が有効な署名とともに添付されていない限り、AAGUID は変更または操作される可能性があります。
実際にAAGUIDを偽装した認証器を用意してWebAuthn.ioに登録してみます。
WebAuthn.ioはWebAuthnの挙動確認を目的とした公開デモサイトです。本記事では、脆弱性攻撃や不正アクセスではなく、登録フロー上で観測できる表示挙動の確認に限定しています。
AAGUIDや各種フラグ、attestation証明書を自由に設定できる仮想認証器を用意しました。
デモが目的なので、仮想認証器の実装には触れません。
この仮想認証器に正規のベンダーが製造した認証器XのAAGUID(deadbeef-000-0000-0000-000000000000)を偽装させてみます。
(今回は実在する認証器と被らない値にしていますが、実際には任意の値にできます。)
仮想認証器がWebauthn.ioに送信したatttestationは下記のようになっています。
{
"fmt": "packed",
"attStmt": {
"alg": -7,
"sig": "3045022..."(attestation秘密鍵による署名),
"x5c": [
"3082020..."(attestation証明書のバイナリ)
]
},
"authData": {
..(省略)..
"flags": {
"_byte": "0x5d",
"UP": true,
"UV": true,
"BE": true,
"BS": true,
"AT": true,
"ED": false
},
"signCount": 0,
"attestedCredentialData": {
"aaguid": "deadbeef-000-0000-0000-000000000000",
..(省略)..
}
}
}
}
WebAuthn.ioにAAGUIDを偽装した認証器を登録してみます。
画面左のターミナル上で仮想認証器を動作させていて、画面右がWebAuthn.ioです。
下記画像のように、AAGUIDを偽装した仮想認証器が正規ベンダーの認証器として登録されています。
そして、同期型パスキーと認識されています。これは、attestationでBE/BSフラグをtrueにしているためです。
ただしこれはAAGUIDや各種フラグという自己申告値を真似ただけで、正規ベンダーの認証器が持つattestationを偽造できるというわけではないです。
パスキー認証において、認証器が送信してくるAAGUIDやフラグを信用してはいけないことをデモしました。
これはコンシューマ用途では些末な問題に見えます。しかし、高度なセキュリティを課す場合にはユーザーが使っている認証器を統制する必要があるかもしれません。RP側は認証器が送信してくる情報をもとに認証器がポリシーに適合するものか判断しますが、認証器が信用できないならポリシーを適用する前提が崩れてしまいます。
では、どうしたら認証器が送信してくる情報を信用できるのか?
RPが認証器のattestation証明書を検証することで、認証器が信頼できるベンダーによって作成されたものだと確認できます。
attestation証明書を検証すると認証器を信用できるようになる理由
attestation証明書を検証する仕組みについて解説していきます。
下記にattestation objectの構造を示します。attestation証明書はx5cフィールドに格納されています。(本記事では、fmt:packedを前提としています。)
https://fidoalliance.org/fido-attestation-enhancing-trust-privacy-and-interoperability-in-passwordless-authentication
今回のattestation証明書の説明には、3種類の鍵ペアが登場します。
紛らわしいので事前に整理しておきます。
① credential 鍵ペア
Google, Amazon, Facebook,,,といったRPごとに生成される鍵ペアです。
パスキー認証では、このcredential鍵ペアを使用して公開鍵認証を行います。
② attestation 鍵ペア
製造時に認証器へ焼き付けられている鍵ペアです。
RPに登録するcredential公開鍵に対して署名をするのがattestation秘密鍵です。
attestation公開鍵の公開鍵証明書がattestation証明書です。
attestation証明書は③のベンダーCA秘密鍵によって署名されています。
③ ベンダーCA鍵ペア
認証器を製造したベンダーが管理する認証局の鍵ペアです。
attesation証明書の記述が正しいことを署名によって保証します。
この鍵ペアはベンダー認証局の管理下にあり、認証器には入っていません。
3種類の鍵ペアの役割について図を作ると下記のようになります。
図中の青色の図形がattestation証明書を表しています。
仮想認証器のattestation証明書を見てみます。
登録時にブラウザの開発者ツールからattestation証明書を確認できます。
Issuerがローカル認証局になっています。実機ではベンダーの認証局がIssuerです。
Subject Public Key Infoには②attestation公開鍵の情報が格納されています。
x509v3拡張にAAGUIDの記載があります。この認証器のAAGUIDはAAGUIDdeadbeef-000-0000-0000-000000000000と言っています。
そして、最後に③CAの秘密鍵で署名をすることで、attestation証明書の内容が改ざんされていないことにお墨付きを与えています。
この証明書ではローカル認証局が署名をしていますが、実際はベンダーのCAで署名を行います。
Certificate:
Data:
...
+ Issuer: CN = Virtual FIDO2 Root CA, O = Virtual FIDO2 Test
+ #ローカル認証局
...
+ Subject: CN = Virtual FIDO2 Authenticator, O = Virtual FIDO2 Test
+ Subject Public Key Info: # ② attestation 公開鍵
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:04:13:f8:39:...
X509v3 extensions:
...
+ 1.3.6.1.4.1.45724.1.1.4: # AAGUID拡張
+ .................. # AAGUID(deadbeef-000-0000-0000-000000000000)
Signature Algorithm: ecdsa-with-SHA256
Signature Value:
+ 30:46:02:21:... # ③ローカルCA秘密鍵で署名
attestationを受け取ったRPはattestation公開鍵でsigフィールドに格納されている署名を検証します。
sigフィールドの署名は、attestation証明書の署名とは別ですので注意してください。
もしattestation公開鍵でsigフィールドの署名を検証できるなら、attestationはattesataion公開鍵に対応する秘密鍵を所有する認証器によって作成されたものです。
しかし、attestation証明書を本当にIssuerが発行したかどうかは確認できていません。
そこでattestation証明書の証明書チェーンを上へ辿り、信頼できるルート証明書まで到達できることを確認する必要があります。
考え方は Web サイトの TLS と同じですが、トラストアンカーの入手方法が異なります。
- Web の場合:信頼するルート証明書は OS やブラウザにあらかじめ組み込まれており、それをトラストアンカーとしています。
- パスキーの場合:認証器ベンダーは多数あり、全ベンダーのルート証明書を事前に持つのは現実的ではありません。そこで FIDO アライアンス Metadata Service(MDS) から、AAGUIDをキーに「その認証器モデルのトラストアンカー」を取得します。
FIDO アライアンス Metadata Service (MDS) は、メタデータ ステートメントの一元化されたリポジトリであり、証明書利用者が認証子の証明を検証し、デバイス モデルの信頼性を証明するために使用されます。
MDSから取得したベンダーのルート証明書でattestation証明書を検証できるなら、下記のフローで認証器を信用できます。
1.1 sigがattestation公開鍵で検証できる(attestation秘密鍵の所持証明)
1.2 attestation証明書がベンダーのルート証明書まで辿れる(attestation証明書を信用できる)
1.3 AAGUIDとattestation証明書のAAGUID拡張が一致する(なりすまし防止)
↓
2. FIDOアライアンスによって審査されたベンダーが作成した、
認証器がattestationで申告しているどおりのAAGUIDの認証器である
↓
3. 認証器が送信してくる情報を信用できる
実際に正規ベンダーのattestation証明書と、AAGUIDを偽装して正規ベンダーの認証器になりすましている仮想認証器のattestation証明書をMDSで検証してみます。
正規ベンダーのattestation証明書をgenuine.pem、仮想認証器のattestation証明書をforged.pemとします。
MDSから正規ベンダーの中間CA証明書とRoot証明書をダウンロードしてmds-root.pemに格納します。
opensslコマンドで、genuine.pemとforged.pemをmds-root.pemで検証します。
❯ openssl verify -CAfile mds-root.pem genuine.pem
genuine.pem: OK
❯ openssl verify -CAfile mds-root.pem forged.pem
error 20 at 0 depth lookup: unable to get local issuer certificate
error forged.pem: verification failed
AAGUIDや各種フラグは自己申告値として任意に設定できても、正規ベンダーのCA秘密鍵を持たない限り、信頼済みトラストアンカーに到達するattestation証明書を作ることはできません。
したがって、MDSから取得したトラストアンカーで証明書チェーンを検証し、さらにsig検証とAAGUID拡張の一致確認を行うことで、正規ベンダーが作成した認証器だとみなすことができるようになります。




Comments
Let's comment your feelings that are more than good