refactor(security): de-duplicate AES-GCM Keystore plumbing and unify the authenticator policy #102

Closed
opened 2026-07-02 02:56:00 +00:00 by JMR-dev · 0 comments
JMR-dev commented 2026-07-02 02:56:00 +00:00 (Migrated from github.com)

Origin: code review of PR #45 (screen-lock app gate). Reuse/maintainability cleanup.

Problem

DatabaseKeyCipher copy-pastes ~60 of its ~142 lines from the pre-existing KeystoreCrypto: doEncrypt/decrypt bodies, existingKey/getOrCreateKey (a split of KeystoreCrypto.secretKey), the keyLock idiom, and 5 identical constants (ANDROID_KEYSTORE, TRANSFORMATION, IV_LENGTH, TAG_BITS, AES_KEY_SIZE_BITS). The two copies already disagree on missing-key handling: KeystoreCrypto.decrypt silently generates a key when the alias is absent (later failing with an opaque AEADBadTagException), while DatabaseKeyCipher.decrypt fails fast with error("auth-bound database key is missing"). Any future change (StrongBox opt-in, IV handling, error mapping) applied to one copy silently misses the other.

Separately, the accepted-authenticator policy is encoded twice with no cross-reference: AppLockManager.AUTHENTICATORS (BIOMETRIC_STRONG or DEVICE_CREDENTIAL, BiometricManager terms) and DatabaseKeyCipher.buildSpec (AUTH_BIOMETRIC_STRONG or AUTH_DEVICE_CREDENTIAL, KeyProperties terms). Loosening one without the other yields prompts that succeed but keys that throw UserNotAuthenticatedException at use — a drift only observable on device.

Suggested fix

Extract a shared alias-parameterized AES-256-GCM Keystore base (encrypt/decrypt/exists/delete + constants), leaving only the auth-bound spec and invalidation classification as DatabaseKeyCipher's delta; reconcile the missing-key behavior deliberately. Tie the two authenticator constants to one definition (or a documented mapping).

Origin: code review of PR #45 (screen-lock app gate). Reuse/maintainability cleanup. ## Problem `DatabaseKeyCipher` copy-pastes ~60 of its ~142 lines from the pre-existing `KeystoreCrypto`: `doEncrypt`/`decrypt` bodies, `existingKey`/`getOrCreateKey` (a split of `KeystoreCrypto.secretKey`), the `keyLock` idiom, and 5 identical constants (`ANDROID_KEYSTORE`, `TRANSFORMATION`, `IV_LENGTH`, `TAG_BITS`, `AES_KEY_SIZE_BITS`). The two copies already **disagree** on missing-key handling: `KeystoreCrypto.decrypt` silently generates a key when the alias is absent (later failing with an opaque `AEADBadTagException`), while `DatabaseKeyCipher.decrypt` fails fast with `error("auth-bound database key is missing")`. Any future change (StrongBox opt-in, IV handling, error mapping) applied to one copy silently misses the other. Separately, the accepted-authenticator policy is encoded twice with no cross-reference: `AppLockManager.AUTHENTICATORS` (`BIOMETRIC_STRONG or DEVICE_CREDENTIAL`, BiometricManager terms) and `DatabaseKeyCipher.buildSpec` (`AUTH_BIOMETRIC_STRONG or AUTH_DEVICE_CREDENTIAL`, KeyProperties terms). Loosening one without the other yields prompts that succeed but keys that throw `UserNotAuthenticatedException` at use — a drift only observable on device. ## Suggested fix Extract a shared alias-parameterized AES-256-GCM Keystore base (encrypt/decrypt/exists/delete + constants), leaving only the auth-bound spec and invalidation classification as `DatabaseKeyCipher`'s delta; reconcile the missing-key behavior deliberately. Tie the two authenticator constants to one definition (or a documented mapping).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: JMR-dev/LibreMail#102