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:
+39
-1
@@ -57,7 +57,43 @@ retéléchargé, pas rester illisible. C'est l'autre branche de la même garde.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1 bis. La décision qui bloque le cache (constatée le 2026-08-15)
|
## 1 bis. ~~La décision qui bloque le cache~~ — RÉSOLU le 2026-08-15
|
||||||
|
|
||||||
|
Mesuré sur appareil (schéma d'écran, 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 (aucune ligne « dechiffre en … ms »)
|
||||||
|
HTTP 304 en 136 ms
|
||||||
|
304 : inchange, coffre deja ouvert
|
||||||
|
```
|
||||||
|
|
||||||
|
Les points §1.1, §1.2, §1.3 et §1.5 sont donc validés d'un coup : clé créée sous
|
||||||
|
authentification, ouverture par schéma, cache survivant à un démarrage à froid
|
||||||
|
sans dérivation, et rafraîchissement de fond silencieux.
|
||||||
|
|
||||||
|
**Trois pièges se cachaient derrière, aucun visible à la compilation** :
|
||||||
|
|
||||||
|
1. `USE_BIOMETRIC` absente de la liste par défaut de Delphi — et
|
||||||
|
`CanAuthenticate` lève au lieu de répondre `False`.
|
||||||
|
2. FMX n'affiche pas l'invite depuis notre activité : il lance
|
||||||
|
`BiometricFragmentActivity`, qui doit être déclarée au manifeste.
|
||||||
|
3. **Le digest du MGF1.** `setDigests()` n'autorise que celui du message ;
|
||||||
|
celui du MGF1 est une autorisation distincte dont le défaut est SHA-1.
|
||||||
|
Le chiffrement passait quand même — la clé publique reconstruite est servie
|
||||||
|
par le fournisseur logiciel, qui accepte n'importe quel MGF1 — et produisait
|
||||||
|
un chiffré valide que le matériel refusait de lire. Le symptôme
|
||||||
|
n'apparaissait qu'au lancement suivant.
|
||||||
|
|
||||||
|
Ce qui reste de §1 : l'expiration de la fenêtre de 30 s (le cache doit
|
||||||
|
survivre — il le fera, `LoadVault` n'efface plus jamais) et l'enrôlement d'une
|
||||||
|
nouvelle empreinte, qui invalide la clé pour de bon.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Le diagnostic d'origine, conservé pour la trace</summary>
|
||||||
|
|
||||||
|
## La décision qui bloquait le cache
|
||||||
|
|
||||||
Le cache ne s'écrit presque jamais. Journal sur appareil :
|
Le cache ne s'écrit presque jamais. Journal sur appareil :
|
||||||
|
|
||||||
@@ -94,6 +130,8 @@ jamais.
|
|||||||
Retenir la 1. Les deux autres échangent une propriété de sécurité contre du
|
Retenir la 1. Les deux autres échangent une propriété de sécurité contre du
|
||||||
confort, ce qui est le mauvais sens pour un gestionnaire de mots de passe.
|
confort, ce qui est le mauvais sens pour un gestionnaire de mots de passe.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## 2. Durcissement — petit, à faire avant tout usage réel
|
## 2. Durcissement — petit, à faire avant tout usage réel
|
||||||
|
|
||||||
Par ordre de gravité, pas de taille.
|
Par ordre de gravité, pas de taille.
|
||||||
|
|||||||
@@ -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),
|
> — `404` signifie que la chaîne est bonne (c'est la réponse voulue sans jeton),
|
||||||
> `000` que rien n'écoute.
|
> `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é
|
## Le piège de la porte sans clé
|
||||||
|
|
||||||
`USE_BIOMETRIC` **ne fait pas partie de la liste de permissions par défaut de
|
`USE_BIOMETRIC` **ne fait pas partie de la liste de permissions par défaut de
|
||||||
|
|||||||
Reference in New Issue
Block a user