Files
Password-Manager/delphi-backend/Source/PM.MachineId.pas
T
r-zakarya 61ec5ff7a1 fix(licence): l'empreinte ne depend plus de l'ordre de demarrage
BoardUUID s'appuyait sur un COM initialise par quelqu'un d'autre. Appele
apres WebView2 il lisait le vrai UUID de carte mere, appele avant il
echouait et retombait sur MachineGuid : deux empreintes pour une seule
machine, et une licence liee a l'une que l'autre refuse. Il suffisait que
licence/status parte avant l'init du navigateur pour verrouiller
l'utilisateur hors de son coffre.

CoInitializeEx est desormais appele sur place. Pas de CoUninitialize
symetrique : essaye, il rend une violation d'acces — les mandataires WMI
et le cache de type-info de ComObj se liberent plus tard, dans un
appartement qu'on vient de fermer.

Verifie avec un harnais qui n'initialise PAS COM : il rend maintenant la
meme empreinte que l'application.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 16:00:50 +01:00

163 lines
5.5 KiB
ObjectPascal

unit PM.MachineId;
{
Empreinte de la machine : carte mere + disque systeme.
Sert a lier une licence a UN poste (PM.Licence). Deux sources, melangees puis
hachees, dont on ne publie que 16 caracteres hexadecimaux :
- UUID de la carte mere, lu dans le SMBIOS via Win32_ComputerSystemProduct.
- Numero de serie du volume systeme (GetVolumeInformation).
POURQUOI HACHER PLUTOT QUE CONCATENER. L'empreinte se recopie dans un
courriel pour demander une licence. L'UUID brut de la carte mere est un
identifiant materiel stable et mondialement unique : il n'a pas a circuler
en clair, ni a se retrouver dans nos journaux. Le condense suffit a comparer,
et ne se remonte pas.
CE QUI LA CHANGE, ET DONC CASSE LA LICENCE : remplacer la carte mere,
remplacer le disque systeme, reinstaller Windows sur un autre volume, ou
cloner vers une autre machine. C'est le prix assume du liage — la reparation
est une reemission, pas un correctif.
QUAND LE SMBIOS MENT. Beaucoup de machines virtuelles et d'images clonees
annoncent un UUID nul ou tout a FF. S'en contenter ferait tomber des postes
differents sur la MEME empreinte, et une licence emise pour l'un ouvrirait
chez l'autre. On retombe alors sur MachineGuid, pose par Windows a
l'installation et distinct sur chaque installation.
}
interface
// 16 caracteres hexadecimaux, minuscules. Calcule une fois, puis retenu :
// l'appel WMI coute quelques centaines de millisecondes.
function MachineFingerprint: string;
// La meme, en groupes de quatre — c'est un code qu'un humain recopie a la
// main dans un courriel, et le decoupage divise les fautes de transcription.
function MachineFingerprintDisplay: string;
implementation
uses
System.SysUtils, System.Win.ComObj, System.Win.Registry, System.Variants,
Winapi.Windows, Winapi.ActiveX, PM.Crypto;
var
CachedPrint: string = '';
function BoardUUID: string;
var
LLocator, LServices, LSet, LItem: OleVariant;
LEnum: IEnumVariant;
LFetched: LongWord;
begin
Result := '';
// COM DOIT ETRE INITIALISE ICI, PAS AILLEURS PAR CHANCE.
//
// Sans ca, l'empreinte depend de qui a demarre avant nous : appelee apres
// l'initialisation de WebView2 elle lit le vrai UUID de la carte mere,
// appelee avant elle echoue et retombe sur MachineGuid — DEUX empreintes
// differentes pour une seule machine, et une licence liee a l'une que
// l'autre refuse. Un identifiant qui depend de l'ordre de demarrage n'est
// pas un identifiant.
//
// ET ON NE LIBERE PAS. Un CoUninitialize symetrique parait propre et se
// paie d'une violation d'acces : les mandataires WMI et le cache de
// type-info de ComObj survivent a l'appel et se liberent plus tard, dans un
// appartement qui n'existe plus. Laisser l'appartement ouvert pour la duree
// du processus ne coute rien ici — cette application heberge WebView2, donc
// COM y est de toute facon initialise et le reste.
CoInitializeEx(nil, COINIT_APARTMENTTHREADED);
try
LLocator := CreateOleObject('WbemScripting.SWbemLocator');
LServices := LLocator.ConnectServer('.', 'root\CIMV2');
LSet := LServices.ExecQuery('SELECT UUID FROM Win32_ComputerSystemProduct');
LEnum := IUnknown(LSet._NewEnum) as IEnumVariant;
if LEnum.Next(1, LItem, LFetched) = S_OK then
if not VarIsNull(LItem.UUID) then
Result := UpperCase(Trim(string(LItem.UUID)));
except
// WMI desactive, service arrete, droits refuses : ce n'est pas une panne,
// c'est une source en moins. L'appelant retombe sur MachineGuid.
Result := '';
end;
// Un UUID nul ou tout a FF n'identifie rien : il est partage par toutes les
// machines qui portent le meme defaut de micrologiciel.
if (Result = '00000000-0000-0000-0000-000000000000') or
(Result = 'FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF') then
Result := '';
end;
function MachineGuid: string;
var
LReg: TRegistry;
begin
Result := '';
LReg := TRegistry.Create(KEY_READ or KEY_WOW64_64KEY);
try
LReg.RootKey := HKEY_LOCAL_MACHINE;
if LReg.OpenKeyReadOnly('SOFTWARE\Microsoft\Cryptography') then
Result := LReg.ReadString('MachineGuid');
except
Result := '';
end;
LReg.Free;
end;
function SystemVolumeSerial: string;
var
LRoot: array[0..MAX_PATH] of Char;
LSerial, LMaxLen, LFlags: DWORD;
begin
Result := '';
if GetWindowsDirectory(LRoot, MAX_PATH) = 0 then Exit;
// "C:\" — la racine du volume qui porte Windows.
var LDrive := Copy(string(LRoot), 1, 3);
LSerial := 0;
if GetVolumeInformation(PChar(LDrive), nil, 0, @LSerial, LMaxLen, LFlags, nil, 0) then
Result := IntToHex(LSerial, 8);
end;
function MachineFingerprint: string;
var
LBoard, LVolume, LSeed: string;
begin
if CachedPrint <> '' then
Exit(CachedPrint);
LBoard := BoardUUID;
if LBoard = '' then
LBoard := MachineGuid;
LVolume := SystemVolumeSerial;
// Les deux sources peuvent manquer sur un poste verrouille a l'extreme.
// Une empreinte vide serait la MEME partout : mieux vaut une chaine qui ne
// correspondra a aucune licence qu'une chaine qui les accepterait toutes.
if (LBoard = '') and (LVolume = '') then
Exit('unavailable');
LSeed := 'pmserver/machine/v1|' + LBoard + '|' + LVolume;
CachedPrint := Copy(PM.Crypto.SHA256Hex(LSeed), 1, 16);
Result := CachedPrint;
end;
function MachineFingerprintDisplay: string;
var
LRaw: string;
I: Integer;
begin
LRaw := UpperCase(MachineFingerprint);
Result := '';
for I := 1 to Length(LRaw) do
begin
if (I > 1) and ((I - 1) mod 4 = 0) then
Result := Result + '-';
Result := Result + LRaw[I];
end;
end;
end.