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

Deep Dive to "com.google.android.gms.fido.fido2"

Kengo Suzuki
February 07, 2019

Deep Dive to "com.google.android.gms.fido.fido2"

# English Description (Japanese below)
We are exhausted with passwords. Users are exhausted with passwords because of too many web-services. Operators are exhausted with passwords because of defense against password-related attacks like Phishing. We, as developers, are also exhausted because of poor UX in passwords.

In last DroidKaigi, I have presented “AuthN and AuthZ with Android”(認証と認可と君と) for the purpose of introducing FIDO UAF 1.1, the password-less authentication framework. Since the presentation, a lot of innovation has been made. For example, major browsers adopted, implemented, and released WebAuthN which will play important role in FIDO 2.0, which will be the new version of UAF 1.1.

Android hasn’t been left behind by the advancement in Authentication. In March 2018, FIDO2.0 package was released in Android API, and that is exactly what I am going to talk about. At the end of this session, the audience will understand what `com.google.android.gms.fido.fido2*` will do, how to use it with `BiometricPromptAPI`, and how it is related to WebAuthN.

## Outline
1. Reviewing FIDO UAF 1.1
2. Safetynet Attestation vs Key Attestation
3. com.google.android.gms.fido.fido2
4. fido2 with BiometricPrompt
5. fido2 with WebAuthN

# 日本語Description
去年のDroidKaigi2018「認証と認可と君と」ではそういった苦労から我々を開放するパスワードレス認証「FIDO UAF 1.1」とAndroid上での実装方法についてお話しました。それから早1年、次バージョンにあたるFIDO 2.0の一翼を担うWebAuthNが主ブラウザに実装され、更にChromeの最新Ver70ではAndroidの指紋認証との連携ができるようになりました。勿論、Android自体にも大きな動きがあり、2018年にはAndroidのAPIに新しくFIDO2.0用パッケージが登場しました。本セッションでは、com.google.android.gms.fido.fido2* についてBiometricPromptAPIなどのAndroid内のFIDO2関連のAPIと絡めつつ、実装方法をお話します。

## Outline
1. Reviewing FIDO UAF 1.1
2. Safetynet Attestation vs Key Attestation
3. com.google.android.gms.fido.fido2
4. fido2 with BiometricPrompt
5. fido2 with WebAuthN

Intended Audience

- anyone interested in Authentication
- anyone interested in FIDO
- whoever wants to understand the difference between FIDO in Android and WebAuthN

Kengo Suzuki

February 07, 2019
Tweet

More Decks by Kengo Suzuki

Other Decks in Technology

