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 full-size slide

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

    View full-size slide

  3. パスワード認証

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  11. FIDO2
    WebAuthn

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  14. Androidを使った認証

    View full-size slide

  15. Yubikeyを使った認証

    View full-size slide

  16. FIDO2の実装

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  27. 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 full-size slide

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

    View full-size slide

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

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  38. Registrationの実装
    Platform Cross Platform

    View full-size slide

  39. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  42. Registrationの実装

    View full-size slide

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

    View full-size slide

  44. Registrationの実装 (Android)
    val rawId = response.keyHandle.toBase64()
    val clientDataJSON = response.clientDataJSON.toBase64()
    val credentialId = response.keyHandle.toBase64()
    val attestationObject = response?.attestationObject?.toBase64() ?: return@launch
    val data = hashMapOf(
    "rawId" to rawId,
    "credentialId" to credentialId,
    "clientDataJSON" to clientDataJSON,
    "attestationObject" to attestationObject,
    "apkSigSha256" to getApkSigSha256()
    )
    val res = functions.registerResponse(data)

    View full-size slide

  45. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  48. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  52. 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 full-size slide

  53. 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 full-size slide

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

    View full-size slide

  55. Sign Inの実装

    View full-size slide

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

    View full-size slide

  57. 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 full-size slide

  58. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  61. 監査ログの取得方法
    - あまり知られていないけど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 full-size slide

  62. 監査ログの取得方法

    View full-size slide

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

    View full-size slide

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

    View full-size slide