Onderteken een groot installatieprogramma zonder het te uploaden

· Componenten
Onderteken een groot installatieprogramma zonder het te uploaden

Een ondertekeningsserver bewaart de privésleutel op één plek, en dat is precies het nut ervan. De gebruikelijke prijs is het bestand zelf: de build agent uploadt het installatieprogramma, de server ondertekent het en stuurt het geheel terug. Voor een installatieprogramma van een paar gigabytes betekent dat, het installatieprogramma gaat twee keer over het netwerk om een handtekening van een paar kilobytes toe te voegen.

Dat hoeft niet zo te zijn. Wat Authenticode ondertekent is nooit het bestand zelf, het is een digest van het bestand, en die digest kan berekend worden op de plek waar het bestand zich al bevindt. Vanaf sgcSign 2026.10 ondertekent de sgcSign Server een Windows Installer pakket en een MSIX of APPX pakket op basis van alleen die digest, zoals al eerder gebeurde voor een EXE.

Een installatieprogramma van 1 GB twee keer ondertekend via de sgcSign Server, één keer geüpload en één keer via de hash, waarbij elke byte op de lijn is geteld. Ook op YouTube.

Wat er precies ondertekend wordt

Een Authenticode handtekening dekt een digest, en elk formaat definieert zijn eigen digest. Voor een EXE is dat de PE image hash, waarbij de checksum en de certificaattabel worden weggelaten. Voor een MSI is het een digest over de streams van het compound file, in een vaste volgorde. Voor een MSIX is het een klein blok SHA-256 digests over de onderdelen van het pakket. De server heeft alleen die waarde nodig, niets meer: hij ondertekent de digest met de sleutel die hij bezit, en de client schrijft de handtekening in het bestand.

Vanaf de command line

De command line tool kiest standaard de hash route voor --format msi en --format appx, zoals dat al gebeurde voor --format authenticode, en --upload stuurt in plaats daarvan het hele bestand. De demo ondertekent hetzelfde pakket van 1 GB op beide manieren tegen een server op dezelfde machine, via een kleine relay die elke byte telt die de tool verstuurt en ontvangt. Het pakket bevat 1 GiB aan willekeurige data, zodat onderweg niets ervan gecomprimeerd kan worden.

> sgcsign sign --format msi --upload --server http://127.0.0.1:18481 --apikey $k `
    --provider demo --tsa http://timestamp.digicert.com --verbose `
    --out BigApp-signed-upload.msi BigApp.msi
Signed: BigApp-signed-upload.msi | Signer: O=eSeGeCe Demo, CN=sgcSign Demo Code Signing | Duration: 3015 ms

> Get-Content .\relay.log -Tail 1
RUN 4: connections=1 bytes_up(client->server)=1075356521 bytes_down(server->client)=1075364108 wall=14.087s

> sgcsign sign --format msi --server http://127.0.0.1:18481 --apikey $k `
    --provider demo --tsa http://timestamp.digicert.com --verbose `
    --out BigApp-signed-hash.msi BigApp.msi
[prehash] alg=sha256 hash=9151f2ee34b18f80a20d4b2cec515ac344066bbed846c7efbc98d7c5e5a0cddd

> Get-Content .\relay.log -Tail 1
RUN 5: connections=1 bytes_up(client->server)=477 bytes_down(server->client)=11044 wall=0.178s

Beide bestanden komen ondertekend terug en dragen dezelfde digest: signtool verify /v toont bij beide Hash of file (sha256): 9151F2EE…5A0CDDD, de waarde uit de regel [prehash], samen met de demo ondertekenaar en de DigiCert tijdstempel.

De cijfers

Upload routeHash route
Pakket1.075.355.648 bytes1.075.355.648 bytes
Verzonden naar de server1.075.356.521 bytes477 bytes
Ontvangen van de server1.075.364.108 bytes11.044 bytes
Tijd op de lijn14,1 s0,18 s
Volledige opdracht16,3 s4,8 s

Dit liep over loopback, waar het verplaatsen van bytes vrijwel niets kost, en toch besteedde de upload route daar nog 14 seconden aan. Over een echte verbinding is de overdracht de hele kostenpost: bij 100 Mbit/s duurt de 2,15 GB van de upload route bijna drie minuten, terwijl de hash route nog steeds maar 477 bytes verstuurt. Wat overblijft van de 4,8 seconden van de hash route is het voorbereiden en hashen van het pakket van 1 GB op de build agent, werk dat de upload route ook doet, maar dan op de server.

Hetzelfde in Delphi

In een Delphi build tool bestaat de flow uit vier stappen: het pakket voorbereiden, het voorbereide pakket hashen, de hash versturen en de handtekening die terugkomt inbedden. De HTTP aanroep is welke client uw project ook al gebruikt. Deze gebruikt de RTL THTTPClient en System.JSON.

program HashSignMSI;

{$APPTYPE CONSOLE}

