Jak podpisać instalator MSI lub MSIX w Delphi

· Komponenty
Jak podpisać instalator MSI lub MSIX w Delphi

Niepodpisany instalator to pierwsza rzecz, która wygląda źle w oczach klienta. SmartScreen ostrzega o nieznanym wydawcy, monit UAC nie pokazuje żadnej nazwy, a pakiet MSIX w ogóle się nie zainstaluje, ponieważ Windows instaluje wyłącznie pakiet posiadający prawidłowy podpis.

sgcSign 2026.10 dodaje do tego dwa komponenty. TsgcMSISigner podpisuje pliki Windows Installer .msi i .msp, a TsgcAppxSigner podpisuje pakiety .msix i .appx oraz ich pakiety zbiorcze. Oba działają dokładnie tak jak podpisujący Authenticode, którego być może już używasz do plików EXE, dostawca klucza, opcjonalny urząd znacznika czasu, jedno wywołanie.

MSI i MSIX podpisane z poziomu programu w Delphi oraz kontrola wydawcy, która zatrzymuje pakiet, który i tak nigdy by się nie zainstalował. Także na YouTube.

Program

Jeden program konsolowy, który wybiera podpisującego na podstawie rozszerzenia pliku, podpisuje przy użyciu SHA-256, a gdy podany jest urząd znacznika czasu, dodaje znacznik czasu 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.

Oba podpisujące dziedziczą po TsgcAuthenticodeSigner, więc dostawca klucza, skrót, poziom znacznika czasu i opis są ustawiane we wspólnej klasie bazowej dokładnie tak samo jak dla pliku EXE, a metodę SignFile można wywołać zarówno przez typ bazowy, jak i na konkretnej klasie.

Podpisywanie pakietu Windows Installer

Plik .msi nie jest plikiem PE. Jest to plik złożony, niewielki system plików ze strumieniami i magazynami, a skrót sprawdzany przez Windows jest obliczany na podstawie tych strumieni w ściśle określonej kolejności, z pominięciem strumieni podpisu. TsgcMSISigner oblicza ten skrót, buduje podpis PKCS#7 i zapisuje go w strumieniu DigitalSignature pakietu. Akceptowane są SHA-1, SHA-256, SHA-384 i SHA-512, a znacznik czasu RFC 3161 utrzymuje ważność podpisu po wygaśnięciu certyfikatu.

Pakiety poprawek (.msp) są podpisywane w ten sam sposób. Po ustawieniu AppendSignature pakiet, który jest już podpisany, otrzymuje drugi podpis obok pierwszego, tak jak signtool /as dodaje kolejny podpis do pliku EXE.

Podpisywanie pakietu MSIX lub APPX

MSIX to plik ZIP z mapą bloków, a to, co podpisuje Windows, to nie skrót pliku, lecz niewielka struktura skrótów obejmująca jego części. TsgcAppxSigner buduje tę strukturę, podpisuje ją i zapisuje wynik w pakiecie jako AppxSignature.p7x. Jeśli pakiet nie deklaruje jeszcze części podpisu, zostaje ona dodana. Windows akceptuje dla pakietu wyłącznie SHA-256, więc jest to jedyny oferowany skrót, a ponowne podpisanie już podpisanego pakietu zastępuje stary podpis.

Kontrola wydawcy

To błąd, który kosztuje całe popołudnie. Wartość Publisher w manifeście pakietu musi dokładnie odpowiadać podmiotowi certyfikatu podpisującego. Gdy te dwie wartości się różnią, Windows nie zgłasza błędnego podpisu. Przestaje rozpoznawać pakiet i nazywa go formatem pliku, którego nie może zweryfikować, co wygląda jak uszkodzona kompilacja.

TsgcAppxSigner sprawdza to przed podpisaniem i zatrzymuje się z komunikatem wskazującym obie wartości.

> 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.

Porównanie odbywa się według typu i wartości atrybutu, a nie jako ciąg znaków, więc różnice w odstępach i kolejności między manifestem a certyfikatem nie powodują fałszywego alarmu. ValidatePublisher (domyślnie True) wyłącza tę kontrolę, a sgcAppxReadPublisher i sgcAppxDNMatches pozwalają samodzielnie wykonać to samo porównanie, na przykład aby przerwać kompilację, zanim dotrze do etapu podpisywania.

Sprawdzanie wyniku

> 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 w Windows znajduje tego samego podpisującego i znacznik czasu DigiCert w obu plikach, a obliczany przez niego skrót jest tym, który został podpisany. Jedynym zastrzeżeniem jest 0x800B010A, ponieważ łańcuch demonstracyjny kończy się na samopodpisanym korzeniu testowym, który nie jest zainstalowany na maszynie. Uszkodzony skrót lub podpis dawałby zamiast tego 0x80096010. TsgcAuthenticodeVerifier samodzielnie rozpoznaje pakiet instalatora i pakiet MSIX, więc to samo wywołanie, które weryfikuje plik EXE, weryfikuje również te pliki.

Na serwerze i w wierszu poleceń

sgcSign Server podpisuje oba formaty pod adresami /api/v1/sign/msi i /api/v1/sign/appx, a narzędzie wiersza poleceń przyjmuje --format msi i --format appx. Oba mają też trasę działającą wyłącznie na skrócie, dzięki czemu duży instalator nigdy nie przechodzi przez sieć, co jest tematem kolejnego wpisu z tej serii.

Dostępność

TsgcMSISigner i TsgcAppxSigner są dostępne w sgcSign 2026.10 dla Delphi i C++Builder, razem z trasami serwera i formatami wiersza poleceń. Kompilują się i działają na Linuksie, macOS, iOS i Androidzie, a także na Windows. Każda właściwość jest udokumentowana w pomocy online sgcSign.

Masz pytania dotyczące pakietu, który nie chce się podpisać lub zainstalować? Skontaktuj się z nami, a odpowiedź otrzymasz od osób, które napisały ten kod.