fix: recover from late keystore auth token delivery when signing - #93
Conversation
On some OEM builds (observed on Motorola and Xiaomi devices) the keystore auth token from a successful BiometricPrompt authentication is delivered asynchronously and may not have reached Keystore when onAuthenticationSucceeded fires, so sign() fails with KEY_USER_NOT_AUTHENTICATED (-26) despite genuine authentication (https://issuetracker.google.com/issues/129937212). Delay the first sign attempt slightly so late tokens can land while the CryptoObject-bound operation is still alive, retry once on a freshly initialized Signature if the operation was already aborted, and surface a distinct KEY_USER_NOT_AUTHENTICATED error code when both attempts fail. Refs #91
|
Warning Review limit reached
Next review available in: 25 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAndroid signing now delays Keystore operations, detects ChangesAndroid signature authentication retry
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@android/src/main/java/com/sbaiahmed1/reactnativebiometrics/ReactNativeBiometricsSharedImpl.kt`:
- Around line 1290-1302: Update the authentication classification around
authResult in the success path to use authResult.authenticationType rather than
authResult.cryptoObject?.signature for detecting device-credential
authentication. When it equals
BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL, set fallbackUsed
to true and biometricStrength to "device_credential"; otherwise use
authenticatorResult.actualStrength, while preserving the existing signature
selection and signing flow.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b2e0cb07-c6fc-4455-b3ba-80647a05de83
📒 Files selected for processing (2)
README.mdandroid/src/main/java/com/sbaiahmed1/reactnativebiometrics/ReactNativeBiometricsSharedImpl.kt
On API 30+ BiometricPrompt returns the bound CryptoObject even for device-credential authentication, so a null crypto signature is not a reliable indicator of how the user authenticated. Derive fallbackUsed and the reported biometricStrength from authenticationType instead, keeping signature selection based on the CryptoObject.
|
Addressed in 2411d7c — device-credential classification now derives from |
Summary
Fixes the
KEY_USER_NOT_AUTHENTICATED(Keystore -26) failure insignWithOptions/verifyKeySignaturereported in #91, where signing fails after a successful biometric authentication on certain OEM devices (reported on Motorola Edge 50 Neo / Moto G23 / Edge 40 Neo and Xiaomi 14 / 14 Ultra / Redmi Note 14 Pro+, Android 14–16).Root cause
AOSP registers the Keystore auth token before invoking
onAuthenticationSucceeded, but some OEM-customized biometric services deliver the token asynchronously. When the callback fires first, the immediatesign()fails withKEY_USER_NOT_AUTHENTICATEDeven though the user genuinely authenticated (see google/issuetracker#129937212). Worse, a failedsign()aborts theCryptoObject-bound Keystore operation, so the failure was previously unrecoverable.Changes
sign()attempt by 150 ms so a late-delivered auth token can reach Keystore while the CryptoObject-bound operation (and its challenge) is still alive. The delay is hidden by the prompt dismissal animation.KEY_USER_NOT_AUTHENTICATED, retry once on a freshly initializedSignatureafter another 250 ms — this recovers on Keystore implementations that validate by recent authentication rather than strict per-operation challenge.KEY_USER_NOT_AUTHENTICATEDerror code (previously buried in the genericSIGNATURE_CREATION_FAILED) so apps can show a targeted retry prompt; documented in the README error-code table.No changes to key generation or to the CryptoObject binding itself — the security model is unchanged.
Testing
:sbaiahmed1_react-native-biometrics:compileDebugKotlinpasses (new arch codegen included).Existing Jest suite passes (85/85).
Regression-verified end to end on the Android emulator (Pixel, API 35, fingerprint enrolled):
createKeys('ec256')(auth-bound key) →Generate Signature→ BiometricPrompt → fingerprint auth → signature produced, andvalidateSignaturereturnedvalid: trueagainst the public key. Debug logcat shows the fix executing as designed:158 ms apart — the 150 ms token-propagation delay plus the sign itself; the first attempt succeeds and the retry branch stays dormant on a compliant device, i.e. no behavior change on healthy hardware.
The
KEY_USER_NOT_AUTHENTICATEDfailure itself is OEM-device-specific and does not reproduce on emulators; verification on affected hardware (Motorola/Xiaomi) is requested from the reporter inKEY_USER_NOT_AUTHENTICATEDerror when callingsignWithOptions#91.Refs #91