fix(android): resolve "Biometric crypto object unavailable" on Android 9/10 - #88
Conversation
On Android 9/10 (API 28-29), setUserAuthenticationValidityDurationSeconds(1) creates a time-based key requiring prior authentication. Calling cipher.init() before biometric auth throws UserNotAuthenticatedException (subclass of InvalidKeyException), which wasn't caught, causing createCryptoObject() to return null and triggering "Biometric crypto object unavailable". Fix: use -1 (per-operation auth) so cipher.init() succeeds before auth and BiometricPrompt CryptoObject binding works correctly. Also broaden exception handling to catch InvalidKeyException for migration of existing time-based keys. Co-authored-by: riderx <4084527+riderx@users.noreply.github.com> Agent-Logs-Url: https://github.com/Cap-go/capacitor-native-biometric/sessions/980fc756-e2c7-4548-9159-1c944ce62a09
|
@copilot your build fail |
Prettier requires Java imports to be in alphabetical order within their groups. The import was placed between Android keystore imports instead of in the java.security.* section, causing the CI prettier check to fail. Co-authored-by: riderx <4084527+riderx@users.noreply.github.com> Agent-Logs-Url: https://github.com/Cap-go/capacitor-native-biometric/sessions/23028fed-4ada-45be-bdef-17ca32669e89
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughFixed cryptographic key handling in Android biometric authentication by catching Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
On Android 9/10 (API 28-29),
BiometricPrompt.CryptoObjectcreation silently fails because the AndroidKeyStore key was generated withsetUserAuthenticationValidityDurationSeconds(1)— a time-based policy requiring prior authentication before the key is usable. Callingcipher.init()before authentication throwsUserNotAuthenticatedException(a subclass ofInvalidKeyException), which wasn't caught, causingcreateCryptoObject()to returnnulland surfacing as "Biometric crypto object unavailable".Key changes
setUserAuthenticationValidityDurationSeconds(1)→(-1)in bothgenerateSecretKey()andgetOrCreateCredentialKey(). Value-1means per-operation authentication —cipher.init()succeeds before auth and the biometric event unlocks the cipher, which is the required pattern forCryptoObjectbinding.Broader exception handling: Widened
catch (KeyPermanentlyInvalidatedException)→catch (InvalidKeyException)in allcipher.init()call sites. Covers bothKeyPermanentlyInvalidatedException(enrollment changed) andUserNotAuthenticatedException(migration from time-based keys), with the same recovery: delete the stale key and regenerate.Decrypt migration path:
createCredentialDecryptCryptoObject()now explicitly handlesInvalidKeyExceptionby deleting the incompatible key and its associated encrypted data, unblocking re-enrollment viasetSecureCredentialsrather than permanently failing.Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
dl.google.com/usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED -Xmx1536m -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant -cp /home/REDACTED/.gradle/wrapper/dists/gradle-8.14.3-all/10utluxaxniiv4wxiphsi49nj/gradle-8.14.3/lib/gradle-daemon-main-8.14.3.jar(dns block)If you need me to access, download, or install something from one of these locations, you can either:
Original prompt
📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.
Summary by CodeRabbit
Bug Fixes