docs(android): the cache chain is verified on device

Write with no prompt, read under the device credential, cache surviving a cold start with no derivation, and a silent 304. Records the three traps that hid behind it, none of which the compiler could see.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-08-15 14:28:58 +01:00
parent c4d2bdde73
commit f3a8631440
2 changed files with 80 additions and 1 deletions
+41
View File
@@ -222,6 +222,47 @@ adb reverse tcp:8080 tcp:80
> — `404` signifie que la chaîne est bonne (c'est la réponse voulue sans jeton),
> `000` que rien n'écoute.
## Le cache : écrire sans invite, lire sous authentification
Validé sur appareil le 2026-08-15, sur un téléphone protégé par un **schéma**
et aucune empreinte enrôlée :
```
cache ecrit - protege par biometrie/code (aucune invite à l'écriture)
--- redémarrage à froid, saisie du schéma ---
4 entrees, 2 en corbeille (pas de ligne « dechiffre en … ms »)
HTTP 304 en 136 ms · 304 : inchange, coffre deja ouvert
```
Une clé AES du Keystore ne pouvait pas convenir : `setUserAuthenticationRequired`
exige une authentification récente **pour chiffrer autant que pour déchiffrer**,
et l'écriture suit une synchronisation silencieuse. Le cache ne s'écrivait donc
jamais, sauf dans les secondes suivant un déverrouillage — un test qui passe,
puis ne passe plus.
C'est une **paire RSA** : la partie publique enveloppe une clé AES jetable sans
invite, la partie privée — protégée — la déballe. L'authentification n'est
exigée qu'à la lecture.
Trois pièges, aucun visible à la compilation :
| | |
|---|---|
| `USE_BIOMETRIC` absente de la liste Delphi | `CanAuthenticate` **lève** au lieu de répondre `False` |
| L'invite vit dans `BiometricFragmentActivity` | à déclarer au manifeste, sinon `ActivityNotFoundException` |
| `setDigests()` n'autorise que le digest **du message** | celui du MGF1 est distinct, défaut SHA-1 |
Le troisième est le plus retors : le chiffrement passe, parce que la clé
publique reconstruite est servie par le fournisseur logiciel qui accepte
n'importe quel MGF1. On produit un chiffré valide que le matériel refuse de
lire, et le symptôme n'apparaît qu'au lancement suivant.
> **Aucune routine de lecture n'efface le cache.** `LoadVault` triait
> « clé perdue » contre « pas authentifié » sur le texte de l'exception et
> supprimait le fichier quand il penchait pour la première. Un tri erroné
> détruisait le cache *en emportant la seule trace de sa cause*. Elle rapporte
> désormais via `LastStoreError` ; l'appelant décide.
## Le piège de la porte sans clé
`USE_BIOMETRIC` **ne fait pas partie de la liste de permissions par défaut de