Come firmare un installer MSI o MSIX in Delphi

· Componenti
Come firmare un installer MSI o MSIX in Delphi

Un installer non firmato è la prima cosa che un cliente vede andare storta. SmartScreen avverte di un editore sconosciuto, il prompt UAC non mostra alcun nome, e un pacchetto MSIX non si installa affatto, perché Windows installa solo un pacchetto che porti una firma valida.

sgcSign 2026.10 aggiunge due componenti per questo. TsgcMSISigner firma i file .msi e .msp di Windows Installer, e TsgcAppxSigner firma i pacchetti .msix e .appx e i loro bundle. Entrambi funzionano esattamente come il firmatario Authenticode che forse già usi per un EXE: un provider di chiave, un'autorità di marcatura temporale opzionale, un'unica chiamata.

Un MSI e un MSIX firmati dal programma Delphi, e il controllo dell'editore che ferma un pacchetto che non si sarebbe mai installato. Anche su YouTube.

Il programma

Un programma console che sceglie il firmatario in base all'estensione del file, firma con SHA-256 e, quando viene indicata un'autorità di marcatura temporale, aggiunge una marca RFC 3161.

program SignInstaller;

{$APPTYPE CONSOLE}

uses
  SysUtils, StrUtils, sgcSign_Authenticode, sgcSign_MSI, sgcSign_APPX,
  sgcSign_KeyProvider_PFX, sgcSign_TSA;

var
  PFX: TsgcPFXKeyProvider;
  TSA: TsgcTSAClient;
  Signer: TsgcAuthenticodeSigner;
begin
  if ParamCount < 4 then
  begin
    WriteLn('usage: SignInstaller <input.msi|input.msix> <output> ' +
      '<file.pfx> <password> [tsa-url]');
    Halt(1);
  end;
  if MatchText(ExtractFileExt(ParamStr(1)), ['.msi', '.msp']) then
    Signer := TsgcMSISigner.Create(nil)
  else
    Signer := TsgcAppxSigner.Create(nil); // .msix .appx and bundles
  PFX := TsgcPFXKeyProvider.Create(nil);
  TSA := TsgcTSAClient.Create(nil);
  try
    try
      PFX.LoadFromFile(ParamStr(3), ParamStr(4));
      Signer.KeyProvider := PFX;
      Signer.Hash := ahSHA256;
      Signer.Level := alBES;
      if ParamCount > 4 then
      begin
        TSA.URL := ParamStr(5);
        Signer.TSAClient := TSA;
        Signer.Level := alT;
      end;
      if Signer is TsgcAppxSigner then
      begin
        WriteLn('Publisher  : ', sgcAppxReadPublisher(ParamStr(1)));
        WriteLn('Certificate: ', PFX.Certificate.SubjectRFC2253);
        TsgcAppxSigner(Signer).SignFile(ParamStr(1), ParamStr(2));
      end
      else
        TsgcMSISigner(Signer).SignFile(ParamStr(1), ParamStr(2));
      WriteLn('Signed: ', ParamStr(2));
    except
      on E: Exception do
      begin
        WriteLn('Error: ', E.Message);
        ExitCode := 1;
      end;
    end;
  finally
    Signer.Free;
    TSA.Free;
    PFX.Free;
  end;
end.

Entrambi i firmatari derivano da TsgcAuthenticodeSigner, quindi il provider di chiave, il digest, il livello della marca temporale e la descrizione vengono impostati sulla base comune esattamente come per un EXE, e SignFile può essere chiamato sia tramite il tipo base sia sulla classe concreta.

Firmare un pacchetto Windows Installer

Un .msi non è un file PE. È un file composto, un piccolo file system di stream e storage, e il digest che Windows verifica viene calcolato su questi stream in un ordine preciso, escludendo gli stream di firma. TsgcMSISigner calcola questo digest, costruisce la firma PKCS#7 e la scrive nello stream DigitalSignature del pacchetto. SHA-1, SHA-256, SHA-384 e SHA-512 sono tutti accettati, e una marca temporale RFC 3161 mantiene la firma valida dopo la scadenza del certificato.

I pacchetti patch (.msp) vengono firmati allo stesso modo. Con AppendSignature attivato, un pacchetto già firmato riceve una seconda firma accanto alla prima, così come signtool /as ne aggiunge una a un EXE.

Firmare un pacchetto MSIX o APPX