uses
  System.SysUtils, System.Classes, System.JSON, System.Net.HttpClient,
  sgcSign_MSI, sgcSign_Base64, sgcSign_Authenticode;

const
  CS_SERVER = 'http://127.0.0.1:18480';

var
  vPrepared, vSigned, vHex, vBody: string;
  vHash, vPKCS7: TBytes;
  vI: Integer;
  oHTTP: THTTPClient;
  oFields: TStringList;
  oResponse: IHTTPResponse;
  oJSON: TJSONValue;
begin
  try
    vPrepared := ChangeFileExt(ParamStr(1), '.prepared.msi');
    vSigned := ChangeFileExt(ParamStr(1), '.signed.msi');
    // 1. Prepare: this is the package the digest covers
    sgcMSIPrepareForSigning(ParamStr(1), vPrepared);
    // 2. Hash the PREPARED package on this machine
    vHash := sgcMSIComputeHash(vPrepared, ahSHA256);
    vHex := '';
    for vI := 0 to Length(vHash) - 1 do
      vHex := vHex + LowerCase(IntToHex(vHash[vI], 2));
    Writeln('SHA-256 : ', vHex);
    // 3. Only the digest leaves the machine
    oHTTP := THTTPClient.Create;
    oFields := TStringList.Create;
    try
      oHTTP.CustomHeaders['X-API-Key'] := GetEnvironmentVariable('SGCSIGN_APIKEY');
      oFields.Add('hash=' + vHex);
      oFields.Add('alg=sha256');
      oFields.Add('provider=demo');
      oFields.Add('tsa_url=http://timestamp.digicert.com');
      oFields.Add('level=t');
      oResponse := oHTTP.Post(CS_SERVER + '/api/v1/sign/msi/hash', oFields);
      vBody := oResponse.ContentAsString(TEncoding.UTF8);
      if oResponse.StatusCode <> 200 then
        raise Exception.CreateFmt('HTTP %d: %s', [oResponse.StatusCode, vBody]);
    finally
      oFields.Free;
      oHTTP.Free;
    end;
    // 4. Base64-decode the PKCS#7 and embed it into the SAME prepared package
    oJSON := TJSONObject.ParseJSONValue(vBody);
    try
      vPKCS7 := TsgcBase64.Decode(oJSON.GetValue<string>('signature'));
    finally
      oJSON.Free;
    end;
    sgcMSIEmbedSignature(vPrepared, vSigned, vPKCS7);
    Writeln('PKCS#7  : ', Length(vPKCS7), ' bytes');
    Writeln('Signed  : ', vSigned);
  except
    on E: Exception do
    begin
      Writeln(ErrOutput, 'error: ', E.Message);
      ExitCode := 1;
    end;
  end;
end.

Van dezelfde server kwam een PKCS#7 van 7.941 bytes terug, en signtool leest het resultaat precies zoals bij de twee bestanden hierboven.

Eerst voorbereiden, dan hashen

Een installatiepakket wordt ondertekend in de vorm waarin het uiteindelijk wordt uitgeleverd, dus moet het in die vorm gebracht worden voordat het gehasht wordt. sgcMSIPrepareForSigning en sgcAppxPrepareForSigning doen dat, en de regel is eenvoudig: hash wat prepare heeft opgeleverd, en bed de handtekening in datzelfde voorbereide pakket in, niet in het origineel. Hasht u in plaats daarvan het origineel, dan dekt de handtekening bytes die niet meer bestaan.

MSIX: controleer de uitgever zelf

Op de hash route ziet geen van beide partijen beide helften van de ene controle waar Windows strikt op let. De server heeft het certificaat maar ziet nooit het manifest, en de client heeft het manifest maar ziet nooit het certificaat. Daarom bevat elk hash antwoord signer_subject, het subject van het certificaat waarmee ondertekend is. Vergelijk dat met de Publisher van het pakket, met sgcAppxReadPublisher en sgcAppxDNMatches, voordat u de handtekening inbedt. De command line tool doet dit automatisch en weigert het pakket te schrijven wanneer ze verschillen.

Wanneer toch uploaden

De routes voor het volledige bestand bestaan nog steeds. Gebruik ze wanneer de client de digest zelf niet kan berekenen, of wanneer een handtekening goedkeuring nodig heeft, omdat de goedkeuringsworkflow alleen werkt met uploads van het volledige bestand. --upload zet de command line tool voor die ene run terug op die route.

Beschikbaarheid

De hash routes voor installatiepakketten, /api/v1/sign/msi/hash en /api/v1/sign/appx/hash, de hash first standaardinstelling van de command line tool en de prepare, hash en embed functies zijn beschikbaar vanaf sgcSign 2026.10 voor Delphi en C++Builder. De routes zijn gedocumenteerd in de sgcSign online help.

Ondertekent u grote pakketten vanuit een build farm? Neem contact op, en u krijgt antwoord van de mensen die de code hebben geschreven.