Upgrade to Pro — share decks privately, control downloads, hide ads and more …

WebAuthn実装してみたー

 WebAuthn実装してみたー

4e4410453964b11bc6077c72334bf8ec?s=128

Yuto Takamune

August 22, 2021
Tweet

Transcript

  1. WebAuthn実装してみたー s1290035 会津大学学部一年 しんぶんぶん

  2. - ただの人間です - s1290035(学部一年) - AizuGeekDojo SA - Zli運営 -

    SGG創設者&運営 - コミュニティ活動いろいろ - LINE API実践ガイド LINEログイン章 著者 - 最近やってること) - Nuxt.js、Node.jsを使ったWeb開発 - AWS使ってインフラ構築 - ラズパイとかESP32とかで遊ぶ - アイデンティティ管理、認証系 - 最近CTFと競プロを始めた - ツイ廃 - 保有資格: 漢検3級、応用情報技術者試験 自己紹介 ポートフォリオ(shinbunbun.info) @shinbunbun_ ¥3993 マイナビ出版
  3. はじめに - 本資料はWebAuthn勉強中のただの大学一年生が書いたものな ので、間違いがある可能性があります。あらかじめご了承くださ い。 - ちゃんとした知識をつけたい方は是非一次情報(仕様書)を一緒 に読んで勉強しましょう - 本記事はほとんどMDNとW3Cの仕様書に載っている内容なの

    で、詳しく知りたい方はぜひ仕様書を読んでみてください
  4. WebAuthnとは - Web Authenticationの略 - Credential Management API の拡張機能 -

    ウェブサイトの認証に公開鍵暗号を使用する - →パスワードレス認証やMFAが簡単にできる - Authenticator(認証機)というものがあり、その中で生成された公 開鍵を使って認証する - 例えばWindows HelloとかTouchIDとかUSBトークンとか
  5. FIDO2について - W3C WebAuthn, CTAP1, CTAP2という3つの仕様からできてい る - WebAuthn: 前述の通り。これはあくまでブラウザ側の規格

    - CTAP: ブラウザと外部認証機間で通信するための仕様 - CTAP1: 従来FIDO U2Fと呼ばれていたもの。FIDO2の規格に対応したブラウ ザ、OSでFIDO U2F対応デバイスが使用できる - CTAP2: FIDO2で新しく定義されたCTAP仕様
  6. 登場人物 - Server - (だいぶ語弊があるけど)Relying Party(RP)って呼ばれるやつ - クライアントアプリケーションのこと - 今回で言うと僕が作った認証アプリのバックエンド

    - Browser - u7693(妖精王)の主食 - Rustで書かれてるServoが特に美味しいらしい - Authenticator - 認証機 - 指紋認証機(TouchID的なやつ)とかUSBトークンとかそういうやつ
  7. 登録(Credential作成)について

  8. よくわかんないので実際に触ってみよう

  9. Credential作成の流れ https://developer.mozilla.org/ja/docs/Web/API/Web_Authentication_API

  10. めちゃ雑に説明すると 0. BrowserからServerに「認証始めたいんだがお前(RP)の情報教えてくれ ん?」ってリクエストする 1. ServerからRPの情報(and more)が返ってくる 2. Browserがその情報とかもろもろ含めてAuthenticatorに「証明書作ってく れや」ってリクエストする

    3. Authenticatorがユーザーを認証して鍵ペアを生成する 4. Authenticatorが認証情報をBrowserに返す 5. Browserがいい感じに加工してServerに返す 6. Serverで署名検証とかいろいろいい感じにvalidationする Credential作成完了🎉🎉🎉
  11. もうちょいちゃんと説明します

  12. Credential作成の説明 1.サーバーに認証開始要求を送る a. この時、サーバからはchallenge, id, rpが返ってくる i. challenge: リプレイアタック防止のために、十分なエントロピーを持ったランダムな文字列(暗号論的 擬似乱数など)を用意する必要がある

    ii. id: ユーザー固有のid iii. rp: リライングパーティ(クライアントアプリケーション)の情報 2-1. いくつかのパラメータを含めてnavigator.credentials.create()を呼び出す b. 今回のサンプルでは以下のパラメータを使用した c. rpのid, name d. userのid, name, displayName e. publicKeyのtype, alg(アルゴリズム) f. attestation(RPにAuthenticationデータを渡すかどうか) g. timeout時間 h. challenge(前述)
  13. Credential作成の説明 2-2. ブラウザが内部的にauthenticatorのauthenticatorMakeCredential()を呼 び出す 3. Authenticatorが新しい鍵ペアと Attestation を作成 a. Attestation:

    credential IDs, credential key pairs, signature countersなどを含む 認証情報を指す b. 厳密に言えば→PIN の入力や指紋・虹彩認証などを通してユーザー確認をした後 に鍵ペアを生成し、秘密鍵を安全に保存する。もう一方の公開鍵はAuthenticator 固有の秘密鍵で署名される。 4. Authenticatorがブラウザにデータを返す a. 一意のcredential idとattestation情報がブラウザに返され、attestation objectにな る
  14. Credential作成の説明 5. PublicKeyCredentialというCredentialデータが作成され、サーバにおくられ る 6. サーバーが検証を行う(検証には主に以下のものが含まれる) a. challenge が送信時と同じものであるかの確認 b.

    origin が期待された origin となっていることの保証 c. clientDataHash の署名とAuthorizerの証明書チェーンを使った attestation の検証
  15. 認証(Credential取得)について

  16. よくわかんないので実際に触ってみよう

  17. 認証の流れ

  18. めちゃ雑に説明すると 0. BrowserからServerに「認証始めたいんだがお前(RP)の情報教えてくれ ん?」ってリクエストする 1. Serverからchallenge(and more)が返ってくる 2. Browserがその情報とかもろもろ含めてAuthenticatorに「認証してくれ や」ってリクエストする

    3. Authenticatorがユーザーを認証して、登録時に生成した秘密鍵を使って 新しい署名を作成する 4. Authenticatorが署名とかもろもろをBrowserに返す 5. Browserがいい感じに加工してServerに返す 6. Serverで署名検証とかいろいろいい感じにvalidationする 認証完了🎉🎉
  19. もうちょいちゃんと説明します

  20. Credential取得の説明 1.サーバーに認証開始要求を送る a. この時、サーバからはchallengeが返ってくる b. 今回のサンプルではuserのメールアドレスをリクエストに含め、それに対応した CredentialIdを返す処理を書いた 2-1. いくつかのパラメータを含めてnavigator.credentials.get()を呼び出す c.

    今回のサンプルでは以下のパラメータを利用した i. rpId ii. allowCredentials(許可されるCredentialのリスト) iii. timeout iv. challenge(前述) 2-2.ブラウザが内部的にauthenticatorのauthenticatorGetCredential()を呼び出 す
  21. Credential取得の説明 3. Authenticatorが登録時に生成された秘密鍵を用いて署名を行うことで 新しいAssertionを生成 a. 厳密に言えば→PIN の入力や指紋・虹彩認証などを通してユーザー確認を した後に鍵ペアを生成し、秘密鍵を安全に保存する。もう一方の公開鍵は Authenticator固有の秘密鍵で署名される。 4.

    AuthenticatorがauthenticatorDataとassertionの署名をBrowserに返す 5. 認証情報がBrowserに返される 6. Serverが検証を行う(主に以下の検証が含まれる) a. rpIdがこのサービスで想定しているものか b. Authenticatorによって署名されたchallengeがserverによって生成されたも のと一致しているか c. 登録要求の際に保管した、Authenticatorによる署名を検証するための公開 鍵が用いられているか
  22. もうちょい仕様に踏み込んでみる

  23. Credential作成時の検証処理について

  24. Credential作成時の検証処理について 1. ※AuthenticatorResponse.clientDataJSONをCとする 2. C.typeがwebauthn.createであることを確認する 3. C.challengeがoption.challengeのbase64url encodeに等しいことを確認する 4. C.originがRPのoriginと一致することを確認する

    5. ※hashをCのSHA-256ハッシュ値とする 6. AuthenticatorResponse.attestationObjectをCBOR decodingし、fmt(attestation statement format), authData( authenticator data), attStmt(attestation statement)を取得する 7. authDataのrpIdHashがRPIDのSHA-256ハッシュであることを確認する 8. authData内のflagsのUser Presentビット, User Verifiedビットが1であることを確認する 9. authData内のattStmtのalgがnavigator.credentials.create()のパラメータで指定したalgと一致していることを確認 する 10. authData内のfmtからAttestation Statementのフォーマットを決定する 11. attStmtが正しい証明書であり、署名が有効であることを検証する 12. 検証が成功した場合、Authenticatorのルート証明書を取得する 13. (自己署名証明書でない場合)Attestationの公開鍵がルート証明書に正しくチェーンアップされているかを確認する 14. credentialIdがまだ他のユーザーに登録されていないことを確認する 15. 正常に検証されて信頼できることが判明したら新しいクレデンシャルをアカウントに登録する
  25. 鍵ペアって毎回作るんですか? by妖精王 - こんな質問があったので簡単に解説します - AuthenticatorはCredentialを作成するたびに鍵ペアを生成し、秘密鍵を安全に保管し ます - 公開鍵はRelying Partyに渡すのですが、その際にAuthenticator”自身”の証明書に紐

    づく秘密鍵で署名を行います - Relying Partyは、Attestationの公開鍵がルート証明書に正しくチェーンアップされてい るかということと署名を検証することで、 Authenticatoの信頼性を確認できる - ちなみにルート証明書はFIDO Metadata Serviceなどのレジストリで公開されて おり、AuthenticatorDataのattestedCredentialDataに含まれるaaguidを使用 して取得できる
  26. 実装で苦労した点とか

  27. Goつらぴ - 未経験言語で実装するのマジ辛かった - CBORつらすぎ(後述) - TSでかけばよかった(後悔)

  28. base64urlでどハマり - PublicKeyCredential.idがbase64urlで返ってくるため、btoaで ArrayBufferに変換できなかった - base64に変換してからbtoaでArrayBufferにした

  29. Authenticator Dataの処理 - ArrayBufferの先頭32バイトがrpIdHash、次の1バイトがflags、次 の4バイトがsignCountといったようになっているため、これらを取 り出さないといけない - 加えてflagsは0ビット目がUser Present、2ビット目がUser Verified...といったようになっているため、シフト演算子を使って1

    ビットずつ取り出すのがめんどくさかった
  30. CBOR - JSONをバイナリで表現したもの(らしい) - Goでこれ扱うのが難しすぎて諦めた - ライブラリ使ったけどうまく動かなかった

  31. 署名検証実装できなかった - CBORの件とかいろいろあった結果、署名検証が実装できなかっ た

  32. さいごに

  33. 今後の展望 - とりあえず一連の検証は全部実装する - Resident Keyを使った実装がうまくいかなかったのでリベンジする - これが実装できれば認証時にクライアントに登録されている鍵一覧から選択 できるため、ユーザー名すら入力する必要がなくなる -

    Federated identity実装してみる(OIDCとか使えるのかも...?) - FIDO2のCTAPさわってみる - LINEのFIDO2 Serverさわってみる - W3Cの仕様書をもっとちゃんと読み込む - WebAuthn以外もいろいろな知識が必要(例えば電子署名とかPKIと か)なので、その辺をもう一回復習する
  34. みんなもれっつうぇぶおーすん!