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. FIDOの認証方法 Device Register public key Challenge code Challenge code Signature

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

    Validation signature 公開鍵暗号方式を使って 登録した人と認証する人の検 証をするよ Access token, Session cookie Sign in request
  3. 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..." ] } } ]
  4. Registrationの実装 (Android) - Androidには Fido2ApiClient というAPIがある。 val fidoClient: Fido2ApiClient =

    Fido.getFido2ApiClient(this) fidoClient.getRegisterPendingIntent(publicKeyCredentialCreationOptions) fidoClient.getSignPendingIntent(publicKeyCredentialRequestOptions)
  5. 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 }
  6. Registrationの実装 (Android) override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)

    { when(requestCode) { REQUEST_FIDO2_REGISTER -> { ... handleRegisterResponse(data)
  7. 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)
  8. Registrationの実装 (Functions) export const registerResponse = functions.https.onCall(async (data: RegisterResponseOption |

    null, context: CallableContext) => { … const result = await f2l.attestationResult(clientAttestationResponse, attestationExpectations, { android: { rpId: HOSTNAME } })
  9. Registrationの実装 (Functions) let uid = '' if (auth) { uid

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

    case 'fido-u2f': transports.push('usb') break default: transports.push('internal') }
  11. 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)
  12. 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 }
  13. 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()
  14. Sign Inの実装 (Android) val task = fidoClient.getSignPendingIntent(publicKeyCredentialRequestOptions) val intent =

    task.await() startIntentSenderForResult(intent.intentSender, REQUEST_FIDO2_SIGNIN, null, 0, 0, 0, null)
  15. Sign Inの実装 (Android) override fun onActivityResult(requestCode: Int, resultCode: Int, data:

    Intent?) { when(requestCode) { REQUEST_FIDO2_SIGNIN -> { handleSignInResponse(data)
  16. 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() ))
  17. 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 }
  18. 監査ログの取得方法 - あまり知られていないけど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
  19. まとめ - FIDO - パスワードのリスクを軽減する公開鍵認証 - Node.jsにいいライブラリなくてつらい( GoかJavaがよさそう) - Firebase

    - 便利! - だけど、何ができて何ができないのか把握せずに使うと辛いことになるので 用量用法は守って利用しよう