feat(android): end-to-end read path validated on device, add the container diagnostic
GET in 80 ms, decryption in 3893 ms, entries and trash counted, second call answers 304. try-open.js opens a container with Node so a failure can be attributed: a GCM tag that does not verify means wrong password OR altered file, and the app cannot tell which.
This commit is contained in:
@@ -42,6 +42,35 @@ ce qui est acceptable et compréhensible pour l'utilisateur.
|
||||
> jamais toucher le disque autrement que chiffré sous la clé du Keystore, et la
|
||||
> sauvegarde automatique Android doit être désactivée pour l'application.
|
||||
|
||||
## Chaîne validée de bout en bout
|
||||
|
||||
Sur appareil : `GET` authentifié (80 ms) → déchiffrement (3893 ms) → lecture du
|
||||
JSON, entrées vivantes et corbeille comptées séparément. Second appel : **304**,
|
||||
rien de retéléchargé, rien de re-déchiffré.
|
||||
|
||||
**L'ETag n'est retenu que si le déchiffrement a réussi.** Le conserver dès la
|
||||
réception paraît naturel et c'est un piège : après un échec, le prochain appel
|
||||
répond 304 et l'application se retrouve sans données tout en se croyant à jour.
|
||||
L'ETag appartient au **clair détenu**, pas au téléchargement.
|
||||
|
||||
## Diagnostiquer un échec de déchiffrement
|
||||
|
||||
`try-open.js` ouvre un conteneur avec Node, donc avec les primitives du
|
||||
navigateur :
|
||||
|
||||
```bash
|
||||
node try-open.js <fichier> <mot-de-passe>
|
||||
```
|
||||
|
||||
Il sépare deux causes que l'application ne peut pas distinguer — un tag GCM qui
|
||||
ne vérifie pas signifie *mot de passe faux OU fichier altéré*, sans plus. Si
|
||||
Node ouvre le fichier et pas le client, le défaut est dans le client.
|
||||
|
||||
> Le mot de passe de sync n'est **pas** le mot de passe maître. Il n'est affiché
|
||||
> nulle part : `prefs.bin` le garde chiffré par DPAPI, et l'application sait
|
||||
> seulement le définir. En cas d'oubli, le redéfinir puis relancer un sync pour
|
||||
> rechiffrer l'instantané distant.
|
||||
|
||||
## Fichiers
|
||||
|
||||
| | |
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// Ouvre un conteneur pm-encrypted-export-v1 avec Node, pour distinguer un
|
||||
// mauvais mot de passe d'un defaut du client Delphi.
|
||||
//
|
||||
// node try-open.js <fichier> <mot-de-passe>
|
||||
//
|
||||
// Utilise les memes primitives que le navigateur. Si Node y arrive et pas
|
||||
// Delphi, le probleme est dans le client ; si les deux echouent, c'est le
|
||||
// mot de passe.
|
||||
|
||||
const fs = require('node:fs');
|
||||
const c = require('node:crypto');
|
||||
|
||||
const [, , file, pwd] = process.argv;
|
||||
if (!file || pwd === undefined) {
|
||||
console.error('usage: node try-open.js <fichier> <mot-de-passe>');
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const raw = fs.readFileSync(file, 'utf8').replace(/^/, '');
|
||||
let v;
|
||||
try {
|
||||
v = JSON.parse(raw);
|
||||
} catch (e) {
|
||||
console.error('Ce fichier n\'est pas du JSON : ' + e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('format : ' + v.format);
|
||||
console.log('kdf : ' + v.kdf + ' / ' + v.kdf_iterations + ' iterations');
|
||||
console.log('mot de passe : ' + pwd.length + ' caracteres, ' +
|
||||
Buffer.byteLength(pwd, 'utf8') + ' octets UTF-8');
|
||||
|
||||
const salt = Buffer.from(v.kdf_salt, 'base64');
|
||||
const iv = Buffer.from(v.iv, 'base64');
|
||||
const ct = Buffer.from(v.ciphertext, 'base64');
|
||||
const key = c.pbkdf2Sync(Buffer.from(pwd, 'utf8'), salt, v.kdf_iterations || 600000, 32, 'sha256');
|
||||
console.log('cle derivee : ' + key.toString('hex'));
|
||||
|
||||
try {
|
||||
const d = c.createDecipheriv('aes-256-gcm', key, iv);
|
||||
d.setAuthTag(ct.subarray(ct.length - 16));
|
||||
const pt = Buffer.concat([d.update(ct.subarray(0, ct.length - 16)), d.final()]).toString('utf8');
|
||||
const obj = JSON.parse(pt);
|
||||
const live = (obj.entries || []).filter(e => !e.deleted).length;
|
||||
const trash = (obj.entries || []).filter(e => e.deleted).length;
|
||||
console.log('==> OUVERT : ' + live + ' entrees, ' + trash + ' en corbeille');
|
||||
console.log(' exporte le ' + (obj.exported_at || obj.snapshot_at || '?'));
|
||||
} catch (e) {
|
||||
console.log('==> ECHEC : ' + e.message);
|
||||
console.log(' Le tag GCM ne verifie pas : mot de passe faux, ou fichier altere.');
|
||||
}
|
||||
Reference in New Issue
Block a user