feat(android): read the snapshot from a Gitea repo, and close cleartext

PMA.SyncClient now speaks two transports, told apart by the URL:
/api/v1/repos/ means the Gitea contents API, anything else the old PHP
endpoint. The PHP path stays until the Windows side publishes to the repo
too - without it the migration would need both ends flipped at once, with
no way back.

Three things differ on the Gitea side and nothing else: the header is
"token <PAT>" not "Bearer" (swapping them gives a silent 401), the body is
JSON carrying base64 content, and the fingerprint comparison happens in the
client since the API has no If-None-Match. The body is re-downloaded, which
is cheap; the four-second key derivation is still avoided. The result's
ETag field carries the sha, so the caller cannot tell which transport
answered.

usesCleartextTraffic is replaced by a network config that permits cleartext
to 127.0.0.1 and localhost BY NAME and forbids it everywhere else. The
attribute drew no such line: a mistyped URL sent the bearer token over the
network in the clear with nothing to signal it. Now it fails immediately,
which is the behaviour worth having.

Verified in the built APK: res/xml/network_security_config.xml is present
and the manifest attribute resolves to a real resource id. NOT verified
against a real repo - that needs a private repo, a published
vault-sync.json and a token, none of which exist yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-08-17 11:55:07 +01:00
parent b3fbaf44bd
commit 68df2dd7e7
5 changed files with 207 additions and 31 deletions
+32 -8
View File
@@ -177,11 +177,15 @@ Par ordre de gravité, pas de taille.
1. ~~**`android:allowBackup="false"`**~~ — **fait**. Le répertoire privé
contient `vault.bin` ; la sauvegarde Google n'a rien à y faire, même si ce
qui en sortirait serait chiffré par une clé restée dans la puce.
2. **Retirer `usesCleartextTraffic="true"`** du manifeste, et servir
l'endpoint en HTTPS avec un vrai certificat. L'attribut est marqué
TEST-ONLY ; le jeton Bearer circule à chaque appel. **Gardé pour l'instant** :
le retirer casserait l'essai local par `adb reverse`, qui passe en clair.
Voir §6 : passer par Gitea réglerait ce point sans rien héberger de plus.
2. ~~**Retirer `usesCleartextTraffic="true"`**~~ — **fait le 2026-08-17**, mais
pas en le retirant : remplacé par un
[network_security_config.xml](android-res/xml/network_security_config.xml) qui
n''autorise le clair que vers `127.0.0.1` et `localhost`, **nommément**, et
l''interdit partout ailleurs. L''attribut ne faisait pas cette distinction :
une URL mal saisie envoyait le jeton en clair sur le réseau sans que rien ne
le signale. Maintenant elle échoue tout de suite, ce qui est le comportement
voulu — bruyant, pas silencieux. L''essai local par `adb reverse` continue de
passer.
3. ~~**`FLAG_SECURE`**~~ — **fait**, [PMA.ScreenGuard.pas](PMA.ScreenGuard.pas),
**en Release seulement** : en debug le drapeau interdit aussi les captures
d'écran de développement. Le vérifier demande donc une compilation Release.
@@ -414,9 +418,29 @@ purge. Ça ne l'est pas sur GitHub, où le chiffré part chez un tiers, répliqu
et sauvegardé, avec un dépôt rendu public par erreur pour scénario du pire :
attaquable hors ligne, par n'importe qui, pour toujours.
**À faire si on s'y engage** : un `PMA.SyncClient` qui parle cette API (le GET
authentifié existe déjà), la même chose côté poste Windows en remplacement de
WebDAV, et une purge périodique de l'historique — sans quoi le dépôt grossit
### État : la lecture est écrite, elle n'a jamais parlé à un vrai dépôt
[PMA.SyncClient.pas](PMA.SyncClient.pas) parle les deux transports, **reconnus à
l'URL** : `/api/v1/repos/` → Gitea, sinon l'endpoint PHP. Le PHP est gardé le
temps que le poste Windows publie aussi dans le dépôt ; sans lui la migration
exigerait de tout basculer d'un coup, des deux côtés, sans retour possible.
Ce qui change côté Gitea, et rien d'autre : l'en-tête est `token <PAT>` et non
`Bearer` (l'un à la place de l'autre donne un 401 muet), le corps est du JSON
dont il faut extraire `content` en base64, et **la comparaison d'empreinte se
fait dans le client** — l'API ne connaît pas `If-None-Match`, donc le corps est
retéléchargé, mais la dérivation de clé, elle, est bien épargnée. Le champ
`ETag` du résultat porte le `sha` : l'appelant n'a pas à savoir lequel des deux
transports lui répond.
**Rien de tout cela n'a encore atteint un vrai dépôt.** Il faut, dans cet ordre :
un dépôt privé, un fichier `vault-sync.json` publié par le poste Windows, un
jeton limité à ce dépôt, puis l'URL de contenu complète dans *Server settings*.
Tant que le poste publie sur `sync.php`, le téléphone continue de lire là.
**Reste à faire** : le PUT (§4 seulement — la lecture n'écrit rien), la même
bascule côté poste Windows en remplacement de WebDAV, et une purge périodique de
l'historique — sans quoi le dépôt grossit
d'un instantané complet à chaque synchronisation, pièces jointes comprises.
## Ordre proposé