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
|
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.
|
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`)
|
### Deuxieme couche : l'API refuse sans licence (`PM.LicenceGate`)
|
||||||
|
|
||||||
La premiere couche (l'ecran d'activation) ne consultait qu'un branchement,
|
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
|
Sensitive actions (export, change master pw, recovery code…) still
|
||||||
require master pw via `askReauth` — sync never substitutes.
|
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
|
**Tombstone purge on (re)create** : `POST /entries` and
|
||||||
`POST /entries/bulk-import` both DELETE any `entry_tombstones` row
|
`POST /entries/bulk-import` both DELETE any `entry_tombstones` row
|
||||||
matching the inserted uuid (same transaction). Without this, restoring
|
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
|
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:.**
|
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**
|
**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
|
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
|
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.Biometric.pas` | authentification via `FMX.BiometricAuth` |
|
||||||
| `PMA.Vault.pas` | l'instantané en mémoire : lecture et recherche |
|
| `PMA.Vault.pas` | l'instantané en mémoire : lecture et recherche |
|
||||||
| `PMA.Totp.pas` | codes TOTP (RFC 6238), base32 ou `otpauth://` |
|
| `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 |
|
| `selftest.dpr` | vérification console : conteneur + TOTP |
|
||||||
| `PMA.TestVector.pas` | le vecteur embarqué, plus référencé par l'interface |
|
| `PMA.TestVector.pas` | le vecteur embarqué, plus référencé par l'interface |
|
||||||
| `make-testvector.js` | régénère le vecteur avec WebCrypto |
|
| `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.
|
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
|
## Fabriquer l'APK en ligne de commande
|
||||||
|
|
||||||
L'EDI y arrive seul ; MSBuild non, et l'erreur ne dit pas pourquoi.
|
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
|
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
|
> **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 à
|
> `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 **à
|
> 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>
|
<button class="btn wide main" id="sSave">Save</button>
|
||||||
<div class="note">The sync password never leaves this device. It is what
|
<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
|
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
|
device that shares it; the token only opens the repository. It is asked
|
||||||
password that opens a vault created on this phone - that one is asked for
|
for once: afterwards the vault opens with whatever you pick under
|
||||||
on the home screen.</div>
|
<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
|
<!-- COFFRE FERME, SECTION CACHEE. Poser comme retirer un code exigent le
|
||||||
clair en main : les deux ne pourraient que refuser, et proposer une
|
clair en main : les deux ne pourraient que refuser, et proposer une
|
||||||
|
|||||||
Reference in New Issue
Block a user