パスキーは現在、情報セキュリティにおいて最も重要な取り組みです。なぜなら、フィッシング攻撃の圧倒的な有効性に対する唯一の原理的な解決策だからです。ちょうどメモリ安全性がメモリ破損攻撃に対する唯一の原理的な解決策であるように。

残念ながら、サーバー側でそれらを実装することは、パスワードハッシュを使うよりも複雑に見える場合があります。この一部は避けられません。パスキーはフィッシング耐性の特性を得るためにブラウザとのやり取りを必要とするからです。しかしその一部は、相互運用可能なパスキーレコードエンコーディングを定義することで、より効果的に抽象化できる可能性があります。

WebAuthn 仕様は、定義するcredential recordを、typeidpublicKeybackupStatetransports などのフラグといった複数のコンポーネントを持つ抽象的な概念として定めています。Google は 推奨するデータベーステーブルとして、Credential ID を主キーとし、public_keybacked_uptransports カラムを持つものを挙げています。Adam Langley の優れた Tour of WebAuthn も同様に、cred_id を主キーとし、別途 public_key_spkibacked_up カラムを持つことを推奨しています。

すべてのガイダンスは WebAuthn 認証の処理にライブラリを使うことを推奨していますが、それでもアプリケーションには潜在的に相互運用性のないデータベーススキーマが残ります。また、認証フロー全体とデータベース操作をライブラリやフレームワークに委ねることが現実的でない場合もあります。

アプリケーションがパスワードハッシュのように不透明な文字列として扱える、相互運用可能で十分に仕様化されたパスキーレコードは、中間的な抽象化レイヤーとなり得ます。

c2sp.org/passkey-record は、Password Hashing Competition (PHC) Strings の構文を借り、作業の大部分に既存の authenticator data エンコーディングを再利用する仕様提案です。レコードは次のようになります:

$webauthn$v=1$transports=hybrid+internal$<base64 authenticator data>

ペイロードは authenticator data で、credential record フィールドの大部分の CTAP2 CBOR エンコーディングであり、すでに WebAuthn によって 仕様化されており、AuthenticatorAttestationResponse の JSON エンコーディングに含まれています(これはアテステーションを使用しない場合でも、navigator.credentials.create() の戻り値の型です)。Transports は唯一欠けているフィールドで、PHC パラメータとして保存されます。

その後、アプリケーションはどのパスキーレコードがユーザーアカウントに関連付けられているかを追跡するだけで済みます。これは、パスワード認証を実装するのと大きく異ならないため、Web 開発者にとって馴染みのあるタスクです(アカウントごとに複数のパスキーが存在する点を除く)。これらの不透明な文字列は、ログインアサーションの検証(または適切な excludedCredentials を用いた登録リクエストの生成)のためにライブラリに渡すことができます。

相互運用可能なストレージ形式が十分に仕様化されていれば、クレデンシャルデータベースを保持したままパスキーライブラリ(あるいはバックエンド言語)を切り替えることが可能になるでしょう。

保存しておきたいその他のフィールド

パスキーレコードとともに、アプリケーションはユーザー選択のニックネームや作成日時、最終使用日時といったメタデータフィールドを保存したい場合があります。これらは見やすいパスキー管理 UI を提供するためのもので、いずれも WebAuthn ライブラリによる特別な処理を必要としません。

唯一の例外が backed up state フラグです。パスキーはサーバーに対して(iCloud Keychain や Google アカウントなどに)バックアップされているかどうかを報告し、サーバーはそのシグナルを使ってアカウントからパスワードを削除することを提案できます。このフラグはログインごとに変化する可能性がありますが、パスキーレコードは不変であるため、別途保存し、ログインごとに更新する必要があります。私はこのロジックは、メールによるパスワードリセットを継続してサポートする一般的なウェブサイトにとっては過大評価されていると考えています。

潜在的な crypto/passkey API

これらのパスキーレコードを基盤として、私は 潜在的な crypto/passkey ステートレス Go パッケージ API のドラフトを作成しました。

登録フローは以下の通りです。

  1. ログイン済み(またはその他の方法で識別された)ユーザーの詳細と既存のパスキーレコードを指定して RelyingParty.NewRegistration を呼び出す
  2. 返された JSON を parseCreationOptionsFromJSON() に渡し、その後 navigator.credentials.create() に渡す
  3. 返された JSON エンコード済み PublicKeyCredential を RelyingParty.Register に渡す
  4. 返されたパスキーレコードをデータベースに保存する

