2024年2月29日木曜日
PlayStationでパスキーを使う(実機編)
2024年2月28日水曜日
PlayStationでパスキーを使う
こんにちは、富士榮です。
そういえばPlayStation Network(PSN)がパスキーに対応したというニュースが1月末ごろに流れたので確認してみていたのですが、その時点ではUSアカウントしかサポートしていない?みたいな状態だったのでしばらく放置していましたが、先ほど確認してみたら私のアカウントでもパスキーが使えるようになっていました。
ポイントはこちらです。
- パスキーのサポート(とりあえずブラウザからのログインで確認)
- パスキーを有効にするとパスワードは無効化される
- パスキーを無効化する際にパスワードの生成を求められる
- パスキーの識別はUser-Agentが使われる
- パスキーを無くした際はメール+秘密の質問もしくは生年月日で回復できる
- 回復の際はパスワードを生成が求められ、パスキーは無効化される
まぁ利便性などを考えると仕方ないのかもしれませんが、メール到達性+生年月日で回復できてしまうのは微妙だなぁ、、と思いつつ。
5/22追記)リカバリプロセスが更新されています。詳しくはこちら
実機(といってもPS4しか持っていない)での確認は次回以降でやりたいと思いますが、とりあえずブラウザでの設定関係についてまとめておきます。
パスキーの登録
編集をクリックして登録を開始していきます。
とりあえずTouchIDで生成していきます。
生成が完了するとログアウトされます。
サインイン
なお、Autofillを無視してサインインIDにメールアドレスを入力して次へ、をクリックすると「パスキーでサインイン」しか出てこずパスワードでのログインはできないようになっています。
先ほどのマイページのセキュリティ設定を見るとパスキーが有効、パスワードが無効になっていることがわかります。
パスキーを無効化する
この状態でパスキーを無効化してみます。パスワードの生成を求められますので、ここで生成するとパスキーが無効になり、パスワードでログインができるようになります。
パスキーの管理
パスキーの管理メニューを開くと登録済みのパスキーの一覧が出てきます。
パスキーの識別にはUser-Agentが使われるようです。自分で名前がつけられる方が親切な気はしますがまぁいいかと思います。
ローカル認証器が使えない状態でのサインイン(ハイブリッド)
メールアドレスを入力しパスキーでサインインから他端末で読ませるQRを表示する方法もありますが、リカバリの流れで他端末を使ったサインインもサポートしています。
リカバリ
他の端末を含め全部ロストしてしまったケース用の動線も用意されています。
具体的にはアカウント回復用のメール+秘密の質問もしくは生年月日を使います。
画面下部の「アカウントの回復用のEメールを送信」をクリックするとメールが送られてくるのでリンクをクリックすると回復用の画面が表示されます。
秘密の質問への回答を覚えている訳がないので生年月日を選びます。
検証に成功するとパスワードの生成を求められます。
この段階でパスキーが無効になりますので、パスワードでログインした後に再度パスキーを登録する必要があります。
なお、パスキーが無効化された状態でパスキーでログインしようとするとエラーが表示されます。
とりあえずはブラウザで試しましたが実機を触る時間ができたら実機でも試してみたいと思います。
2024年2月27日火曜日
パスキーでログインを実装する(クレデンシャルの取得編)
こんにちは、富士榮です。
前回までで登録がある程度実装できたので、細かいところを実装する前にログイン側の処理を実装してみましょう。
その前に、これまでのポストはこちらです。
- パスキーの実装をし始めてみる
- パスキー登録APIのオプションを読み解く
- さまざまな認証器でパスキー登録APIの返却値を確認する
- パスキー登録APIのレスポンスを解析する
- パスキーの登録レスポンスの検証を行う
- パスキーの登録を行う
- 登録済みのパスキーを削除する(MacOS)
登録に比べてログインは割とシンプルです。
- 登録と同じ様にサーバ側で生成したchallengeを含むoptionを生成する
- optionを指定してnavigator.credential.get()を実行する
- 認証器をアクティブ化し、navigator.credential.get()の返却値から取得できるCredentialIDをキーに登録済みの公開鍵を取得する
- 取得した公開鍵を使い同じくnavigator.credential.get()の返却地に含まれるデジタル署名等の検証を行い、成功したら認証OKとする
Challengeの取得
登録の時と同じですね。
サーバ側で生成してbase64urlエンコードされたchallengeをクライアント側でデコードしてArrayBufferの形にしてあげます。
optionパラメータを生成する
取得したchallengeを含むoptionパラメータを生成します。ユーザ認証を認証器側に求めるかどうかのスイッチもここで設定できます。
navigator.credential.get()を実行する
生成したoptionを指定してAPIを実行します。
画面にはパスキーダイアログが表示されます。
結果、返却されるPublicKeyCredentialの中身を見ると今度は特徴的なプロパティとして
- userHandle
- signature
あとは検証の部分ですので、次回解説していきたいと思います。
2024年2月26日月曜日
登録済みのパスキーを削除する(MacOS)
2024年2月25日日曜日
パスキーの登録を行う
- ユーザ情報(ユーザ名、ユーザID)
認証器クレデンシャルのID(credentialId)※ritou先生ご指摘ありがとうございます。- 公開鍵(PublicKey)
- 署名回数(signCount)
ユーザの情報を取得する
- 認証器の登録開始前にユーザ名入力〜ユーザIDの生成 → セッションに保存
- 認証の生成API実行
- API返却値の取得〜登録 → セッションからユーザ情報を取り出して一緒に登録
認証器の登録結果を取得する
- ユーザ情報(ユーザ名、ユーザID)
- 認証器のID(credentialId)
- 公開鍵(PublicKey)
- 署名回数(signCount)
2024年2月24日土曜日
デジタル認証アプリがやってくる(その後)
こんにちは、富士榮です。
先月、デジタル認証アプリに関連する法令に関するパブコメ募集が出ている件について個人的に考える課題点について書きました。
デジタル認証アプリがやってくる
それなりのPVがあったこともあり、某雑誌社の方からインタビューがあったりもしました。
パブコメの募集が2月末までだったこともあり、コメントをしてみました。
(なお、重要なことですがこの分野は素人なので頓珍漢なコメントをしている可能性も高いです。後述しますが今回のパブコメの対象は認証アプリそのものというよりも法律だったこともあり、私の条文の解釈の仕方はおそらく間違っている可能性が高いです)
ということで個人的解釈に基づく今回のパブコメ募集の中身を深掘りしていきたいと思います。
パブコメの対象は何か?
これをみていると結局のところ「電子署名等確認業務受託者」、つまり現状でいう「プラットフォーム事業者」といわれるJ-LISのサーバに対してアクセスをしてマイナンバーカードの有効性確認等を実施することができる事業者に「個人番号カード用利用者証明用電子証明書」に関して「利用者に関する情報を適切に扱うこと」などを義務付ける法律であることがわかります。
では、なぜ内閣総理大臣を受託事業者に追加するのか?
今回やりたいことは認証アプリの「システム連携イメージ」に記載がある様に、デジタル庁の管理する「デジタル認証アプリサーバ」がJ-LISの管理するJPKIサーバに対してアクセスする、ということです。これは正に現在のプラットフォーム事業者が行なっているマイナンバーカードの有効性確認などと同じ構図となります。
これまでは前述の法律に則って総務省認定を受けた受託事業者のみがJPKIサーバへのアクセスを許可されていたわけですが、上記の図の構成、つまりデジタル庁がJPKIサーバへアクセスしようと思うと根拠法が存在しないわけです。
となると、行政機関の長である内閣総理大臣を受託事業者に加えておかないといけなくなる、とうロジックなんだと思います。
本当にそれでいいのか?どうコメントすべきか?
構図がわかったところでツッコミどころコメントすべき事項を探すわけですが、結局は従来の民間のプラットフォーム事業者と行政機関の差を紐解いていくのがアプローチが良いのではないかと思います。
利用者に関する情報を適正にあつかうこと、という点については民間だろうが行政機関だろうがそうでしょうね、という感じで違和感はないのですが、この利用者に関する情報は一体何を含むんだろうか?という点を見ていくとどうやら法律では「利用者証明利用者符号」を中心に考えているんじゃないかな?と思えてきます。
結局、マイナンバー(カードじゃなく)や証明書シリアルなど、背番号制度に関するアレルギーが意味不明に強い日本では符号や識別子に関しては非常に慎重に扱われる傾向にありますが、行政機関においては識別子についてはそもそも扱う前提があるんじゃなかったっけ?民間事業者が扱うことになるから認定制度があるんじゃなかったっけ?というところに行き着きます。
むしろ前回のポストでも記載した通り行政機関がやるから気持ち悪いポイントは公共・準公共・民間という異なるコンテキストを横断的に政府のIdentity Providerがまとめてフェデレーションをする、つまりデジタル庁の認証アプリサーバから見て利用者がどのRelying PartyとID連携しているのかがわかってしまう、という点にあるはずです。これはIdentity Providerを実装したことがある人ならわかると思いますが、利用者がどのRelying Partyに対して属性提供について同意しているかの状態を保持したり、PPIDを生成するための連携状態を保持したり、とログを含むとそれなりにID連携状態の情報を持つ必要が出てきてしまいます。
こうなってくるとこの法令施行規則に関する改正のポイントが受託事業者の対象に内閣総理大臣を加えることでプラットフォーム事業者と同じことをデジタル庁ができる様になりますよ、だけでは少々不足していると思われるので、法令改正対象には少なくとも「符号などに関する情報の適正な管理」だけでなく「ログを含むID連携状態の適正な管理」を求めていかないといけないのではないか?というのが現時点での私の結論です。
とりあえずそんな主旨でコメントはしてみたので、今後どうなっていくかは注視していきたいと思います。
2024年2月23日金曜日
パスキーの登録レスポンスの検証を行う
- サーバのエンドポイントからchallengeを取得する
- ブラウザAPIにchallenge、ユーザ情報、Relying Party情報、認証器への要求事項をセットして実行する
- 認証器をアクティブ化する(Touch IDをタッチする、など)
- ブラウザAPIからの返却値を検証し、認証器を登録する
- 登録を開始したセッションから最終的に認証器を登録するまでのセッションの一貫性
- フィッシングやMITM等により攻撃者の認証器を横から登録されることの防止
- challenge
- 認証器登録を開始する際にサーバ側で生成し、セッションに紐づけておきます。
- credential.create()のオプションに取得したchallengeを入れることで登録しようとしている認証器とchallengeの紐付けを行います
- 認証器がアクティブ化されcredential.create()からの返却値には認証器から受け取ったchallengeの値が入ります
- credential.create()の返却値をサーバ側へ渡し、サーバ側に認証器を登録します。この際、最初に生成しセッションに紐づけたchallengeと同じ値が認証器の登録結果から取得したものと同一かどうかを確認します
- origin
- 認証器を提示した先のサイトと、認証器を登録する先のエンドポイントが同一のホストであることを確認することでフィッシングなどを防ぎます
- credential.create()の返却値に含まれるclientJSONのメンバにoriginの値が入っているため、この値と認証器を登録するエンドポイントのホスト名が一致しているかどうかの検証が大切です。
- rpIdHash
- これoriginと同じ様にユーザがパスキーを登録しようとしているRelyingParty(credential.create()を実行する際のオプションに含まれます)と実際に認証器を登録するRelyingParty(credential.create()の結果のclientJSONに含まれます)が同一であることが必要です。
Challenge
origin
rpIDHash
The SHA-256 hash of the Relying Party ID that the credential is scoped to. The server will ensure that this hash matches the SHA256 hash of its own relying party ID in order to prevent phishing or other man-in-the-middle attacks.
2024年2月22日木曜日
パスキー登録APIのレスポンスを解析する
こんにちは、富士榮です。
先日、いろいろな認証器でパスキー登録をしてみましたが、レスポンスの中のフラグをどうやって取得するの?というあたりについて細かく解説していないの今回解説していきたいと思います。
navigator.credentials.create()メソッドの返却値の定義がこちらのドキュメントにあります。
https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/create#return_value_2
定義によると、PublicKeyCredentialが返されるようですね。
A Promise that resolves with an PublicKeyCredential instance matching the provided parameters. If no credential object can be created, the promise resolves with null.
では、PuiblicKeyCredentialの定義を見ていきましょう。
https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential
以下のメンバが含まれます。
- PublicKeyCredential.authenticatorAttachment
- PublicKeyCredential.id
- PublicKeyCredential.rawId
- PublicKeyCredential.response
- PublicKeyCredential.type
- rpIdHash (32 bytes)
- flags (1 bytes)
- signCount (4 bytes)
- attestedCredentialData (variable length)
- extensions (variable length)
Bit | 意味 | 説明 |
0 | User Presence (UP) | If set (i.e., to 1), the authenticator validated that the user was present through some Test of User Presence (TUP), such as touching a button on the authenticator. |
1 | - | - |
2 | User Verification (UV) | If set, the authenticator verified the actual user through a biometric, PIN, or other method. |
3 | Backup Eligibility (BE) | If set, the public key credential source used by the authenticator to generate an assertion is backup-eligible. This means that it can be backed up in some fashion (for example via cloud or local network sync) and as such may become present on an authenticator other than its generating authenticator. Backup-eligible credential sources are therefore also known as multi-device credentials. |
4 | Backup State (BS) | If set, the public key credential source is currently backed up (see Bit 3 for context). |
5 | - | - |
6 | Attested Credential Data (AT) | If set, the attested credential data will immediately follow the first 37 bytes of this authenticatorData. |
7 | Extension Data (ED) | If set, extension data is present. Extension data will follow attested credential data if it is present, or will immediately follow the first 37 bytes of the authenticatorData if no attested credential data is present. |
2024年2月21日水曜日
OAuth2.0 Security Best Current Practiceを読んでみる(5)
こんにちは、富士榮です。
すこし空きましたがこちらも続けていきます。
引き続き攻撃パターンと緩和策です。前回はアクセストークンインジェクションまで行きましたので続き7個目/18個のクロスサイトリクエストの偽造から行きたいと思います。
攻撃パターンと緩和策
- CSRF(クロスサイトリクエストフォージェリ)
- 攻撃者としては正規のクライアントが攻撃者の制御下にあるリソースにアクセスさせたいので、redirect_uriに不正にリクエストをインジェクションしようとします
- 緩和策
- 基本はstate/nonce/PKCEを正しくセッションに紐づけた形で利用することにつきます
- しかしながら例えば、クライアントがPKCEを使う場合は当然のことながら認可サーバがPKCEをサポートしていることを確認しないといけません
- 同じくstateを使う場合はstateの改ざんやスワッピングに対する耐性を持つような実装にしないと意味がありません
- 認可サーバは自身がPKCEをサポートしていることをクライアントが検知するための仕組みを提供する必要があります(MUST)
- 基本はメタデータを使うことになりますが、別のメカニズムで検知方法を提供しても問題はありません(MAY)
- stateやnonce(response_typeがid_tokenの場合)は認可レスポンスやトークンレスポンスを攻撃者が読み取れる環境においてはリプレイアタックなどに使われる可能性がありますが、その点はPKCEを使うことで対応ができます
- PKCEダウングレード攻撃
- PKCEをサポートしているものの、全てのフローがPKCE対応しているわけではない認可サーバはPKCEダウングレード攻撃を受ける可能性があります
- 例えばこんな実装です
- 認可リクエストにcode_challengeがあったらPKCEを有効にする、という判定ロジックが認可サーバに組み込まれている(逆にいうとcode_challengeが指定されない場合はPKCE対応しない)
- 上記の前提があるにも関わらずCSRF対策としてstateを使わない(PKCEを使うことを前提としてしまっている)
- まぁ、当然ですがこうなるとCSRF対策をしていないのと同じです
- 攻撃者はクライアントと認可サーバの間に入り、code_challengeを丸ごと削除してしまいセッションを乗っ取るわけです
- 緩和策
- この攻撃を受けている際の特徴は、認可リクエストにcode_challengeがない(削除されている)にも関わらずトークンエンドポイントへのアクセス時はcode_verifierが指定されることにあります
- code_challengeが削除されていることを検知するために認可サーバは認可コードとcode_challengeを紐づけて管理しないといけません。このことによりトークンエンドポイントに認可コードがPOSTされたタイミングでcode_challengeとの紐付けが有効かどうかを検証することが可能となります
- 加えて認可サーバは、認可エンドポイントへのリクエストにcode_challengeが指定されなかったにも関わらずトークンエンドポイントへcode_verifierが指定された場合はリクエストを拒否しないといけません(MUST)
- リソースサーバでのアクセストークンの漏洩
- リソースサーバが偽造されている場合(アクセストークンフィッシング)
- 基本はredirect_uri(リソースサーバ)とクライアントの紐付きがゆるいケースにおいて発生します
- リソースサーバが侵害されている場合
- ログの盗難やシステムの完全な掌握までさまざまなパターンがありますが、リソースサーバが侵害されるとアクセストークンが盗難されてしまいます。当たり前ですが
- 緩和策
- 基本的にSender Contraintトークンを使うという対策に尽きます
- 同じくAudience Restrictionも大切な対策です
- またこれも原則ですがリソースサーバでアクセストークンは他の機密情報と同じようにPlain textで保存したり他のシステムへ転送するなどは避け厳重に扱う必要があります
2024年2月20日火曜日
さまざまな認証器でパスキー登録APIの返却値を確認する
こんにちは、富士榮です。
OpenID Providerをパスキー対応にするためにパスキーの実装をしているわけですが、登録時のnavigator.credentials.createのAPIのレスポンスを各種認証器で確認してみました。
色々と解せない点(黄色で色付けしたセル)も出てきているのですが、実際にパスキーの実装をする際は色々なデバイス・ブラウザ・認証器の組み合わせで試験をしないといけないのでこういうチェックはしないといけないと思います。
利用 デバイス | ブラウザ | 認証器 | ユーザ認証 | レスポンス | ||||||
Transport | UP | UV | BE | BS | AT | ED | ||||
Mac | Safari | 内蔵Touch ID | 指紋 | Internal, hybrid | 1 | 1 | 1 | 1 | 1 | 0 |
Yubikey USB-C/NFC | PIN | nfc, usb | 1 | 1 | 0 | 0 | 1 | 0 | ||
Yubikey USB-C/Lightning | PIN | usb | 1 | 1 | 0 | 0 | 1 | 0 | ||
Yubikey USB-A/NFC (PIN未設定) | - | userVerificationをrequiredにしているのにPIN設定が求められず、登録できてしまう。ただしUVは0となる | ||||||||
eWBM GoldenGate USB-C | PIN | usb | 1 | 1 | 0 | 0 | 1 | 0 | ||
iPhone | FaceID | Internal, hybrid | 1 | 1 | 1 | 1 | 1 | 0 | ||
Android | 指紋 | Internal, hybrid | 1 | 1 | 1 | 1 | 1 | 0 | ||
Chrome | 内蔵Touch ID | 指紋 | Internal, hybrid | 1 | 1 | 1 | 1 | 1 | 0 | |
Yubikey USB-C/NFC | PIN | nfc, usb | 1 | 1 | 0 | 0 | 1 | 0 | ||
Yubikey USB-C/Lightning | PIN | usb | 1 | 1 | 0 | 0 | 1 | 0 | ||
Yubikey USB-A/NFC (PIN未設定) | - | userVerificationをdiscouragedにしてもPIN設定が求められる | ||||||||
eWBM GoldenGate USB-C | PIN | operation not allowed | ||||||||
iPhone | FaceID | Internal, hybrid | 1 | 1 | 1 | 1 | 1 | 0 | ||
Android | 指紋 | Internal, hybrid | 1 | 1 | 1 | 1 | 1 | 0 | ||
Firefox | 内蔵Touch ID | 指紋 | internal | 1 | 1 | 1 | 1 | 1 | 0 | |
Yubikey USB-C/NFC | PIN | null | 1 | 1 | 0 | 0 | 1 | 0 | ||
Yubikey USB-C/Lightning | PIN | null | 1 | 1 | 0 | 0 | 1 | 0 | ||
Yubikey USB-A/NFC (PIN未設定) | - | userVerificationをrequiredにしているのにPIN設定が求められず、登録できてしまう。ただしUVは0となる | ||||||||
eWBM GoldenGate USB-C | PIN | null | 1 | 1 | 0 | 0 | 1 | 0 | ||
iPhone | FaceID | internal | 1 | 1 | 1 | 1 | 1 | 0 | ||
Android | 指紋 | internal | 1 | 1 | 1 | 1 | 1 | 0 | ||
iPhone | Safari | FaceID | FaceID | Internal, hybrid | 1 | 1 | 1 | 1 | 1 | 0 |
Yubikey USB-C/NFC | PIN | nfc, usb | 1 | 1 | 0 | 0 | 1 | 0 | ||
Yubikey USB-A/NFC (PIN未設定) | - | userVerificationをrequiredにしているのにPIN設定が求められず、登録できてしまう。ただしUVは0となる | ||||||||
Authntrend AT-Key/NFC | 指紋 | nfc | 1 | 1 | 0 | 0 | 1 | 0 | ||
Android | 指紋 | Internal, hybrid | 1 | 1 | 1 | 1 | 1 | 0 | ||
Chrome | FaceID | FaceID | Internal, hybrid | 1 | 1 | 1 | 1 | 1 | 0 | |
Yubikey USB-C/NFC | PIN | nfc, usb | 1 | 1 | 0 | 0 | 1 | 0 | ||
Yubikey USB-A/NFC (PIN未設定) | - | userVerificationをrequiredにしているのにPIN設定が求められず、登録できてしまう。ただしUVは0となる | ||||||||
Authntrend AT-Key/NFC | 指紋 | nfc | 1 | 1 | 0 | 0 | 1 | 0 | ||
Android | 指紋 | Internal, hybrid | 1 | 1 | 1 | 1 | 1 | 0 | ||
Android | Chrome | OSアンロック | 指紋 | Internal, hybrid | 1 | 1 | 1 | 1 | 1 | 0 |
Yubikey USB-C/NFC | PIN | ble, hybrid, internal, nfc, usb | 1 | 1 | 0 | 0 | 1 | 0 | ||
Yubikey USB-C/Lightning | PIN | ble, hybrid, internal, nfc, usb | 1 | 1 | 0 | 0 | 1 | 0 | ||
Yubikey USB-A/NFC (PIN未設定) | - | userVerificationをrequiredにするとPINが求められ、discouragedにするとPIN設定が求められず、UVが0で登録される | ||||||||
eWBM GoldenGate USB-C | PIN | ble, hybrid, internal, nfc, usb | 1 | 1 | 0 | 0 | 1 | 0 |
ポイントとしては、
- Firefoxを使うとTransportが上手く取れない
- AndroidのChromeではcross-platform認証器のTransportがおかしい
- userVerificationをtrueにセットしてPIN未設定のYubikeyを使うと本来はPIN設定が求められるべきだと思うがそのまま登録ができてしまうケースがある
などあるので、結局はちゃんとflagsの値を見て期待通りの認証器の状態となっているかを確認しないといけない、、ということです。