
2025/12/23 3:58
パスキー・ボット構築で学んだこと (Note: The original title is short, so the translation preserves its brevity while conveying the meaning.)
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
改善された概要
Passkeybot.com は、軽量なサーバー側 HTTP ハンドラーを挿入してパスキー認証を追加するホスト型ログインページを提供します。サービスはデバイスレベルのセキュアエレメント(Apple の Secure Enclave Processor、YubiKey、SIM カード)に依存し、暗号鍵の秘密情報をユーザーのデバイス内に保持します。プライベートキーは決して外部へ送られず、パブリックキーのみがサーバーに登録用として送信されます。
サービスではブラウザが統一された Passkey API を公開する方法と、単純なボタンタップや生体認証プロンプトである User Presence と、より強力な生体検証である User Verification の違いを説明しています。鍵ペアはデバイス上で生成され、アテスタションレコードにより真正の認証器が使用されたことを証明します。ただし、Apple はエンタープライズ MDM が有効になっていない限りデフォルトでアテスターションを無効化しています。
本文では最近のブラウザ実験(Chrome の Immediate Mediation 試行と Return‑of‑Request‑Origin (ROR) サポート)も触れています。これらは異なるオリジン間でパスキーを要求できるようにしますが、ROR は HTTPS 上でのみ機能し、iOS 18 や Firefox ではサポートされていません。
また、セキュリティ上の懸念点も指摘しています。JavaScript が侵害された場合、認証器は署名内容を表示しないためユーザーに任意データへの署名を強要される恐れがあります。Subresource Integrity はスクリプト保護に有効ですが、ルート HTML を守ることはできません。
まとめでは Passkeybot が PKCE(Proof Key for Code Exchange)を利用してベアラートークンを排除している点と、将来の計画として Digital Credentials API と連携しパスキーをネイティブ OS ウォレットに格納すること、アテスターションポリシーを洗練させること、
crypto.subtle.generateKey を用いた一般的な暗号署名向けの非抽出可能鍵生成を拡張することが挙げられています。
ユーザーはよりスムーズで安全なログイン体験を享受でき、企業はパスワードサポートコストを削減できます。業界全体としては標準化されプライバシーに配慮した認証へと進む動きが加速します。
本文
近年リリースした passkeybot.com(パスキー認証を数行のサーバー側HTTPハンドラーで実装できるホスト型サイン‑インページ)について、開発中に得た知見をまとめました。
Secure Enclave Processors (SEP) とは
- Apple 製品には Secure Enclave(SE) が搭載されており、メインCPUの内部に設けられた小型コンピュータです。暗号化された独立したメモリと専用OSを持ち、秘密鍵などの機密情報は外部へ漏れません。
- メインOS は SE に「データを署名してほしい」と要求し、その署名結果だけで秘密鍵の所有権を証明します。
OS と SE の間では専用メッセージプロトコルのみが使用されます。 - パスキー認証が有効になっている場合、ユーザーは SE がプライベートキーで署名する前に生体情報やパスコードを入力しなければなりません。
- 他社製品にも同様の仕組み(名称は異なる)が存在します。
(例:電話 SIM カードは「Secure Element」で、Java を走らせた CPU がカード内で秘密を保持し、署名により所有権を証明します。)
User Presence (UP) と User Verification (UV)
| 用語 | 意味 |
|---|---|
| Presence | 「ユーザーがボタンをタップしてその場にいた」こと。 |
| Verification | 「ユーザーが生体情報やパスコードを入力した」こと。 |
JavaScript の Passkey API でどちらもリクエストできます。
- Presence はデバイスがロック解除済みならば偽造しやすいですが、
- Verification は必ず再認証が必要です。
Authenticator(認証器)とは
- ハードウェア + ソフトウェアで構成される認証器は、プライベート/パブリックキー対を保持し、Passkey チャレンジに署名することで鍵の所有権を示します。
Apple 製品の場合は SE がこれに該当します。 - ブラウザはユーザーに「どの認証器を使うか」を尋ね、OS レベルの API を介してその認証器とやり取りします。
例:
① ユーザーがデバイス内 Apple SEP を選択 → サイトが JS API 呼び出し → ブラウザは Swift API で Passkey 操作 ② ユーザーが Yubikey を選択 → サイトが JS API 呼び出し → ブラウザは Yubikey USB API で Passkey 操作
JS API はこれらの認証器 API を統一的に扱い、ブラウザ側でそれぞれのプロトコルを実装しています。Chrome DevTools にはテスト時に OS パスワード入力を省く仮想認証器も用意されています。
Attestation(アテステーション)とは
| 項目 | 説明 |
|---|---|
| Signing | プライベートキーで署名できること=所有権の証明。 |
| Attestation | どのハードウェア/ソフトウェアが鍵ペアを生成したかを示し、特定デバイスの信頼ポリシーを実装可能にします。 |
| アテステーション情報は正確なハードウェア構成を露出するため、ユーザー指紋として利用されることがあります。 | |
| 発生タイミング | 鍵ペア作成時のみで、認証毎には行われません(キーが別デバイスへ同期された場合は元のアテステーションは無効)。 厳格なアテステーションを要求すると、デバイスごとに新しい鍵ペアが生成され、移動したパスキーの利用を防止します。 |
| Apple の設定 | デフォルトではアテステーションは無効です。企業向け機能(MDM)で有効化し、許可リストを定義できます。 |
| Passkey の用途 | 認証のみであり、意図の署名には使われません。ユーザーが認証するときに送られるチャレンジは 16 バイト以上のランダムデータを含み、リプレイ攻撃を防止します。また、メタデータハッシュも含むことがあります。 認証器 GUI は「sign in to your_domain.com」だけを表示し、取引等の任意内容署名は許可されません。 |
JS コードは安全である必要があるが検証不可能
- サイト側の JavaScript が改ざんされると個人情報を読むことや偽チャレンジをユーザーに署名させることができます。
認証器は実際に何が署名されたかを GUI に表示しないため、攻撃者は自由にデータを改変できてしまいます。 - ブラウザは Subresource Integrity (SRI) をサポートしていますが、ルート HTML ドキュメント自体は検証されません。
攻撃者は SRI ハッシュを変更して悪意あるスクリプトを実行できます。Chrome 拡張機能も同様に JS を注入可能です。 - もし認証器が「ページ上で読み込まれた HTML/JS」をアテステートできれば、改ざんの検出が容易になるでしょう。
「即時サイン‑イン」API(Chrome Origin Trial)
navigator.credentials.get({ mediation: "immediate" })
- パスキーをすでに持っているユーザーは高速に署名できます。
- パスキーが無い場合、JS が次のステップを決定し、ブラウザ UI で他デバイスへの切り替えを表示しません。
- JS からはユーザーのパスキー一覧を取得できず、常にブラウザ UI を通じて表示します。
| キー数 | 即時 JS の応答 |
|---|---|
| 0 | (次ステップを JS が選択) |
| 1 | 該当キーでサインインするよう促す |
| >1 | ユーザーにキーを選択させる |
Related Origin Requests (ROR)
- ドメイン所有者が
を配信し、他ドメインから自ドメインへのパスキー作成を許可するリストを定義できます。/.well-known/webauthn
Passkeybot.com は ROR を使って passkeybot 自身の権限付与を実装しています。 - HTTPS 必須であり、localhost では機能しません(認証器は HTTPS 経由で well‑known ファイルを取得します)。
- iOS 18 および Firefox では未対応です。
- ルートドメインに作成したパスキーはすべてのサブドメインで有効ですが、サブドメイン単位で作成したものはそのドメインのみで使用できます。
カウンタは「ヒューリスティック」だけ
- 認証器は各パスキー利用時にカウンタを増加させます。
クローン認証器の検出に使えますが、正当な理由(例:複数デバイスで同期)で誤動作するケースも多いため、最終的にはヒューリスティックとして扱われます。
Bluetooth を利用した近距離デバイス上のパスキー
- 公衆 PC へサインインするときにローカルにパスキーが無くても、ユーザーのデバイス認証器と BLE 通信で署名プロトコルをやり取りできます。
近接性(BLE のみ)で安全性を担保しつつ、鍵自体はデバイス内に留まります。
Signal API を使ったパスキー削除
- JS API はパスキーの一覧取得・直接変更はできません。ブラウザ GUI や Apple Passwords からのみ操作可能です。
- 削除を「シグナル」することで、ユーザーに対して削除の意図を示すだけで確認応答は返されず、ユーザーデータ漏洩リスクを低減します。
現在サポートされている Signal API メソッド:
PublicKeyCredential.signalUnknownCredential({ rpId, credentialId }) PublicKeyCredential.signalAllAcceptedCredentials({ rpId, userId, allAcceptedCredentialIds }) PublicKeyCredential.signalCurrentUserDetails({ rpId, userId, name, displayName })
user.id
と userHandle
は「1 つのアカウント」を表す
user.iduserHandle- 同じ値ですが、JS API の呼び出し先で名前が異なります。
- 複数パスキーを 1 つのロジカルアカウントに紐付ける際に使用されます。
- パスキー API(特にシグナル API)では必ず設定・保存する必要があります。
- 新規パスキーごとにユニークな
を生成し、関係を保持すれば「アカウント単位」でのパスキーグルーピングがブラウザ UI で行われなくなる可能性があります。user.id
crypto.subtle.generateKey
による非抽出可能鍵
crypto.subtle.generateKeycrypto.subtle.generateKey(algorithm, extractable, keyUsages)
- プライベートキーを外部に抽出できない鍵ペアを生成します(パスキーと同様)。
署名操作は可能ですが、JS が改ざんされてもプライベートキー自体は読み取れず、移動も不可能です。 - PKCE(Proof Key for Code Exchange)は OAuth に後付けで組み込まれた仕組みです。
PKCE の概要
- フロー開始:
(32 バイトのランダム)とcode_verifier
(SHA‑256 ハッシュ)を作成。code_challenge - ユーザーに
を提示し、認証サービスへ送信。code_challenge - 認証サービスはトークンを発行し、
と紐付ける。code_challenge - トークンを引き換える際には
のペアが必要。(token, code_verifier) - そのため、トークン自体が盗まれても
を持つ本人だけが使用できます。code_verifier
PKCE は「静的シークレットを保持できない環境」(JS アプリやデスクトップクライアント)向けに設計されました。
Passkeybot では PKCE の原理を応用し、API クライアントごとにベアラートークンの管理を回避していますが、OAuth 標準とは名前・相互作用が異なります。
Digital Credentials API
- OS のネイティブウォレットへのブラウザブリッジです。
ID、チケット、バッジ、会員カードなどを要求でき、実際の本人確認書類を共有せずに「年齢証明」や「運転免許証」等の検証が可能になります。