ログインフローは以下の通りです。

  1. ログインページ生成時に RelyingParty.NewLogin を呼び出す
  2. 返されたリクエストを短い TTL でキーバリューキャッシュに RequestID(request) をキーとして保存し、返された JSON を parseRequestOptionsFromJSON() に渡した後、navigator.credentials.get() に渡す
  3. 返された JSON エンコード済み PublicKeyCredential を Inspect に渡し、返された requestID を使ってキーバリューキャッシュからリクエストを取得し、返された userID を使ってデータベースからパスキーレコードを取得する
  4. JSON の PublicKeyCredential、リクエスト、パスキーレコードを RelyingParty.Login に渡す

アプリケーションは以下の責任を負います。

  • 各ユーザーに対して不透明で永続的、かつプライバシーを保護するユーザー ID を関連付ける
  • ユーザーに関連付けられたパスキーレコードを保存する
  • RelyingParty.NewLogin が生成したリクエストチャレンジをキャッシュする

ライブラリは、parseCreationOptionsFromJSON()parseRequestOptionsFromJSON() にそのまま渡せる JSON 値を提供し、PublicKeyCredential に対して JSON.stringify() を呼び出して得られる JSON 値を受け入れます。

これは discoverable credential フロー(オーセンティケータがユーザー ID を保存・提供する、いわゆるパスキー)に最適化されていますが、RelyingParty.NewLoginForUser メソッドはセカンドファクターフローや再認証プロンプトにも使用できます。この API はモーダル UI と条件付き UI(オートフィル)の両方のフローに対応します。

パスキーレコードから情報を抽出するためのヘルパー(AAGUIDBackedUp)や、JSON エンコード済み PublicKeyCredential から情報を抽出するためのヘルパー(ResponseBackedUp)がいくつか用意されています。

現在、実装は存在しません。私は パスキーレコード形式Go API に関するフィードバックを得て、Go 1.28 への提案を行う可能性を検討したいと考えています。

重複する Credential ID について

この保存モデルでは、異なるアカウントが同じ Credential ID のパスキーを共有しないことを保証できませんが、仕様ではこれを行う SHOULD とされています。

このチェックの理由は、攻撃者が自分のアカウントを通じて意図的に衝突する Credential ID を注入した場合に、ID でクレデンシャルを検索して誤った公開鍵やユーザー ID に到達する攻撃を避けるためです。

この攻撃は、そもそも Credential ID インデックスを持たなければ発生し得ません!インデックスが必要になるのは、インデックスの存在によって生じる攻撃を緩和するためだけです。

ログイン試行にはユーザー ID が含まれ、それを使ってユーザーのパスキーレコードを検索し、ログインを検証すれば、別のユーザーが同じ Credential ID のパスキーを持っていても問題になりません。ちょうど 2 人のユーザーが同じパスワードを共有していても問題にならないのと同じです。

攻撃者に PRIMARY KEY を決めさせなければ、PRIMARY KEY 衝突攻撃は起こりません。

さらなる Go API のプレビューについては、Bluesky の @filippo.abyssdomain.expert または Mastodon の @[email protected] でフォローしてください。

写真

今年の CENTOPASSI(GPS 追跡付きのオートバイ競技で、3 日半で 100 地点、1,700 km の二次道路を慎重に計画して走破するもの)からのその他の写真です。無人のまま雪の残る Campo Imperatore から下りた後の Castel del Monte (AQ) の様子です。

中世の街が木々に囲まれ、上空から見えます。遠くに森、丘、そして雪の筋のある山頂が見えます。空は灰色で曇っています。

私の活動は、プロフェッショナルな Go メンテナの組織である Geomys によって可能になっています。Geomys は Ava LabsTeleportDatadogTailscaleSentry によって資金提供されています。彼らのリテイナー契約により、私たちのオープンソースメンテナンス活動の持続可能性と信頼性が確保され、私や他の Geomys メンテナの専門知識への直接的なアクセスが提供されます。(詳細は Geomys 発表 をご覧ください。)

以下に、彼らの声の一部を紹介します!

Teleport — 過去 5 年間で、攻撃と侵害は従来のマルウェアやセキュリティ侵害から、ソーシャルエンジニアリング、クレデンシャル盗難、フィッシングによる有効なユーザーアカウントやクレデンシャルの特定と侵害へと移行しています。Teleport Identity は、アクセス監視による弱いアクセスパターンの排除、アクセスリクエストによる攻撃対象の最小化、必須アクセスレビューの実施による未使用権限の削除を目的として設計されています。

Ava Labs — Avalanche Network とやり取りするための最も広く使われているクライアントである AvalancheGo のメンテナである Ava Labs は、オープンソースの暗号プロトコルの持続可能なメンテナンスと開発が、ブロックチェーン技術の広範な採用に不可欠であると考えています。私たちは Filippo とそのチームの継続的なスポンサーシップを通じて、この必要かつ影響力のある活動を支援できることを誇りに思います。