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

1b177b37d966f573b1df5d7218a5f560?s=47 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/

1b177b37d966f573b1df5d7218a5f560?s=128

k2wanko

April 28, 2020
Tweet

Transcript

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

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

    Organizer
  3. パスワード認証

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

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

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

  7. FIDO

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

  9. FIDOの認証方法 Device Register public key Challenge code Challenge code Signature

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

    Validation signature 公開鍵暗号方式を使って 登録した人と認証する人の検 証をするよ Access token, Session cookie Sign in request
  11. FIDO認証のメリット 1. ユーザーはパスワードを覚える必要がない 2. 物理的なデバイスを盗まれなければなりすましのリスクが低い

  12. FIDO2 WebAuthn

  13. WebAuthn - ブラウザで公開鍵認証を行うための仕様 - Chrome、Firefox、Safariなど主要なブラウザで実装されている。 - FIDO2と呼ばれるものもWebAuthnと同様のもの - FIDO2 API

    for AndroidはWeb Authnクライアント https://developers.google.com/identity/fido/android/native-apps
  14. サンプルアプリのDEMO https://github.com/k2wanko/fido2-example

  15. Androidを使った認証

  16. Yubikeyを使った認証

  17. FIDO2の実装

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

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

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

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

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

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

    スケールして低コストで使えるデータベース - ChallengeやCredentialの保存に利用
  24. 実装の参考にさせてもらったもの - Googleが提供しているCodelab - https://github.com/googlecodelabs/fido2-codelab

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

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

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

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

  30. assetlinks.json AndroidManifest.xml <meta-data android:name="asset_statements" android:resource="@string/asset_statements" /> strings.xml <string name="asset_statements" >

    [ { \"include\": \"https://[project-id].web.app/.well-known/assetlinks.json\" }, ] </string>
  31. Registrationの実装 Cloud FunctionsでCredentialの登録 export const registerRequest = functions.https.onCall(...) export const

    registerResponse = functions.https.onCall(...)
  32. Callable Functionについて - Firebase SDK経由でCloud Functionsを呼び出すシンタックスシュガー - Firebase Authで認証していたら自動でトークンを付与して、 検証までしといてくれる便利なやつ

    Androidから呼び出す例 FirebaseFunctions.getHttpsCallable("registerRequest").call(data)
  33. Instance IDについて - 広告やPush用に使われているアプリを識別するためのトークン - 認証なしでとりあえず付与されるので今回はデバイス識別に利用 - GDPR対応させるなら、このIDを使う前にユーザーの同意を取る必要がある - 再インストールすると変わる

    Callable Function ではInstance ID Tokenという形で取得ができ:の前半が Instance ID fosrj-JUrl4:APA91bEvf7OixMTK...
  34. Instance IDの検証 curl -H "Authorization: key=[API_KEY]" "https://iid.googleapis.com/iid/info/fosrj-JUrl4:APA91bEvf7OixM... NDh?details=true

  35. Registrationの実装 (Android) - Androidには Fido2ApiClient というAPIがある。 val fidoClient: Fido2ApiClient =

    Fido.getFido2ApiClient(this) fidoClient.getRegisterPendingIntent(publicKeyCredentialCreationOptions) fidoClient.getSignPendingIntent(publicKeyCredentialRequestOptions)
  36. val task = fidoClient.getRegisterPendingIntent(publicKeyCredentialCreationOptions) val intent = task.await() startIntentSenderForResult(intent.intentSender, REQUEST_FIDO2_REGISTER,

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

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

    = Attachment.CROSS_PLATFORM
  39. Registrationの実装 Platform Cross Platform

  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 }
  41. Registrationの実装 (Android) setChallenge(res.challenge.decodeBase64())

  42. val task = fidoClient.getRegisterPendingIntent(publicKeyCredentialCreationOptions) val intent = task.await() startIntentSenderForResult(intent.intentSender, REQUEST_FIDO2_REGISTER,

    ull, 0, 0, 0, null) Registrationの実装 (Android)
  43. Registrationの実装

  44. Registrationの実装 (Android) override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)

    { when(requestCode) { REQUEST_FIDO2_REGISTER -> { ... handleRegisterResponse(data)
  45. 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)
  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 } })
  47. Registrationの実装 (Functions) let uid = '' if (auth) { uid

    = auth.uid } else { const user = await firebase.auth().createUser({}) uid = user.uid }
  48. Registrationの実装 (Functions) const transports: string[] = [] switch (result.authnrData.get('fmt')) {

    case 'fido-u2f': transports.push('usb') break default: transports.push('internal') }
  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)
  50. Registrationの実装 (Functions) const token = await firebase.auth().createCustomToken(uid, { webauthn: true

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

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

    (要確認)
  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 }
  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()
  55. Sign Inの実装 (Android) val task = fidoClient.getSignPendingIntent(publicKeyCredentialRequestOptions) val intent =

    task.await() startIntentSenderForResult(intent.intentSender, REQUEST_FIDO2_SIGNIN, null, 0, 0, 0, null)
  56. Sign Inの実装

  57. Sign Inの実装 (Android) override fun onActivityResult(requestCode: Int, resultCode: Int, data:

    Intent?) { when(requestCode) { REQUEST_FIDO2_SIGNIN -> { handleSignInResponse(data)
  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() ))
  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 }
  60. Sign Inの実装 (Android) auth.signInWithCustomToken(res!!.token).await()

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

  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
  63. 監査ログの取得方法

  64. まとめ - FIDO - パスワードのリスクを軽減する公開鍵認証 - Node.jsにいいライブラリなくてつらい( GoかJavaがよさそう) - Firebase

    - 便利! - だけど、何ができて何ができないのか把握せずに使うと辛いことになるので 用量用法は守って利用しよう
  65. Enjoy! Twitter: @k2wanko k2.wanko@gmail.com

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