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

パスワードのない未来のための Firebaseで実装するFIDO2 / FIDO2 actualized by Firebase for the password-less future

k2wanko
April 28, 2020

パスワードのない未来のための Firebaseで実装するFIDO2 / FIDO2 actualized by Firebase for the password-less future

FIDO(ファイド)はユーザーの認証をローカルで行うため、ユーザーの識別情報がネットワーク上でやりとりされないセキュアなプロトコルです。 ユーザーからのメリットとしてはパスワードを覚える必要がなくワンタップで簡単にログインできますが、実装に関する情報はまだまだ少ないのが現状です。 本トークではサードパーティ認証を使わず、ファーストパーティ認証としてどのようにAndroidアプリにFIDO2を実装し運用していくかについて紹介します。 デモにはFirebase AuthenticationとCloud Functionsを利用します。 サーバーサイドはNode.jsで実装を行います。

https://line.connpass.com/event/173284/

k2wanko

April 28, 2020
Tweet

More Decks by k2wanko

Other Decks in Technology

Transcript

  1. パスワードのない未来のための
    Firebaseで実装するFIDO2
    2020/2/20
    コキチーズ@k2wanko
    LINE Engineer
    Firebase Japan User Group Organizer

    View Slide

  2. パスワードのない未来のための
    Firebaseで実装検証するFIDO2
    2020/2/20
    コキチーズ@k2wanko
    LINE Engineer
    Firebase Japan User Group Organizer

    View Slide

  3. パスワード認証

    View Slide

  4. Web Service
    Password
    Service
    Device
    最高のサービスを
    提供するゾ!

    View Slide

  5. パスワードのリスクについて

    View Slide

  6. パスワードリスク
    Device
    phishing site
    よく使われるパスワード
    他社で漏洩したパスワード
    パスワードのメモ
    色んなリスクがあるなぁ

    View Slide

  7. FIDO

    View Slide

  8. FIDOとは
    - FIDO Alliance が仕様策定、標準化する認証プロトコル
    - FIDOに対応したデバイスでFIDOに対応したサーバーにメーカー問わず
    利用できる
    - 公開鍵認証を利用して、シークレットはネットワークに流れず認証を行う

    View Slide

  9. FIDOの認証方法
    Device
    Register public key
    Challenge code
    Challenge code
    Signature
    Validation signature
    公開鍵暗号方式を使って
    登録した人と認証する人の検
    証をするよ
    Access token,
    Session cookie

    View Slide

  10. FIDOの認証方法
    Device
    Register public key
    Challenge code
    Challenge code
    Signature
    Validation signature
    公開鍵暗号方式を使って
    登録した人と認証する人の検
    証をするよ
    Access token,
    Session cookie
    Sign in request

    View Slide

  11. FIDO認証のメリット
    1. ユーザーはパスワードを覚える必要がない
    2. 物理的なデバイスを盗まれなければなりすましのリスクが低い

    View Slide

  12. FIDO2
    WebAuthn

    View Slide

  13. WebAuthn
    - ブラウザで公開鍵認証を行うための仕様
    - Chrome、Firefox、Safariなど主要なブラウザで実装されている。
    - FIDO2と呼ばれるものもWebAuthnと同様のもの
    - FIDO2 API for AndroidはWeb Authnクライアント
    https://developers.google.com/identity/fido/android/native-apps

    View Slide

  14. サンプルアプリのDEMO
    https://github.com/k2wanko/fido2-example

    View Slide

  15. Androidを使った認証

    View Slide

  16. Yubikeyを使った認証

    View Slide

  17. FIDO2の実装

    View Slide

  18. 利用するプラットフォーム

    View Slide

  19. Firebase
    - モバイル、WebアプリのBackendを提供してくれるサービス
    - Googleに買収され、今はGCPの一部になっている。

    View Slide

  20. Firebaseで使用するサービス
    - Auth
    - Cloud Functions
    - Firestore

    View Slide

  21. Firebase Auth
    - パスワード認証、SNS認証、電話番号認証など
    さまざまな認証方法を提供してくれるサービス
    - カスタム認証を使うことで
    サポートされていない認証方式を実現することも可能

    View Slide

  22. Cloud Functions
    - 手軽にコード実行ができるサービス
    - HTTPサーバーとしてや、ユーザーの作成やデータベースへの書き込みなどを
    トリガーに実行できる。
    - FIDOサーバーとして利用

    View Slide

  23. Firestore
    - ドキュメント指向のデータベース
    - クライアントから直接読み書きができる
    - サーバーの変更をクライアントにリアルタイムに同期される
    - 独自のセキュリティルールでアクセスコントロールを宣言的にできる
    - スケールして低コストで使えるデータベース
    - ChallengeやCredentialの保存に利用

    View Slide

  24. 実装の参考にさせてもらったもの
    - Googleが提供しているCodelab
    - https://github.com/googlecodelabs/fido2-codelab

    View Slide

  25. Androidアプリの登録
    - Firebaseでは計測や認証の場面で
    アプリを特定するための事前登録を行います

    View Slide

  26. Androidアプリの登録
    - 署名用の鍵のSHA256の取得
    keytool -exportcert -list -v -alias [alias] -keystore
    /path/to/key.keystore

    View Slide

  27. Androidアプリの登録
    - https://console.firebase.google.com/u/0/project/k2webauthn/settings/general/android:dev.k2wank
    o.examples.fido2

    View Slide

  28. https://[project-id].web.app/.well-known/assetlinks.json
    [
    {
    "relation": [
    "delegate_permission/common.handle_all_urls"
    ],
    "target": {
    "namespace": "android_app",
    "package_name": "dev.k2wanko.examples.fido2",
    "sha256_cert_fingerprints": [
    “64:98:9A:CC:5B:A0:DB:42:BC:8..."
    ]
    }
    }
    ]

    View Slide

  29. assetlinks.json
    - AndroidでOriginの検証のために必要。
    - FirebaseではDyamic Linksのために自動で作成される。
    - Hostingをデプロイすれば上書きはできるので安心
    (サポートに問い合わせたら教えてくれた)

    View Slide

  30. assetlinks.json
    AndroidManifest.xml
    android:resource="@string/asset_statements" />
    strings.xml

    [
    {
    \"include\":
    \"https://[project-id].web.app/.well-known/assetlinks.json\"
    },
    ]

    View Slide

  31. Registrationの実装
    Cloud FunctionsでCredentialの登録
    export const registerRequest = functions.https.onCall(...)
    export const registerResponse = functions.https.onCall(...)

    View Slide

  32. Callable Functionについて
    - Firebase SDK経由でCloud Functionsを呼び出すシンタックスシュガー
    - Firebase Authで認証していたら自動でトークンを付与して、
    検証までしといてくれる便利なやつ
    Androidから呼び出す例
    FirebaseFunctions.getHttpsCallable("registerRequest").call(data)

    View Slide

  33. Instance IDについて
    - 広告やPush用に使われているアプリを識別するためのトークン
    - 認証なしでとりあえず付与されるので今回はデバイス識別に利用
    - GDPR対応させるなら、このIDを使う前にユーザーの同意を取る必要がある
    - 再インストールすると変わる
    Callable Function ではInstance ID Tokenという形で取得ができ:の前半が
    Instance ID
    fosrj-JUrl4:APA91bEvf7OixMTK...

    View Slide

  34. Instance IDの検証
    curl -H "Authorization: key=[API_KEY]"
    "https://iid.googleapis.com/iid/info/fosrj-JUrl4:APA91bEvf7OixM...
    NDh?details=true

    View Slide

  35. Registrationの実装 (Android)
    - Androidには Fido2ApiClient というAPIがある。
    val fidoClient: Fido2ApiClient = Fido.getFido2ApiClient(this)
    fidoClient.getRegisterPendingIntent(publicKeyCredentialCreationOptions)
    fidoClient.getSignPendingIntent(publicKeyCredentialRequestOptions)

    View Slide

  36. val task =
    fidoClient.getRegisterPendingIntent(publicKeyCredentialCreationOptions)
    val intent = task.await()
    startIntentSenderForResult(intent.intentSender,
    REQUEST_FIDO2_REGISTER,
    ull,
    0,
    0,
    0,
    null)
    Registrationの実装 (Android)

    View Slide

  37. setRp(
    PublicKeyCredentialRpEntity("k2webauthn.web.app", "FIDO2 Demo", null)
    )
    第一引数は rpIDといい、assetlinks.jsonのあるドメインを指定する。
    Registrationの実装 (Android)

    View Slide

  38. Registrationの実装 (Android)
    setAuthenticatorSelection(AuthenticatorSelectionCriteria.Builder().apply {
    setAttachment(authenticationSelection)
    }.build())
    authenticationSelection = Attachment.PLATFORM
    authenticationSelection = Attachment.CROSS_PLATFORM

    View Slide

  39. Registrationの実装
    Platform Cross Platform

    View Slide

  40. Registrationの実装 (Functions)
    export const registerRequest = functions.https.onCall(async (data: object | null, context: CallableContext)
    => {
    ...
    const iid = await getIIDInfo(instanceIdToken)
    const response = await f2l.attestationOptions()
    const challenge = coerceToBase64Url(response.challenge, 'challenge')
    await db.doc(`/challenges/${iid.id}`)
    .withConverter(Challenge)
    .set({
    type: 'registration',
    challenge
    })
    return {
    challenge
    }

    View Slide

  41. Registrationの実装 (Android)
    setChallenge(res.challenge.decodeBase64())

    View Slide

  42. val task =
    fidoClient.getRegisterPendingIntent(publicKeyCredentialCreationOptions)
    val intent = task.await()
    startIntentSenderForResult(intent.intentSender,
    REQUEST_FIDO2_REGISTER,
    ull,
    0,
    0,
    0,
    null)
    Registrationの実装 (Android)

    View Slide

  43. Registrationの実装

    View Slide

  44. Registrationの実装 (Android)
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    when(requestCode) {
    REQUEST_FIDO2_REGISTER -> {
    ...
    handleRegisterResponse(data)

    View Slide

  45. Registrationの実装 (Android)
    val rawId = response.keyHandle.toBase64()
    val clientDataJSON = response.clientDataJSON.toBase64()
    val credentialId = response.keyHandle.toBase64()
    val attestationObject = response?.attestationObject?.toBase64() ?: [email protected]
    val data = hashMapOf(
    "rawId" to rawId,
    "credentialId" to credentialId,
    "clientDataJSON" to clientDataJSON,
    "attestationObject" to attestationObject,
    "apkSigSha256" to getApkSigSha256()
    )
    val res = functions.registerResponse(data)

    View Slide

  46. Registrationの実装 (Functions)
    export const registerResponse = functions.https.onCall(async (data:
    RegisterResponseOption | null, context: CallableContext) => {

    const result = await f2l.attestationResult(clientAttestationResponse,
    attestationExpectations, {
    android: {
    rpId: HOSTNAME
    }
    })

    View Slide

  47. Registrationの実装 (Functions)
    let uid = ''
    if (auth) {
    uid = auth.uid
    } else {
    const user = await firebase.auth().createUser({})
    uid = user.uid
    }

    View Slide

  48. Registrationの実装 (Functions)
    const transports: string[] = []
    switch (result.authnrData.get('fmt')) {
    case 'fido-u2f':
    transports.push('usb')
    break
    default:
    transports.push('internal')
    }

    View Slide

  49. Registrationの実装 (Functions)
    const credential: Credential = {
    user: uid,
    credId: coerceToBase64Url(result.authnrData.get('credId'), 'credId'),
    publicKey: result.authnrData.get('credentialPublicKeyPem'),
    aaguid: coerceToBase64Url(result.authnrData.get('aaguid'), 'aaguid'),
    prevCounter: result.authnrData.get('counter'),
    transports,
    created: firebase.firestore.FieldValue.serverTimestamp()
    }
    const credentialRef = db.doc(`/instanceId/${iid.id}/credentials/${credential.credId}`)
    await credentialRef.create(credential)

    View Slide

  50. Registrationの実装 (Functions)
    const token = await firebase.auth().createCustomToken(uid, {
    webauthn: true
    })
    return {
    token
    }

    View Slide

  51. Registrationの実装 (Android)
    auth.signInWithCustomToken(res!!.token).await()

    View Slide

  52. Registrationの実装まとめ
    - サーバーで生成したChallengeにAndroidで署名を行いサーバーで検証
    - デバイスを識別する何かが必要、FirebaseではInstance IDでよさそう
    - NativeのFIDO Api Clientだとattestationはandroid-safetynet限定(?)
    (要確認)

    View Slide

  53. Sign Inの実装 (Functions)
    challengeとCredentials一覧を返す。
    const allowCredentials: allowCredential[] = []
    credentialsSnap.forEach(doc => {
    const cred = doc.data() as Credential
    allowCredentials.push({
    credId: cred.credId,
    type: 'public-key',
    transports: cred.transports
    })
    })
    const res = {
    challenge,
    allowCredentials
    }

    View Slide

  54. Sign Inの実装 (Android)
    val publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions.Builder().apply {
    setRpId("k2webauthn.web.app")
    setAllowList(res.allowCredentials.map {
    PublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY.toString(),
    it["credId"].toString().decodeBase64(), (it["transports"] as List<*>).map { transport ->
    when(transport) {
    Transport.USB.toString() -> Transport.USB
    else -> Transport.INTERNAL
    }
    })
    })
    // Set challenge
    setChallenge(res.challenge.decodeBase64())
    }.build()

    View Slide

  55. Sign Inの実装 (Android)
    val task = fidoClient.getSignPendingIntent(publicKeyCredentialRequestOptions)
    val intent = task.await()
    startIntentSenderForResult(intent.intentSender,
    REQUEST_FIDO2_SIGNIN,
    null,
    0,
    0,
    0,
    null)

    View Slide

  56. Sign Inの実装

    View Slide

  57. Sign Inの実装 (Android)
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    when(requestCode) {
    REQUEST_FIDO2_SIGNIN -> {
    handleSignInResponse(data)

    View Slide

  58. Sign Inの実装 (Android)
    val res = functions.signInResponse(hashMapOf(
    "type" to PublicKeyCredentialType.PUBLIC_KEY.toString(),
    "rawId" to response.keyHandle.toBase64(),
    "clientDataJSON" to response.clientDataJSON.toBase64(),
    "authenticatorData" to response.authenticatorData.toBase64(),
    "signature" to response.signature.toBase64(),
    "userHandle" to response.userHandle?.toBase64(),
    "apkSigSha256" to getApkSigSha256()
    ))

    View Slide

  59. Sign Inの実装 (Functions)
    const result = await f2l.assertionResult(clientAssertionResponse, assertionExpectations, {
    android: {
    rpId: HOSTNAME
    }
    })
    credential.prevCounter = result.authnrData.get('counter')
    await credentialSnap.ref.set(credential)
    const token = await firebase.auth().createCustomToken(credential.user, {
    webauthn: true
    })
    return {
    token
    }

    View Slide

  60. Sign Inの実装 (Android)
    auth.signInWithCustomToken(res!!.token).await()

    View Slide

  61. Sign In処理は重要な箇所で都度行うようにしよう
    認証時だけじゃなくて、
    - 決済情報の登録編集時
    - 送金の確認
    などアプリケーションの重要操作時には本人確認の意味も含めて
    FIDOを利用するとクール

    View Slide

  62. 監査ログの取得方法
    - あまり知られていないけどFirebase Authのログが取れる。
    curl -d "{'monitoring': {'requestLogging':{'enabled':true}}}" \
    -H "Authorization: Bearer $(gcloud auth print-access-token)" -X
    PATCH \
    -H 'Content-Type: application/json' \
    https://identitytoolkit.googleapis.com/admin/v2/projects/[project-
    id]/config?updateMask=monitoring.requestLogging.enabled

    View Slide

  63. 監査ログの取得方法

    View Slide

  64. まとめ
    - FIDO
    - パスワードのリスクを軽減する公開鍵認証
    - Node.jsにいいライブラリなくてつらい( GoかJavaがよさそう)
    - Firebase
    - 便利!
    - だけど、何ができて何ができないのか把握せずに使うと辛いことになるので
    用量用法は守って利用しよう

    View Slide

  65. Enjoy!
    Twitter: @k2wanko
    [email protected]

    View Slide

  66. References
    - https://github.com/k2wanko/fido2-example
    - https://github.com/googlecodelabs/fido2-codelab
    - https://webauthn.io/
    - https://firebase.google.com/docs/reference

    View Slide