バックエンドはWebaAthn4jを使用しています。
Androidのパスワード系は軒並みDeprecateになっていて、今は androidx.credentials
一択になっているようです。
androidx.credentials:credentials-play-services-auth
androidx.credentials:credentials
/.well-known/assetlinks.json
をサーバーに置きます。DeepLink等でおなじみですが、delegate_permission/common.get_login_creds
を追加する必要があります。尚、delegate_permission/common.handle_all_urls
の方も無いとエラーになります。
Googleのマネージャーだと androidx.credentials.exceptions.publickeycredential.GetPublicKeyCredentialDomException: The incoming request cannot be validated
。
Bitwadenだとユーザーがキャンセルした扱いになります。
[ { "relation": [ "delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds" ], "target": { "namespace": "android_app", "package_name": "${packageName}", "sha256_cert_fingerprints": [ "${appFingerprint}" ] } } ]
WebAuthn4J側の設定では検証時にandroid:apk-key-hash:
の設定が必要になります。
実際にエラーを起こしてhashを見るのが手っ取り早いですが、以下でも取得できます。
keytool -exportcert -keystore <keystore> -alias <alias> -storepass <storepass> -keypass <keypass> | openssl sha256 -binary | openssl base64 | tr -- '+/' '-_' | tr -d '='
ServerProperty( setOf( Origin("https://example.com"), Origin("android:apk-key-hash:${apkKeyHash}"), ), "example.com", { challenge.toByteArray() }, null, )
Android側でリクエストを組み立てる時に、challengeはBase64エンコードしないと検証時にinvalidになりました。
val getPublicKeyCredentialOption = GetPublicKeyCredentialOption( """ { "challenge": "${Base64.getEncoder().encodeToString(challenge.toByteArray())}", "rpId": "$domain", "userVerification": "required", "timeout": 1800000 } """.trimIndent(), null, )
Androidで取得できたresponseのauthenticatorData
、clientDataJSON
、signature
、userHandle
は全てUrlBase64な為、通常のBase64に直す必要がありました。
private fun String?.base64UrlToBase64(): String? { this ?: return null return Base64.getEncoder() .encode( Base64.getUrlDecoder() .decode(this), ) .decodeToString() }
おわり
情報があまり無くて大変でしたが、なんとか実装できました。