61ec5ff7a1
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>
163 lines
5.5 KiB
ObjectPascal
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.
|