Transcript

  1. 1. Motivation 2. Overview of FIDO 3. Implementing FIDO2 in

    Android 4. Attestation in Android 5. Assertion in Android Outline
  2. 1. Motivation 2. Overview of FIDO 3. Implementing FIDO2 in

    Android 4. Attestation in Android 5. Assertion in Android Outline
  3. 8FC"VUIO CFDPNFT XDQSPQPTFE SFDDFPNFOEBUJPO HNTpEPpEP 'FC 'FC +BO 8FC"VUIO CFDPNFT

    XDQSPQPTFE SFDDFPNFOEBUJPO .BSDI .BZ 8FC"VUIO XDDBOEJEBUF SFDDFPNFOEBUJPO 8FC"VUIO XDQSPQPTFE SFDDFPNFOEBUJPO +VOF
  4. 'FC 'FC +BO 8FC"VUIO CFDPNFT XDQSPQPTFE SFDDFPNFOEBUJPO 8FC"VUIO CFDPNFT XDQSPQPTFE

    SFDDFPNFOEBUJPO .BSDI .BZ 8FC"VUIO XDDBOEJEBUF SFDDFPNFOEBUJPO 8FC"VUIO XDQSPQPTFE SFDDFPNFOEBUJPO HNTpEPpEP +VOF HNTpEPpEP
  5. v 2

  6. 1. Motivation 2. Overview of FIDO 3. Implementing FIDO2 in

    Android 4. Attestation in Android 5. Assertion in Android Outline
  7. - Scalable - Inter-Operable - Kill Phishing - Protect User

    Privacy - Elimination Password as Credential What FIDO is trying to achieve?
  8. - Break down AuthN responsibility - bounding credentials to origin

    - customize challenge-response to make 2FA by default How FIDO2 addresses problems?
  9. "VUIFOUJDBUPS $MJFOU 3FMZJOH 1BSUZ 3FMZJOH 1BSUZ $SFEFOUJBM,FZ ,FZ1BJS  ,FZ1BJS

    $SFEFOUJBM,FZ ,FZ1BJS  5IJTLFZJTCPVOEUP0SJHJO *UBDUVBMMIBTBOJE
  10. - “attestation is a statement serving to bear witness, confirm,

    or authenticate” - “attest to the provenance of an authenticator and the data it emits; including, for example: credential IDs, credential key pairs, signature counters, etc. An attestation statement is conveyed in an attestation object during registration What is Attestation? https://www.w3.org/TR/webauthn
  11. val certs = keyStore.getCertificateChain(keyStoreAlias) val x509Certs: Array<ByteArray?> = arrayOfNulls(certs.size) certs.forEachIndexed

    { index, certificate -> x509Certs[index] = certificate.encoded } // key attestation -> x509Certs[0]
  12. Certificate: Data: Version: 3 (0x2) Serial Number: 1 (0x1) Signature

    Algorithm: ecdsa-with-SHA256 Issuer: serialNumber=0fccf0d5489ba04c/title=TEE Validity Not Before: Jan 1 00:00:00 1970 GMT Not After : Feb 7 06:28:15 2106 GMT Subject: CN=Android Keystore Key X509v3 extensions: X509v3 Key Usage: critical Digital Signature 1.3.6.1.4.1.11129.2.1.17:
  13. Certificate: Data: Version: 3 (0x2) Serial Number: 1 (0x1) Signature

    Algorithm: ecdsa-with-SHA256 Issuer: serialNumber=0fccf0d5489ba04c/title=TEE Validity Not Before: Jan 1 00:00:00 1970 GMT Not After : Feb 7 06:28:15 2106 GMT Subject: CN=Android Keystore Key X509v3 extensions: X509v3 Key Usage: critical Digital Signature 1.3.6.1.4.1.11129.2.1.17:
  14. - Check device integrity by collecting from HW/SW and testing

    against Android Compatibility Test Suite (CTS) - Send result in JWS - contains pub key SafetyNet Attestation API
  15. type AndroidSafetyNetAttestationResponse struct { Nonce string `json:"nonce"` TimestampMs int64 `json:"timestampMs"`

    ApkPackageName string `json:"apkPackageName"` ApkCertificateDigestSha256 []string `json:"apkCertificateDigestSha256"` ApkDigestSha256 string `json:"apkDigestSha256"` CtsProfileMatch bool `json:”ctsProfileMatch”` BasicIntegrity bool `json:"basicIntegrity"` Error string `json:"error"` }
  16. - It only checks Device Integrity - So not really

    Attesting it - Requires unnecessary(?) access to Google Backend - Logic verifying device profile completely up to Google - Device Profile requires out of scope contects - FIDO cares about privacy - FIDO is authN protocol, so if it does not need it, then it is desire to not collect unnecessary data - 10,000 requests per day for each API key Drawback in SafetyNet Attestation API
  17. 1. Motivation 2. Overview of FIDO 3. Implementing FIDO2 in

    Android 4. Attestation in Android 5. Assertion in Android Outline
  18. - .fido - .common - .u2f (out of scope) -

    .fido2 - .api.common. - .ui (internal) - .AuthenticateActivity - .Fido2FullScreenActivity - .PolluxActivity - .GenericActivity - .pollux (internal) - .GcmReceiverService - .CableAuthenticatorService com.google.android.gms packages "QQ "DUJWJUZ pEP  "DUJWJ UZ 3FTVMU *OUFSOBM VJ QPMMV Y
  19. data class ServerPublicKeyCredentialCreationOptionsRequest( val username: String, val displayName: String, val

    authenticatorSelection: AuthenticatorSelectionCriter val attestation: AttestationConveyancePreference) EJDUJPOBSZ4FSWFS1VCMJD,FZ$SFEFOUJBM$SFBUJPO0QUJPOT3FRVFTU\ SFRVJSFE%0.4USJOHVTFSOBNF SFRVJSFE%0.4USJOHEJTQMBZ/BNF "VUIFOUJDBUPS4FMFDUJPO$SJUFSJBBVUIFOUJDBUPS4FMFDUJPO "UUFTUBUJPO$POWFZBODF1SFGFSFODFBUUFTUBUJPOOPOF ^ https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-server-v2.0- rd-20180702.html#serverpublickeycredentialcreationoptionsrequest
  20. val criteria = AuthenticatorSelectionCriteria.Builder() .setAttachment(Attachment.PLATFORM) //or .CROSS-PLATFORM //.setResidentKey(true) // No

    method exists //.setUserVerification() // No method exists .build() EJDUJPOBSZ"VUIFOUJDBUPS4FMFDUJPO$SJUFSJB\ "VUIFOUJDBUPS"UUBDINFOUBVUIFOUJDBUPS"UUBDINFOU CPPMFBOSFRVJSF3FTJEFOU,FZGBMTF 6TFS7FSJpDBUJPO3FRVJSFNFOUVTFS7FSJpDBUJPOQSFGFSSFE ^ https://w3c.github.io/webauthn/#authenticatorSelection
  21. val options = PublicKeyCredentialCreationOptions.Builder() .setRp(rp) .setUser(user) .setChallenge("challenge".toByteArray()) .setParameters(pubKeyParams) .setTimeoutSeconds(3600.toDouble()) .setExcludeList(exclusionList)

    .setAuthenticatorSelection(criteria) .setAttestationConveyancePreference(AttestationConveyancePreference.DIR .build() val client = Fido.getFido2ApiClient(this.applicationContext) val clientTask = client.getRegisterIntent(options) clientTask.addOnSuccessListener { it.launchPendingIntent(this, REGISTER_RESULT_CODE) }
  22. val options = PublicKeyCredentialCreationOptions.Builder() .setRp(rp) // required .setUser(user) // required

    .setChallenge("challenge".toByteArray()) // required .setParameters(pubKeyParams) // required .setTimeoutSeconds(3600.toDouble()) .setExcludeList(exclusionList) .setAuthenticatorSelection(criteria) .setAttestationConveyancePreference(AttestationConveyancePreference.DIR .build() val client = Fido.getFido2ApiClient(this.applicationContext) val clientTask = client.getRegisterIntent(options) clientTask.addOnSuccessListener { it.launchPendingIntent(this, REGISTER_RESULT_CODE) }
  23. val options = PublicKeyCredentialCreationOptions.Builder() .setRp(rp) .setUser(user) .setChallenge("challenge".toByteArray()) .setParameters(pubKeyParams) .setTimeoutSeconds(3600.toDouble()) .setExcludeList(exclusionList)

    .setAuthenticatorSelection(criteria) .setAttestationConveyancePreference(AttestationConveyancePreference.DIR .build() val client = Fido.getFido2ApiClient(this.applicationContext) val clientTask = client.getRegisterIntent(options) clientTask.addOnSuccessListener { it.launchPendingIntent(this, REGISTER_RESULT_CODE) }
  24. val rp = PublicKeyCredentialRpEntity( "login.example.com", "Example Company", “https://ex.com/icon.svg“) EJDUJPOBSZ1VCMJD,FZ$SFEFOUJBM3Q&OUJUZ1VCMJD,FZ$SFEFOUJBM&OUJUZ\ %0.4USJOHJE

    ^ EJDUJPOBSZ1VCMJD,FZ$SFEFOUJBM&OUJUZ\ SFRVJSFE%0.4USJOHOBNF 6474USJOHJDPO ^ https://w3c.github.io/webauthn/#sctn-rp-credential-params
  25. val options = PublicKeyCredentialCreationOptions.Builder() .setRp(rp) .setUser(user) .setChallenge(“challenge".toByteArray()) .setTimeoutSeconds(3600.toDouble()) .setParameters(pubKeyParams) .setExcludeList(exclusionList)

    .setAuthenticatorSelection(criteria) .setAttestationConveyancePreference(AttestationConveyancePreference.DIR .build() val client = Fido.getFido2ApiClient(this.applicationContext) val clientTask = client.getRegisterIntent(options) clientTask.addOnSuccessListener { it.launchPendingIntent(this, REGISTER_RESULT_CODE) }
  26. val user = PublicKeyCredentialUserEntity( "user-id-like-uuid".toByteArray(), "ken5scal", "https://gravata.com/ken5scal", "Kengo Suzuki") EJDUJPOBSZ1VCMJD,FZ$SFEFOUJBM6TFS&OUJUZ1VCMJD,FZ$SFEFOUJBM&OUJUZ\

    SFRVJSFE#V⒎FS4PVSDFJE SFRVJSFE%0.4USJOHEJTQMBZ/BNF ^ EJDUJPOBSZ1VCMJD,FZ$SFEFOUJBM&OUJUZ\ SFRVJSFE%0.4USJOHOBNF 6474USJOHJDPO ^ https://w3c.github.io/webauthn/#sctn-user-credential-params
  27. val options = PublicKeyCredentialCreationOptions.Builder() .setRp(rp) .setUser(user) .setChallenge(“challenge".toByteArray()) .setTimeoutSeconds(3600.toDouble()) .setParameters(pubKeyParams) .setExcludeList(exclusionList)

    .setAuthenticatorSelection(criteria) .setAttestationConveyancePreference(AttestationConveyancePreference .DIRECT) .build() val client = Fido.getFido2ApiClient(this.applicationContext) val clientTask = client.getRegisterIntent(options) clientTask.addOnSuccessListener { it.launchPendingIntent(this, REGISTER_RESULT_CODE) }
  28. val options = PublicKeyCredentialCreationOptions.Builder() .setRp(rp) .setUser(user) .setChallenge(“challenge".toByteArray()) .setTimeoutSeconds(3600.toDouble()) .setParameters(pubKeyParams) .setExcludeList(exclusionList)

    .setAuthenticatorSelection(criteria) .setAttestationConveyancePreference(AttestationConveyancePreference .DIRECT) .build() val client = Fido.getFido2ApiClient(this.applicationContext) val clientTask = client.getRegisterIntent(options) clientTask.addOnSuccessListener { it.launchPendingIntent(this, REGISTER_RESULT_CODE) }
  29. val pubKeyParam = PublicKeyCredentialParameters( // Essentially, only “public-key” can be

    chosen PublicKeyCredentialType.PUBLIC_KEY.toString(), //“public-key EC2Algorithm.ES512.algoValue ɹɹɹ// Recommended // EC2Algorithm.ES256.algoValue // Recommended // RSAAlgorithm.PS*.algoValue // RSAAlgorithm.RS*.algoValue ) EJDUJPOBSZ1VCMJD,FZ$SFEFOUJBM1BSBNFUFST\ SFRVJSFE1VCMJD,FZ$SFEFOUJBM5ZQFUZQF SFRVJSFE$04&"MHPSJUIN*EFOUJpFSBMH ^ https://w3c.github.io/webauthn/#credential-params
  30. val options = PublicKeyCredentialCreationOptions.Builder() .setRp(rp) .setUser(user) .setChallenge(“challenge".toByteArray()) .setTimeoutSeconds(3600.toDouble()) .setParameters(pubKeyParams) .setExcludeList(exclusionList)

    .setAuthenticatorSelection(criteria) .setAttestationConveyancePreference(AttestationConveyancePreference .DIRECT) .build() val client = Fido.getFido2ApiClient(this.applicationContext) val clientTask = client.getRegisterIntent(options) clientTask.addOnSuccessListener { it.launchPendingIntent(this, REGISTER_RESULT_CODE) }
  31. val excludedCredOne = PublicKeyCredentialDescriptor( PublicKeyCredentialType.PUBLIC_KEY.toString(),//“public-key” "base64url-credential-id".toByteArray()) val exclusionList = listOf(excludedCredOne)

    //, excludedCredTwo EJDUJPOBSZ4FSWFS1VCMJD,FZ$SFEFOUJBM%FTDSJQUPS\ SFRVJSFE1VCMJD,FZ$SFEFOUJBM5ZQFUZQF SFRVJSFE%0.4USJOHJE TFRVFODF"VUIFOUJDBUPS5SBOTQPSUUSBOTQPSUT ^ https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-server-v2.0-rd-20180702.html#serverpublickeycredentialdescriptor
  32. val options = PublicKeyCredentialCreationOptions.Builder() .setRp(rp) .setUser(user) .setChallenge(“challenge".toByteArray()) .setTimeoutSeconds(3600.toDouble()) .setParameters(pubKeyParams) .setExcludeList(exclusionList)

    .setAuthenticatorSelection(criteria) .setAttestationConveyancePreference(AttestationConveyancePreference .DIRECT) .build() val client = Fido.getFido2ApiClient(this.applicationContext) val clientTask = client.getRegisterIntent(options) clientTask.addOnSuccessListener { it.launchPendingIntent(this, REGISTER_RESULT_CODE) }
  33. val criteria = AuthenticatorSelectionCriteria.Builder() .setAttachment(Attachment.PLATFORM) //.setResidentKey(true) // No method exists

    //.setUserVerification() // No method exists .build() EJDUJPOBSZ"VUIFOUJDBUPS4FMFDUJPO$SJUFSJB\ "VUIFOUJDBUPS"UUBDINFOUBVUIFOUJDBUPS"UUBDINFOU CPPMFBOSFRVJSF3FTJEFOU,FZGBMTF 6TFS7FSJpDBUJPO3FRVJSFNFOUVTFS7FSJpDBUJPOQSFGFSSFE ^ https://w3c.github.io/webauthn/#authenticatorSelection
  34. val options = PublicKeyCredentialCreationOptions.Builder() .setRp(rp) .setUser(user) .setChallenge(“challenge".toByteArray()) .setTimeoutSeconds(3600.toDouble()) .setParameters(pubKeyParams) .setExcludeList(exclusionList)

    .setAuthenticatorSelection(criteria) .setAttestationConveyancePreference( AttestationConveyancePreference.DIRECT) .build() val client = Fido.getFido2ApiClient(this.applicationContext) val clientTask = client.getRegisterIntent(options) clientTask.addOnSuccessListener { it.launchPendingIntent(this, REGISTER_RESULT_CODE) }
  35. "QQ "DUJWJUZ pEP "DUJWJUZ 3FTVMU *OUFSOBM VJ QPMMVY - .fido

    - .common - .u2f (out of scope) - .fido2 - .api.common. - .ui (internal) - .AuthenticateActivity - .Fido2FullScreenActivity - .PolluxActivity - .GenericActivity - .pollux (internal) - .GcmReceiverService - .CableAuthenticatorService
  36. val options = PublicKeyCredentialCreationOptions.Builder() .setRp(rp) .setUser(user) .setChallenge("challenge".toByteArray()) .setParameters(pubKeyParams) .setTimeoutSeconds(3600.toDouble()) .setExcludeList(exclusionList)

    .setAuthenticatorSelection(criteria) .setAttestationConveyancePreference(AttestationConveyancePreference. DIRECT) .build() val client = Fido.getFido2ApiClient(this.Context) val clientTask = client.getRegisterIntent(options) clientTask.addOnSuccessListener { it.launchPendingIntent(this, REGISTER_RESULT_CODE)} }
  37. val clientDataJSON = Base64.getUrlEncoder().encodeToString(attestedData?.clientDataJSON) val ao = Cbor.plain.dumps(AttestationObject(attestedData?.attestationObject)) val attstObject

    = Base64.getUrlEncoder.encode(ao.toByteArray()) EJDUJPOBSZ4FSWFS"VUIFOUJDBUPS"UUFTUBUJPO3FTQPOTF 4FSWFS"VUIFOUJDBUPS3FTQPOTF\ SFRVJSFE%0.4USJOHDMJFOU%BUB+40/ SFRVJSFE%0.4USJOHBUUFTUBUJPO0CKFDU ^ https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-server-v2.0-rd-20180702.html#serverauthenticatorattestationresponse
  38. EJDUJPOBSZ$PMMFDUFE$MJFOU%BUB\ SFRVJSFE%0.4USJOHUZQF SFRVJSFE%0.4USJOHDIBMMFOHF SFRVJSFE%0.4USJOHPSJHJO 5PLFO#JOEJOHUPLFO#JOEJOH ^ https://w3c.github.io/webauthn/#sec-client-data val clientDataJSON =

    Base64.getUrlEncoder().encodeToString(attestedData?.clientDataJSON) val ao = Cbor.plain.dumps(AttestationObject(attestedData?.attestationObject)) val attstObject = Base64.getUrlEncoder.encode(ao.toByteArray())
  39. val getOptions = PublicKeyCredentialRequestOptions.Builder() .setChallenge(“challenge".toByteArray()) //required .setTimeoutSeconds(3600.toDouble()) .setRpId("login.example.com") .setAllowList(allowList) .setAuthenticationExtensions(extensions)

    .build() val client = Fido.getFido2ApiClient(this.applicationContext) val clientTask = client.getRegisterIntent(options) clientTask.addOnSuccessListener { if (it.hasPendingIntent()) { it.launchPendingIntent(this, AUTHN_RESULT_CODE)} }
  40. val getOptions = PublicKeyCredentialRequestOptions.Builder() .setChallenge(“challenge".toByteArray()) .setTimeoutSeconds(3600.toDouble()) .setRpId("login.example.com") .setAllowList(allowList) .setAuthenticationExtensions(extensions) .build()

    val client = Fido.getFido2ApiClient(this.applicationContext) val clientTask = client.getRegisterIntent(options) clientTask.addOnSuccessListener { if (it.hasPendingIntent()) { it.launchPendingIntent(this, AUTHN_RESULT_CODE)} }
  41. val getOptions = PublicKeyCredentialRequestOptions.Builder() .setChallenge("challenge".toByteArray()) .setTimeoutSeconds(3600.toDouble()) .setRpId("login.example.com") .setAllowList(allowList) .setAuthenticationExtensions(extensions) .build()

    val client = Fido.getFido2ApiClient(this.applicationCont val clientTask = client.getRegisterIntent(options) clientTask.addOnSuccessListener { if (it.hasPendingIntent()) { it.launchPendingIntent(this, AUTHN_RESULT_CODE)} }
  42. val getOptions = PublicKeyCredentialRequestOptions.Builder() .setChallenge("challenge".toByteArray()) .setTimeoutSeconds(3600.toDouble()) .setRpId("login.example.com") .setAllowList(allowList) .setAuthenticationExtensions(extensions) .build()

    val client = Fido.getFido2ApiClient(this.applicationContext) val clientTask = client.getRegisterIntent(options) clientTask.addOnSuccessListener { if (it.hasPendingIntent()) { it.launchPendingIntent(this, AUTHN_RESULT_CODE)} }
  43. val allowedCred = PublicKeyCredentialDescriptor( PublicKeyCredentialType.PUBLIC_KEY.toString(), "base64url-credential-id".toByteArray()) val allowList = listOf(allowedCred)

    EJDUJPOBSZ4FSWFS1VCMJD,FZ$SFEFOUJBM%FTDSJQUPS\ SFRVJSFE1VCMJD,FZ$SFEFOUJBM5ZQFUZQF SFRVJSFE%0.4USJOHJE TFRVFODF"VUIFOUJDBUPS5SBOTQPSUUSBOTQPSUT ^ https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-server-v2.0-rd-20180702.html#serverpublickeycredentialdescriptor
  44. val getOptions = PublicKeyCredentialRequestOptions.Builder() .setChallenge("challenge".toByteArray()) .setTimeoutSeconds(3600.toDouble()) .setRpId("login.example.com") .setAllowList(allowList) .setAuthenticationExtensions(extensions) .build()

    val client = Fido.getFido2ApiClient(this.applicationContext) val clientTask = client.getRegisterIntent(options) clientTask.addOnSuccessListener { if (it.hasPendingIntent()) { it.launchPendingIntent(this, AUTHN_RESULT_CODE)} }
  45. val getOptions = PublicKeyCredentialRequestOptions.Builder() .setChallenge("challenge".toByteArray()) .setTimeoutSeconds(3600.toDouble()) .setRpId("login.example.com") .setAllowList(allowList) .setAuthenticationExtensions(extensions) .build()

    val client = Fido.getFido2ApiClient(this.applicationContext) val clientTask = client.getRegisterIntent(options) clientTask.addOnSuccessListener { if (it.hasPendingIntent()) { it.launchPendingIntent(this, AUTHN_RESULT_CODE)} }
  46. 1. Motivation 2. Overview of FIDO 3. Implementing FIDO2 in

    Android 4. Attestation in Android 5. Assertion in Android What we have covered
  47. - Avoid reinventing the wheel - Instead focus on each

    params and patters - Be prepared to communicate with other departments - For example, you should cooperate with CS in case of device lose Things we should care
  48. - FIDO2 may solve current pain in Password-centric Authentication -

    Android has nice and thorough package - Be prepared to inconvenient environment without password Conclusion