feat(licence): deuxieme couche, l'API refuse sans licence valable

L'ecran d'activation ne consultait qu'un branchement : le rapiecer
ouvrait tout, l'API ne demandant rien. Chaque route de PM.Router passe
maintenant par PM.LicenceGate.ApiLicenceOk avant son handler.

Aucune fonction de decision partagee avec PM.Licence — sinon un seul
rapiecage ouvrirait les deux couches. La verification est refaite de
bout en bout, appels CNG redeclares compris ; seules la cle publique et
le nom de la pref sont partages, parce que ce sont des donnees.

Branchee dans DispatchRequest : les fichiers de l'ecran d'activation
passent par TryServeEmbedded, non garde, sinon l'ecran ne se chargerait
plus. Un oui est retenu pour le processus, un non jamais.

Le risque etait qu'elle refuse un client licencie. Auto-test sur un
vecteur lie a une empreinte fixe, sens accepte en premier ; et accord
verifie en conditions reelles sur ce poste : IsLicensed = TRUE,
ApiLicenceOk = TRUE.

Documente aussi le piege du jour, sans lien avec ce code : des .dcu TMS
x64 deposes a cote des sources cassaient le build Win32, et la sortie
redirigee vers E: faute de place sur Z:.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-09-16 22:55:33 +01:00
parent 5722347e75
commit 1fa5113c02
6 changed files with 304 additions and 2 deletions
+56
View File
@@ -459,6 +459,43 @@ 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.
### Deuxieme couche : l'API refuse sans licence (`PM.LicenceGate`)
La premiere couche (l'ecran d'activation) ne consultait qu'un branchement,
`IsLicensed` ; le rapiecer ouvrait tout, l'API ne demandant rien. **Chaque route
de `PM.Router` passe maintenant par `ApiLicenceOk` avant son handler** — sauter
l'ecran ne donne plus acces aux donnees.
- **Aucune fonction de decision partagee avec `PM.Licence`.** Si les deux
couches appelaient `IsLicensed`, un seul rapiecage les ouvrirait toutes les
deux. `PM.LicenceGate` refait sa propre verification de bout en bout —
decodage, SHA-256, appels CNG **redeclares sous d'autres noms**, lecture du
champ `m` — et ne prend a `PM.Licence` que des **donnees** : la cle publique
(`LicencePublicKey`) et le nom de la pref (`LicencePrefKey`).
- **Branchee dans `DispatchRequest`, pas avant.** Seules les routes de l'API
atteignent ce point ; les fichiers de l'ecran d'activation passent par
`TryServeEmbedded`, non garde — sinon l'ecran qui permet de s'activer ne se
chargerait plus. **404**, pas 403, comme le reste du verrouillage.
- Un **oui** est retenu pour le processus (le test est sur le chemin de chaque
requete) ; un **non** jamais, pour qu'une activation faite juste avant soit
vue a la requete suivante.
- **Le prix de l'independance : deux implementations qui doivent dire la meme
chose.** Une divergence ferait passer l'ecran puis tomber chaque requete en
404 — refuser quelqu'un qui a paye. `SelfTestLicenceGate` eprouve la 2e couche
seule sur un vecteur lie a une empreinte fixe (`0123456789abcdef`, paire
jetable dont la privee n'a jamais ete ecrite), **en commencant par le sens qui
accepte**. L'**accord** entre couches ne se teste pas en vecteur — la 1re
compare a l'empreinte reelle du poste — et se prouve en conditions reelles :
au 2026-09-16, sur ce poste avec la vraie licence, `IsLicensed` = TRUE et
`ApiLicenceOk` = TRUE.
- **Ce que ca n'arrete pas** : quelqu'un qui trouve et rapiece les deux. Une
heure devient deux ; la nature d'un schema hors ligne ne change pas. Les
protections du commerce (VMProtect, Themida) iraient plus loin mais
declenchent des **faux positifs antivirus** — ruineux pour un gestionnaire de
mots de passe. Les outils libres ne s'appliquent pas : OLLVM exige un
compilateur LLVM (dcc32 Win32 n'en est pas un), Alcatraz est x64 seulement,
UPX se defait par `upx -d`.
**ORDRE OBLIGATOIRE.** Tant que `LicencePublicKeyBlob` est a zero, aucune
licence n'est valide et l'ecran d'activation est **infranchissable** — l'exe
construit ne s'ouvre plus. Poser la cle publique AVANT de deployer :
@@ -1375,6 +1412,25 @@ le vide ET vide `FPendingURL` → écran noir permanent sur cold-start lent.
fix port-fixe est possible (`port=N` dans `config.txt`) mais on perd
l'aléatoire qui complique le scan externe.
- **Modifier les `Enable*` props TMS en `OnInitialized`**, pas FormCreate.
- **`F2048 Bad unit format … FMX.TMSFNC*.dcu … Found x64`** : le dossier des
sources `… mssoftware
egistered\TMS FNC Core\` est dans le chemin de
recherche, et une compilation y depose ses `.dcu` **a cote des `.pas`**. Une
compilation **Win64** (le 2026-09-07 a 11 h 45) en a reecrit 33 en x64, et le
build Win32 buttait dessus — **sans aucun rapport avec le code du projet** :
l'arbre propre echouait a l'identique. Correctif : les deplacer
(sauvegarde dans `… mssoftware\_backup-x64-dcu-2026-09-07`) ; le build Win32
retombe sur `Delphi12\Win32\Release\`. Le symptome reviendra a chaque
alternance Win32/Win64 sur ce poste.
- **Z: est quasi plein (0,3 Go au 2026-09-16).** Le build est redirige vers
`E:\password manager\` **par surcharge MSBuild**, pas dans le `.dproj` — un
chemin propre a une machine n'a pas sa place dans un fichier versionne :
```
msbuild PMServer.dproj /p:Config=Release /p:Platform=Win32
"/p:DCC_ExeOutput=E:\password manager" "/p:DCC_DcuOutput=E:\password manager\dcu"
```
**F9 dans l'IDE sort toujours sur Z:.** Et l'exe de `E:\password manager\`
cree **son propre `vault.db`** a cote de lui, sans `config.txt` : ce n'est pas
le coffre de `delphi-backend\` ni celui de `E: pmanager\`.
## Conventions code (héritage sessions précédentes)