Un MSIX è uno ZIP con una mappa dei blocchi, e ciò che Windows firma non è un hash del file, ma una piccola struttura di digest sulle sue parti. TsgcAppxSigner costruisce questa struttura, la firma e scrive il risultato nel pacchetto come AppxSignature.p7x. Se il pacchetto non dichiara ancora la parte di firma, viene aggiunta. Windows accetta solo SHA-256 per un pacchetto, quindi è l'unico digest offerto, e firmare un pacchetto già firmato sostituisce la firma precedente.

Il controllo dell'editore

Questo è l'errore che costa un intero pomeriggio. Il Publisher nel manifesto del pacchetto deve corrispondere esattamente al soggetto del certificato di firma. Quando i due differiscono, Windows non segnala una firma non valida. Smette di riconoscere il pacchetto e lo definisce un formato di file che non può verificare, il che sembra una build corrotta.

TsgcAppxSigner lo controlla prima di firmare e si ferma con un messaggio che indica entrambi i valori:

> SignInstaller.exe DemoApp-wrongpublisher.msix out.msix ..\certs\demo.pfx demo
Publisher  : CN=Someone Else
Certificate: CN=sgcSign Demo Code Signing,O=eSeGeCe Demo
Error: APPX: the signing certificate subject "O=eSeGeCe Demo, CN=sgcSign Demo Code Signing"
does not match the Publisher declared in AppxManifest.xml ("CN=Someone Else"). Windows
requires them to be identical and refuses a package where they differ; signtool rejects
the same combination with 0x8007000B. Sign with a certificate whose subject matches the
manifest, or rebuild the package with the Publisher set to the certificate subject.

Il confronto viene fatto per tipo e valore dell'attributo, non come stringa, quindi le differenze di spaziatura e di ordine tra il manifesto e il certificato non causano un falso allarme. ValidatePublisher (True per impostazione predefinita) disattiva il controllo, e sgcAppxReadPublisher e sgcAppxDNMatches ti permettono di eseguire tu stesso lo stesso confronto, per esempio per far fallire una build prima che raggiunga la fase di firma.

Verificare il risultato

> SignInstaller.exe DemoApp.msi DemoApp-signed.msi ..\certs\demo.pfx demo http://timestamp.digicert.com
Signed: DemoApp-signed.msi

> SignInstaller.exe DemoApp.msix DemoApp-signed.msix ..\certs\demo.pfx demo http://timestamp.digicert.com
Publisher  : CN=sgcSign Demo Code Signing, O=eSeGeCe Demo
Certificate: CN=sgcSign Demo Code Signing,O=eSeGeCe Demo
Signed: DemoApp-signed.msix

> Get-AuthenticodeSignature DemoApp-signed.msi, DemoApp-signed.msix |
    Format-List Path, @{n='Signer';e={$_.SignerCertificate.Subject}},
                      @{n='TSA';e={$_.TimeStamperCertificate.Subject}}

Path   : DemoApp-signed.msi
Signer : CN=sgcSign Demo Code Signing, O=eSeGeCe Demo
TSA    : CN=DigiCert SHA256 RSA4096 Timestamp Responder 2026 1, O="DigiCert, Inc.", C=US

Path   : DemoApp-signed.msix
Signer : CN=sgcSign Demo Code Signing, O=eSeGeCe Demo
TSA    : CN=DigiCert SHA256 RSA4096 Timestamp Responder 2026 1, O="DigiCert, Inc.", C=US

signtool verify /pa /v su Windows trova lo stesso firmatario e la marca temporale DigiCert su entrambi i file, e il digest che calcola è quello che è stato firmato. La sua unica lamentela è 0x800B010A, perché la catena demo termina in una radice di test autofirmata che non è installata sulla macchina. Un digest o una firma danneggiati darebbero invece 0x80096010. TsgcAuthenticodeVerifier riconosce da solo un pacchetto installer e un pacchetto MSIX, quindi la stessa chiamata che verifica un EXE verifica anche questi.

Sul server e nella riga di comando

Il sgcSign Server firma entrambi i formati su /api/v1/sign/msi e /api/v1/sign/appx, e lo strumento a riga di comando accetta --format msi e --format appx. Entrambi hanno anche una rotta solo hash, così che un installer di grandi dimensioni non attraversi mai la rete, argomento del prossimo articolo di questa serie.

Disponibilità

TsgcMSISigner e TsgcAppxSigner sono disponibili in sgcSign 2026.10 per Delphi e C++Builder, insieme alle rotte del server e ai formati della riga di comando. Si compilano ed eseguono su Linux, macOS, iOS e Android oltre che su Windows. Ogni proprietà è documentata nella guida online di sgcSign.

Domande su un pacchetto che non si firma o non si installa? Mettiti in contatto, e riceverai una risposta da chi ha scritto il codice.