Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

パスワード認証

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

FIDO

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

FIDO2 WebAuthn

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Androidを使った認証

Slide 16

Slide 16 text

Yubikeyを使った認証

Slide 17

Slide 17 text

FIDO2の実装

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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..." ] } } ]

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

Registrationの実装 Platform Cross Platform

Slide 40

Slide 40 text

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 }

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

Registrationの実装

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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)

Slide 46

Slide 46 text

Registrationの実装 (Functions) export const registerResponse = functions.https.onCall(async (data: RegisterResponseOption | null, context: CallableContext) => { … const result = await f2l.attestationResult(clientAttestationResponse, attestationExpectations, { android: { rpId: HOSTNAME } })

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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)

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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 }

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

Sign Inの実装

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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 }

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

監査ログの取得方法

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

Enjoy! Twitter: @k2wanko [email protected]

Slide 66

Slide 66 text

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