feat(licence): verification hors ligne d'une licence signee

PM.Licence verifie une chaine charge-utile.signature contre la cle
publique de l'editeur, via BCryptVerifySignature. Aucun ASN.1 : la
signature du script est deja en r||s brut et les coordonnees se rangent
telles quelles dans un BCRYPT_ECCPUBLIC_BLOB.

La cle publique est livree A ZERO — tant que la sortie de licence.ps1
-Init n'y est pas collee, aucune licence n'est valide. C'est le defaut
sur : un blob par defaut qui accepterait quelque chose serait pire.

IsLicensed REVERIFIE la licence rangee plutot que de lire un drapeau.
La signature est verifiee AVANT que la charge utile soit analysee.

SelfTestLicence a l'initialization rejoue un vecteur fige signe par une
paire de test : valable accepte, un octet retourne refuse. Verifie en
executant l'unite, pas seulement en la compilant.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-09-05 14:44:55 +01:00
parent 2b9f3ca37d
commit 960d2b388a
4 changed files with 367 additions and 0 deletions
+46
View File
@@ -245,6 +245,52 @@ Détails :
Le cadre est peint dans `WM_ERASEBKGND` plutôt que par `WS_BORDER`, qui se
sert dans les couleurs système.
## Licences (`PM.Licence`)
Verification **hors ligne** d'une licence signee ECDSA P-256. Une licence est
une chaine `charge-utile.signature`, chacune en base64url, fabriquee par
`tools/licence.ps1` avec la cle privee qui reste chez l'editeur. Seule la cle
**publique** est dans l'exe : elle ne permet que de verifier.
```
tools\licence.ps1 -Init # une seule fois : cree la paire,
# imprime la constante Delphi
tools\licence.ps1 -For "a@b.com" -Name "Ami" # une licence
```
- **`LicencePublicKeyBlob`** (72 octets) est un `BCRYPT_ECCPUBLIC_BLOB` :
magic `ECS1` + longueur(32) + X(32) + Y(32). **Livre a zero** — tant qu'on
n'y a pas colle la sortie de `-Init`, aucune licence n'est valide. Ne jamais
regenerer la paire ensuite : toutes les licences deja emises tomberaient.
- **Aucun ASN.1 nulle part.** .NET sur Windows EST CNG : la signature produite
par le script (`r||s` brut, 64 octets) est deja celle qu'attend
`BCryptVerifySignature`, et les coordonnees exportees se rangent telles
quelles dans le blob. Le decodage DER, moitie penible de l'exercice
d'habitude, n'existe pas ici.
- `BCryptVerifySignature` attend **l'empreinte**, pas le message : SHA-256 est
calcule avant l'appel (`THashSHA2.Create` + `Update`, pas `GetHashBytes` qui
n'a pas de surcharge `TBytes`).
- **La signature d'abord, la lecture ensuite** : `SplitAndVerify` n'analyse
jamais une charge utile dont il ne sait pas encore qu'elle vient de nous.
- `IsLicensed` **REVERIFIE** la licence rangee au lieu de lire un drapeau : un
drapeau se retourne, une signature se refabrique seulement avec la cle privee.
Resultat mis en cache en memoire, invalide par `ActivateLicence`/`ClearLicence`.
- Rangee en pref DPAPI (`licenceKey`) — liee au compte Windows, survit au
changement de port comme le reste.
- **Pas d'expiration dans la charge utile**, volontairement : une date qu'on
fait respecter avec l'horloge de la machine du client ne prouve rien. Le
champ `d` n'est qu'une date d'emission.
- `SelfTestLicence` a l'`initialization` (comme `SelfTestCrypto`) rejoue un
vecteur fige signe par une **paire de test** dont la privee a ete jetee :
licence valable acceptee, un octet retourne dans la charge utile refuse.
`VerifyAgainstKey` prend la cle publique en parametre exactement pour que
l'auto-test n'ait pas besoin d'un second jeu de constantes de production.
**Ce que ca arrete** : sans la cle privee, personne ne fabrique de licence
valable. **Ce que ca n'arrete pas** : rapiecer l'exe pour sauter l'appel — vrai
de tout schema hors ligne. La seule parade reelle serait de deporter une
fonction sur un serveur, ce qui contredit le « 100 % hors ligne ».
## Debug mode
Fichier `config.txt` à côté de l'exe (tous champs optionnels) :