docs: QR des reglages, trois facons d'ouvrir, scanner, AllowCreateLocal, debuggable
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -459,6 +459,14 @@ en dix minutes serait le mauvais echange. Le but est de tenir le mobile attache
|
||||
a un poste paye, pas de resister a une attaque — l'APK ne peut pas resister, et
|
||||
faire semblant couterait plus que de l'admettre.
|
||||
|
||||
**Le reste du travail Android est dans
|
||||
[android-client/README.md](android-client/README.md)**, tenu a jour le
|
||||
2026-09-17 : les trois facons d'ouvrir le coffre (verrouillage d'ecran, code
|
||||
d'application, mot de passe), la copie de secours `vault-recover.bin` qui
|
||||
survit au schema retire, le scanner de QR (`ThirdParty/ZXing`), le define
|
||||
`AllowCreateLocal` — creer un coffre sur le telephone est reserve au debogage —
|
||||
et le correctif du manifeste, dont l'APK **release n'est plus `debuggable`**.
|
||||
|
||||
### Deuxieme couche : l'API refuse sans licence (`PM.LicenceGate`)
|
||||
|
||||
La premiere couche (l'ecran d'activation) ne consultait qu'un branchement,
|
||||
@@ -1047,6 +1055,34 @@ device, never transmitted.
|
||||
Sensitive actions (export, change master pw, recovery code…) still
|
||||
require master pw via `askReauth` — sync never substitutes.
|
||||
|
||||
### QR des reglages pour le telephone (`sync/qr`)
|
||||
|
||||
Recopier une URL d'API et quarante caracteres de jeton sur un telephone est le
|
||||
vrai obstacle a l'installation. **Settings -> Sync -> QR for phone** affiche un
|
||||
QR que l'application Android scanne elle-meme.
|
||||
|
||||
- **C'est l'EXE qui fabrique le QR**, pas la page : il lit `syncUrl` et
|
||||
`syncPwd` dans ses prefs DPAPI et n'envoie a la page que la **matrice**
|
||||
noir/blanc (`Bridge.onSyncQr(taille, bits)`). Le jeton ne traverse donc pas le
|
||||
moteur de rendu en clair - meme regle que `prefs/has`.
|
||||
- **Le mot de passe de synchro n'y est JAMAIS** : un QR se photographie
|
||||
par-dessus l'epaule, et avec lui il ouvrirait le coffre du depot. Il se tape
|
||||
une fois sur le telephone. Le QR se masque seul au bout de 60 s.
|
||||
- **Le contenu est un lien `pmserver://sync?u=&t=`, pas du texte.** L'appareil
|
||||
photo du telephone envoie tout TEXTE a une recherche web : le jeton serait
|
||||
parti chez Google a un appui pres. Un schema propre n'a qu'une application
|
||||
pour l'ouvrir.
|
||||
- **Le plus court possible** : l'adresse du depot (`https://hote/Owner/Repo`) et
|
||||
non l'URL d'API - le telephone la rallonge a l'enregistrement -, et seuls
|
||||
`% & # + espace` sont echappes. `TNetEncoding.URL.Encode` changeait chaque
|
||||
`:` et `/` en trois caracteres, et le QR devenait trop dense pour etre lu.
|
||||
- Generateur : **`Source/DelphiZXingQRCode.pas`** (portage ZXing, Apache 2.0,
|
||||
licence a cote). `BCryptVerifySignature` n'a rien a voir ici : le QR n'est pas
|
||||
signe, il ne porte que ce que l'utilisateur voit deja a l'ecran.
|
||||
- **Piege CSS rencontre** : `css/style.css` impose `svg { width: 16px }` a
|
||||
toutes les icones, ce qui reduisait le QR a la taille d'une icone. La taille
|
||||
est donc posee **en style direct** sur le `<svg>`.
|
||||
|
||||
**Tombstone purge on (re)create** : `POST /entries` and
|
||||
`POST /entries/bulk-import` both DELETE any `entry_tombstones` row
|
||||
matching the inserted uuid (same transaction). Without this, restoring
|
||||
@@ -1448,6 +1484,9 @@ le vide ET vide `FPendingURL` → écran noir permanent sur cold-start lent.
|
||||
```
|
||||
Surcharge MSBuild, pas dans le `.dproj` : un chemin propre a une machine n'a
|
||||
pas sa place dans un fichier versionne. **F9 dans l'IDE sort toujours sur Z:.**
|
||||
**La sortie Android suit le meme chemin** : `Z:\password-manager\Android64` est
|
||||
une **jonction** vers `E:\password manager\Android64`, posee le 2026-09-17 quand
|
||||
`Z:` est tombe a 40 Ko libres et que `aapt` echouait sans dire pourquoi.
|
||||
**Des `.dcu` separes par plateforme sont ce qui empeche la pollution TMS**
|
||||
ci-dessus : verifie au premier build 32+64, aucun `.dcu` depose a cote des
|
||||
sources TMS. L'exe de `E:\password manager\` cree **son propre `vault.db`** a
|
||||
|
||||
+112
-1
@@ -176,7 +176,8 @@ Node ouvre le fichier et pas le client, le défaut est dans le client.
|
||||
| `PMA.Biometric.pas` | authentification via `FMX.BiometricAuth` |
|
||||
| `PMA.Vault.pas` | l'instantané en mémoire : lecture et recherche |
|
||||
| `PMA.Totp.pas` | codes TOTP (RFC 6238), base32 ou `otpauth://` |
|
||||
| `PMA.ScreenGuard.pas` | `FLAG_SECURE` (Release seulement) |
|
||||
| `PMA.ScreenGuard.pas` | `FLAG_SECURE` (Release seulement) — bloque aussi `adb exec-out screencap` |
|
||||
| `ThirdParty/ZXing/` | ZXing.Delphi (Apache 2.0), décodage du QR des réglages |
|
||||
| `selftest.dpr` | vérification console : conteneur + TOTP |
|
||||
| `PMA.TestVector.pas` | le vecteur embarqué, plus référencé par l'interface |
|
||||
| `make-testvector.js` | régénère le vecteur avec WebCrypto |
|
||||
@@ -184,6 +185,100 @@ Node ouvre le fichier et pas le client, le défaut est dans le client.
|
||||
|
||||
L'interface est `mainApk.pas`, à la racine du dépôt.
|
||||
|
||||
## Trois façons d'ouvrir le coffre — 2026-09-17
|
||||
|
||||
`Settings → Unlock with` : le **verrouillage d'écran** (schéma, code,
|
||||
empreinte, visage), un **code d'application**, ou le **mot de passe** qui ouvre
|
||||
le coffre. Le bouton en place porte une coche, et la note dit lequel est actif.
|
||||
Le coffre lui-même n'est jamais touché : seul le scellement de `vault.bin`
|
||||
change (`VER_WRAP` ↔ `VER_PIN`).
|
||||
|
||||
- Le nom du mot de passe **suit l'origine du coffre** : « sync password » pour
|
||||
un coffre venu du dépôt, « vault password » pour un coffre créé sur le
|
||||
téléphone. `TForm8.PwdName` et `PushUnlockMode` le décident, et le libellé se
|
||||
repose à chaque `AdoptVault` — sinon un téléchargement laissait l'ancien.
|
||||
- Revenir au verrouillage d'écran demande de **retaper le code courant** :
|
||||
retirer une protection exige de la connaître.
|
||||
- Sans verrouillage d'écran sur l'appareil, le bouton est grisé et **dit
|
||||
pourquoi** au lieu de rester muet.
|
||||
|
||||
### La copie de secours, pour survivre au schéma retiré
|
||||
|
||||
Retirer ou réinitialiser le verrouillage d'écran fait **détruire par Android**
|
||||
la clé qui scelle `vault.bin` : le coffre devenait illisible et il fallait le
|
||||
retélécharger. `SaveVault` écrit donc, **tant que le cache est en `VER_WRAP`**,
|
||||
une seconde copie `vault-recover.bin` scellée sous le mot de passe (format
|
||||
`VER_PIN` : clé d'appareil sans contrainte + PBKDF2), qu'Android n'invalide
|
||||
jamais.
|
||||
|
||||
- Tout autre scellement l'efface : en mode code ou mot de passe, elle
|
||||
n'apporterait rien.
|
||||
- À l'ouverture, `OpenVault` bascule dessus quand il n'y a plus de
|
||||
verrouillage utilisable, et `OpenLocalNow` quand le système vient de déclarer
|
||||
la clé morte (`LastStoreKeyInvalidated`). L'accueil demande alors le mot de
|
||||
passe au lieu d'annoncer un coffre illisible.
|
||||
- **Rescellé immédiatement** après cette ouverture : sous le nouveau
|
||||
verrouillage s'il existe (clé neuve, l'ancienne est morte), sinon sous le mot
|
||||
de passe. Le coffre ne reste jamais sans protection.
|
||||
- Un coffre **venu seulement de la synchro** avant cette version n'a pas de
|
||||
copie de secours : lui, il se retélécharge.
|
||||
|
||||
## Scanner le QR des réglages — 2026-09-17
|
||||
|
||||
Le poste affiche un QR (`Settings → Sync → QR for phone`) qui porte
|
||||
`pmserver://sync?u=&t=` — URL du dépôt et jeton, **jamais** le mot de passe de
|
||||
synchro. Deux chemins le reçoivent :
|
||||
|
||||
1. **`Settings → Scan the QR shown on the PC`** : aperçu caméra plein écran,
|
||||
décodage par **`ThirdParty/ZXing`** (ZXing.Delphi, Apache 2.0).
|
||||
2. **Le lien lui-même**, si un scanner extérieur l'ouvre : `intent-filter`
|
||||
`VIEW` sur le schéma `pmserver` dans le manifeste, intent de départ lu au
|
||||
`FormCreate` et suivants par `TMessageReceivedNotification`.
|
||||
|
||||
Dans les deux cas **rien n'est enregistré** : les champs se remplissent, c'est
|
||||
`Save` qui valide. Le lien peut venir de n'importe où, et l'URL est sous les
|
||||
yeux de l'utilisateur au moment où il valide.
|
||||
|
||||
Quatre défauts rencontrés, tous invisibles à la compilation :
|
||||
|
||||
| Symptôme | Cause |
|
||||
|---|---|
|
||||
| L'appareil photo propose « Ouvrir avec » et des navigateurs | il envoie tout **texte** à une recherche web ; d'où le schéma `pmserver://`, que seule l'app ouvre |
|
||||
| Caméra noire au **premier** scan après l'autorisation | la fenêtre d'autorisation fait passer par `BecameActive`, qui remettait la vue web **par-dessus** l'aperçu ; elle ne le fait plus pendant un scan |
|
||||
| Écran noir, aucune image | la caméra est tenue par une autre application (l'appareil photo, même en arrière-plan). Un guet de 3 s sans image le **dit** maintenant |
|
||||
| QR lu mais ignoré | le texte décodé arrive avec un **caractère invisible** en tête (104 lus pour 103 émis) : on cherche le lien dans la chaîne au lieu de comparer le début |
|
||||
|
||||
Le décodage est lent et n'aboutit que sur une image de temps en temps — c'est un
|
||||
portage Delphi, pas le scanner natif de Google. Une image sur quatre part au
|
||||
décodage, jamais deux à la fois, et l'indice **`TRY_HARDER`** est indispensable
|
||||
pour un QR photographié sur un écran.
|
||||
|
||||
## Créer un coffre ici : essais seulement (`AllowCreateLocal`)
|
||||
|
||||
Un coffre né sur le téléphone n'existe nulle part ailleurs — le poste ne le
|
||||
connaît pas, et personne ne peut le rendre à qui perd son mot de passe. Le
|
||||
define **`AllowCreateLocal`**, posé par `{$IFDEF DEBUG}` et jamais en release,
|
||||
garde ce chemin pour les essais.
|
||||
|
||||
En release l'accueil n'offre que **Download vault** (`PM.setAuthMode("download")`),
|
||||
et `RegisterLocal` refuse aussi : l'interdit ne tient pas qu'à l'écran. Dépôt
|
||||
vide (404) ou conteneur illisible, **rien n'est créé** — un coffre neuf
|
||||
masquerait un réglage à corriger.
|
||||
|
||||
## Saisie : ce qui a résisté
|
||||
|
||||
- **`user-select: none` du `body`** était hérité par les champs : le curseur ne
|
||||
suivait pas le doigt et le texte ne défilait jamais, donc la fin d'une URL
|
||||
restait hors d'atteinte. Les champs ont leur exception, mais **ça n'a pas
|
||||
suffi** : l'URL est désormais une `textarea` qui passe à la ligne et prend la
|
||||
hauteur de son texte.
|
||||
- La **croix** d'effacement couvrait toute la marge droite : toucher la fin du
|
||||
texte effaçait le champ. Elle est plus étroite que la marge.
|
||||
- **`Save` est un geste** : fermer les réglages n'enregistre plus rien. Une URL
|
||||
effacée par erreur revient en revenant en arrière.
|
||||
- L'URL s'affiche en **forme courte** (`https://hôte/Owner/Repo`) et
|
||||
`normalizeRepoUrl` la rallonge en URL d'API à l'enregistrement.
|
||||
|
||||
## Fabriquer l'APK en ligne de commande
|
||||
|
||||
L'EDI y arrive seul ; MSBuild non, et l'erreur ne dit pas pourquoi.
|
||||
@@ -229,6 +324,22 @@ signature* sur le dossier déjà prêt, sans le vider :
|
||||
android-client\repack-apk.ps1
|
||||
```
|
||||
|
||||
> **La sortie Android vit sur `E:`.** `Z:` est arrivé à 40 Ko libres le
|
||||
> 2026-09-17 et `aapt` échouait sans dire pourquoi. `Z:\password-manager\Android64`
|
||||
> est désormais une **jonction** vers `E:\password manager\Android64` : les
|
||||
> scripts et le `.dproj` n'ont pas changé, et rien de machine-dépendant n'est
|
||||
> entré dans le dépôt.
|
||||
|
||||
> **La release n'est plus `debuggable`.** Delphi écrit
|
||||
> `android:debuggable="true"` dans le manifeste **même en Release**, et l'APK
|
||||
> livré le portait. Sur un téléphone où le débogage USB est actif, `adb run-as`
|
||||
> ouvrait alors le dossier privé de l'application — donc `vault.bin` et les
|
||||
> réglages — sans accès root. `build-apk.ps1` force l'attribut à `false` dans
|
||||
> le manifeste mis en scène, **et seulement pour une release**. Vérification :
|
||||
> `adb shell dumpsys package com.embarcadero.pmserverApk` ne doit plus lister
|
||||
> `DEBUGGABLE` dans `pkgFlags`. Conséquence assumée : plus de `run-as` ni de
|
||||
> capture d'écran sur une release, c'est-à-dire plus d'inspection à distance.
|
||||
|
||||
> **Place disque.** Le `.so` de debug pèse **91 Mo** (symboles), et le volume
|
||||
> `Z:` n'en fait que 2 Go. Le dépouiller avec le `strip` du NDK le ramène à
|
||||
> 54 Mo, et l'APK final à 16 Mo — mais `strip` écrit sa copie temporaire **à
|
||||
|
||||
@@ -592,9 +592,10 @@
|
||||
<button class="btn wide main" id="sSave">Save</button>
|
||||
<div class="note">The sync password never leaves this device. It is what
|
||||
encrypts the copy kept in the repository, so it must be the same on every
|
||||
device that shares it; the token only opens the repository. It is not the
|
||||
password that opens a vault created on this phone - that one is asked for
|
||||
on the home screen.</div>
|
||||
device that shares it; the token only opens the repository. It is asked
|
||||
for once: afterwards the vault opens with whatever you pick under
|
||||
<b>Unlock with</b> below - the phone screen lock, an app code, or this
|
||||
password.</div>
|
||||
|
||||
<!-- COFFRE FERME, SECTION CACHEE. Poser comme retirer un code exigent le
|
||||
clair en main : les deux ne pourraient que refuser, et proposer une
|
||||
|
||||
Reference in New Issue
Block a user