diff --git a/android-client/PLAN.md b/android-client/PLAN.md index 81ff07b..7352eba 100644 --- a/android-client/PLAN.md +++ b/android-client/PLAN.md @@ -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. + +
+Le diagnostic d'origine, conservé pour la trace + +## La décision qui bloquait le cache 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 confort, ce qui est le mauvais sens pour un gestionnaire de mots de passe. +
+ ## 2. Durcissement — petit, à faire avant tout usage réel Par ordre de gravité, pas de taille. diff --git a/android-client/README.md b/android-client/README.md index c9ed225..323c237 100644 --- a/android-client/README.md +++ b/android-client/README.md @@ -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