Skip to content

Use Android Keystore for device auth storage - #856

Open
0xh3rman wants to merge 4 commits into
mainfrom
hotfix/android-device-auth-keystore
Open

Use Android Keystore for device auth storage#856
0xh3rman wants to merge 4 commits into
mainfrom
hotfix/android-device-auth-keystore

Conversation

@0xh3rman

@0xh3rman 0xh3rman commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Store new device-auth keys with direct Android Keystore AEAD and a dedicated alias.
  • Lazily migrate readable values from the legacy Tink keyset-backed store.
  • Reset only the regenerable device-auth values when the direct Keystore key is missing or cannot authenticate the ciphertext.
  • Keep the password and Gem preference stores unchanged.

Root cause

The exact originating Keystore condition is unknown. The crash happens when AndroidKeysetManager cannot authenticate its SharedPreferences-wrapped Tink keyset with the Android Keystore master key, which raises BadPaddingException or AEADBadTagException.

GetDeviceIdImpl catches the failed read and generates a new device key pair, but the old implementation then writes through the same unreadable Tink keyset and crashes again. New device-auth writes now use Android Keystore AEAD directly, so regeneration no longer depends on that legacy keyset. Existing readable values migrate on first read.

Impact

Fixes the two observed device-auth crash paths. Invalid direct-Keystore data is cleared so the device identity can be regenerated; unrelated Android Keystore failures may still surface.

Validation

  • ./gradlew :app:assembleGoogleDebug
  • ./gradlew :app:lintGoogleDebug
  • 8 TinkEncryptedKeyValueStoreInstrumentedTest tests on API 35
  • git diff --check

1. Encrypt device auth values with a direct Android Keystore AEAD, migrating legacy Tink and DataStore values on read
2. Recover from keystore failures: drop corrupt values, retry transient faults once, reset only this namespace's keystore values
3. Regenerate device keys only when no stored value exists and return synthetic 599 when request signing fails
4. Add missing getPriceAlertAssetIds override in price alerts test fake
@0xh3rman
0xh3rman force-pushed the hotfix/android-device-auth-keystore branch from bdaba61 to 1a2a9c5 Compare August 8, 2026 14:16
@0xh3rman
0xh3rman marked this pull request as ready for review August 11, 2026 00:38
@0xh3rman
0xh3rman requested a review from DRadmir August 11, 2026 00:38
@0xh3rman 0xh3rman self-assigned this Aug 11, 2026
@0xh3rman

Copy link
Copy Markdown
Collaborator Author

Verified the migration and self-heal end-to-end on an emulator upgrading from 2.114.6 (old-format values), which surfaced a bug — fixed in 64e643a:

Bug: after resetOnInvalidKey rotates the device keypair, the device could never re-register. getOrCreateDevice short-circuited on the stale local DeviceRegistered flag and called getDevice(), which throws GemApiException: Device not found for the rotated id (GemApiErrorInterceptor converts the error body) instead of returning null — so the registerDevice fallback was unreachable. The WebSocket looped Device not found across app restarts, permanently. Fix: consult backend isDeviceRegistered() instead of the local flag.

Verification (Pixel 10 Pro AVD, API 37):

  • Migration: both gem_device_keys values rewritten in place to android-keystore-v1: format, no error logs, device id unchanged, second launch idempotent (byte-identical ciphertexts)
  • Self-heal: corrupted both stored values → no crash, corrupt entries dropped, keypair regenerated; with the fix the rotated device re-registers (pushed device id updated, subscriptions reconciled, zero WS errors)

Note: :data:repositories unit tests currently don't compile on this branch (AssetsRepositoryTest.kt out of sync with AssetsRepository, 14 errors, pre-existing).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants