diff --git a/core/security/src/main/kotlin/dev/privacyllc/period/core/security/AndroidKeyStoreMacProvider.kt b/core/security/src/main/kotlin/dev/privacyllc/period/core/security/AndroidKeyStoreMacProvider.kt index fc50458..2870b15 100644 --- a/core/security/src/main/kotlin/dev/privacyllc/period/core/security/AndroidKeyStoreMacProvider.kt +++ b/core/security/src/main/kotlin/dev/privacyllc/period/core/security/AndroidKeyStoreMacProvider.kt @@ -28,8 +28,11 @@ import javax.crypto.SecretKey * - **`setUserAuthenticationParameters`** — meaningless without the above. * - **`setInvalidatedByBiometricEnrollment`** — a no-op without user * authentication, and named here so nobody adds it thinking it does something. - * The *biometric* key in [BiometricKey] does set it, correctly, because - * invalidation there degrades to "use your PIN" instead of to lockout. + * There is no second, biometric-bound key that does set it: this KDoc used to + * claim otherwise and was describing something that has never existed. A + * fingerprint flips the session flag; it unwraps nothing. See + * docs/security/SECURITY.md for why the version that would set it is filed + * rather than built. * - **`setUnlockedDeviceRequired`** — would fail exactly in the background, * where the reminder worker runs with the screen off. * - **`setIsStrongBoxBacked`** — StrongBox defends against extracting the key diff --git a/docs/security/SECURITY.md b/docs/security/SECURITY.md index 5c662a7..08e5180 100644 --- a/docs/security/SECURITY.md +++ b/docs/security/SECURITY.md @@ -74,9 +74,21 @@ Data Safety section, and never lets health data reach any of them. KDoc on `AndroidKeyStoreMacProvider` lists every builder call omitted to keep it that way. A key bound to the device credential dies when the passcode changes or the screen lock is removed — and with no recovery path, that is - somebody's entire history destroyed by an unrelated action. The *biometric* - key is separate and takes the opposite policy, where invalidation correctly - degrades to "use your PIN" rather than to lockout. + somebody's entire history destroyed by an unrelated action. +- **There is no second, biometric-bound key.** This document and the provider's + own KDoc both used to describe one, taking "the opposite policy" and + invalidating on enrolment. It does not exist: `grep -rn BiometricKey` finds + only those two sentences. A successful fingerprint flips the in-memory session + flag and nothing else — there is no `CryptoObject` and no key unwrap, so the + biometric path is a gate, not a second secret. +
Worth stating plainly because the absent version is not obviously wrong: + binding a key to biometric enrolment would mean that adding a fingerprint — a + partner's, say — invalidates it, which is exactly the protection somebody + would want. It is filed rather than built (tracker #63) because that only + degrades safely where a PIN sits behind it; in a fingerprint-only lock, a key + destroyed by an ordinary enrolment is the no-recovery policy turned against + its owner. A security document describing protection the code does not have is + worse than one admitting the gap, because the next reader trusts it. - **Wrong PINs cost time and never cost data.** Four free attempts, then 30s → 1m → 2m → 5m → 15m, capped at fifteen minutes forever. There is no attempt limit and nothing is ever erased automatically: an auto-wipe would let diff --git a/docs/security/SECURITY_CHECKLIST.md b/docs/security/SECURITY_CHECKLIST.md index 5f33b32..dce2b89 100644 --- a/docs/security/SECURITY_CHECKLIST.md +++ b/docs/security/SECURITY_CHECKLIST.md @@ -54,6 +54,11 @@ The one group that is not generic. Every item proves part of - [ ] With the app lock on, the recents card is a solid colour and `adb exec-out screencap` returns a black frame — proves `FLAG_SECURE` is actually applied, which no source check can establish - [ ] Killed with `adb shell am kill` and reopened from recents, the app lands on the lock screen — proves the unlock flag is not in saved state, which is the one bug that would make the lock look fine and never engage - [ ] A reminder action tapped while locked writes nothing until after the unlock — proves a bystander cannot record an answer in someone's history from the phone's own lock screen +- [ ] A fingerprint enrolled on API 26 or 27 opens the app through the compat dialog — proves the pre-API-28 path `USE_FINGERPRINT` exists for is real, and is the one biometric route no current emulator exercises +- [ ] An OEM biometric overlay that stops the activity does not relock the app under its own prompt — proves the `authInProgress` guard holds on the devices it was written for; without it a successful scan returns to a locked screen forever +- [ ] Rotating the phone and setting font scale to 2.0 while unlocked does not relock — proves the gate tells a configuration change apart from a real backgrounding +- [ ] TalkBack reads the lock screen as a password field and announces the wait after a wrong PIN — proves the one screen a person may meet without ever having opened the app is usable without sight +- [ ] No `FLAG_SECURE` flicker on first launch — proves the flag is set before anything can be drawn, rather than after a frame of real content has reached the recents thumbnail - [ ] `KeystoreVerifierTest` run on `PeriodMinSdk26` as well as a current image — proves `PBKDF2WithHmacSHA256` and a non-auth-bound Keystore key exist at the oldest supported version, which is the one algorithm choice here with no margin - [ ] The erase behind "Forgot your PIN?" leaves the app openable — proves the Keystore key went with the records, and the user is not erased *and* still locked out - [ ] No shipped string, illustration, screenshot or store listing says the cycle database is **encrypted** — proves the product does not claim a control [`SECURITY.md`](SECURITY.md) records as deliberately out of scope. It is on this list because the claim is attractive, easy to draw, and was already sitting in an artwork brief before anybody checked whether it was true