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

WebAuthn実装してみたー

 WebAuthn実装してみたー

Yuto Takamune

August 22, 2021
Tweet

More Decks by Yuto Takamune

Other Decks in Programming

Transcript

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

    View full-size slide

  2. - ただの人間です
    - s1290035(学部一年)
    - AizuGeekDojo SA
    - Zli運営
    - SGG創設者&運営
    - コミュニティ活動いろいろ
    - LINE API実践ガイド LINEログイン章 著者
    - 最近やってること)
    - Nuxt.js、Node.jsを使ったWeb開発
    - AWS使ってインフラ構築
    - ラズパイとかESP32とかで遊ぶ
    - アイデンティティ管理、認証系
    - 最近CTFと競プロを始めた
    - ツイ廃
    - 保有資格: 漢検3級、応用情報技術者試験
    自己紹介 ポートフォリオ(shinbunbun.info)
    @shinbunbun_
    ¥3993 マイナビ出版

    View full-size slide

  3. はじめに
    - 本資料はWebAuthn勉強中のただの大学一年生が書いたものな
    ので、間違いがある可能性があります。あらかじめご了承くださ
    い。
    - ちゃんとした知識をつけたい方は是非一次情報(仕様書)を一緒
    に読んで勉強しましょう
    - 本記事はほとんどMDNとW3Cの仕様書に載っている内容なの
    で、詳しく知りたい方はぜひ仕様書を読んでみてください

    View full-size slide

  4. WebAuthnとは
    - Web Authenticationの略
    - Credential Management API の拡張機能
    - ウェブサイトの認証に公開鍵暗号を使用する
    - →パスワードレス認証やMFAが簡単にできる
    - Authenticator(認証機)というものがあり、その中で生成された公
    開鍵を使って認証する
    - 例えばWindows HelloとかTouchIDとかUSBトークンとか

    View full-size slide

  5. FIDO2について
    - W3C WebAuthn, CTAP1, CTAP2という3つの仕様からできてい

    - WebAuthn: 前述の通り。これはあくまでブラウザ側の規格
    - CTAP: ブラウザと外部認証機間で通信するための仕様
    - CTAP1: 従来FIDO U2Fと呼ばれていたもの。FIDO2の規格に対応したブラウ
    ザ、OSでFIDO U2F対応デバイスが使用できる
    - CTAP2: FIDO2で新しく定義されたCTAP仕様

    View full-size slide

  6. 登場人物
    - Server
    - (だいぶ語弊があるけど)Relying Party(RP)って呼ばれるやつ
    - クライアントアプリケーションのこと
    - 今回で言うと僕が作った認証アプリのバックエンド
    - Browser
    - u7693(妖精王)の主食
    - Rustで書かれてるServoが特に美味しいらしい
    - Authenticator
    - 認証機
    - 指紋認証機(TouchID的なやつ)とかUSBトークンとかそういうやつ

    View full-size slide

  7. 登録(Credential作成)について

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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作成完了🎉🎉🎉

    View full-size slide

  11. もうちょいちゃんと説明します

    View full-size slide

  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(前述)

    View full-size slide

  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にな

    View full-size slide

  14. Credential作成の説明
    5. PublicKeyCredentialというCredentialデータが作成され、サーバにおくられ

    6. サーバーが検証を行う(検証には主に以下のものが含まれる)
    a. challenge が送信時と同じものであるかの確認
    b. origin が期待された origin となっていることの保証
    c. clientDataHash の署名とAuthorizerの証明書チェーンを使った attestation
    の検証

    View full-size slide

  15. 認証(Credential取得)について

    View full-size slide

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

    View full-size slide

  17. 認証の流れ

    View full-size slide

  18. めちゃ雑に説明すると
    0. BrowserからServerに「認証始めたいんだがお前(RP)の情報教えてくれ
    ん?」ってリクエストする
    1. Serverからchallenge(and more)が返ってくる
    2. Browserがその情報とかもろもろ含めてAuthenticatorに「認証してくれ
    や」ってリクエストする
    3. Authenticatorがユーザーを認証して、登録時に生成した秘密鍵を使って
    新しい署名を作成する
    4. Authenticatorが署名とかもろもろをBrowserに返す
    5. Browserがいい感じに加工してServerに返す
    6. Serverで署名検証とかいろいろいい感じにvalidationする
    認証完了🎉🎉

    View full-size slide

  19. もうちょいちゃんと説明します

    View full-size slide

  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()を呼び出

    View full-size slide

  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による署名を検証するための公開
    鍵が用いられているか

    View full-size slide

  22. もうちょい仕様に踏み込んでみる

    View full-size slide

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

    View full-size slide

  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. 正常に検証されて信頼できることが判明したら新しいクレデンシャルをアカウントに登録する

    View full-size slide

  25. 鍵ペアって毎回作るんですか? by妖精王
    - こんな質問があったので簡単に解説します
    -
    AuthenticatorはCredentialを作成するたびに鍵ペアを生成し、秘密鍵を安全に保管し
    ます
    - 公開鍵はRelying Partyに渡すのですが、その際にAuthenticator”自身”の証明書に紐
    づく秘密鍵で署名を行います
    -
    Relying Partyは、Attestationの公開鍵がルート証明書に正しくチェーンアップされてい
    るかということと署名を検証することで、
    Authenticatoの信頼性を確認できる
    - ちなみにルート証明書はFIDO Metadata Serviceなどのレジストリで公開されて
    おり、AuthenticatorDataのattestedCredentialDataに含まれるaaguidを使用
    して取得できる

    View full-size slide

  26. 実装で苦労した点とか

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  32. さいごに

    View full-size slide

  33. 今後の展望
    - とりあえず一連の検証は全部実装する
    - Resident Keyを使った実装がうまくいかなかったのでリベンジする
    - これが実装できれば認証時にクライアントに登録されている鍵一覧から選択
    できるため、ユーザー名すら入力する必要がなくなる
    - Federated identity実装してみる(OIDCとか使えるのかも...?)
    - FIDO2のCTAPさわってみる
    - LINEのFIDO2 Serverさわってみる
    - W3Cの仕様書をもっとちゃんと読み込む
    - WebAuthn以外もいろいろな知識が必要(例えば電子署名とかPKIと
    か)なので、その辺をもう一回復習する

    View full-size slide

  34. みんなもれっつうぇぶおーすん!

    View full-size slide