Cómo firmar un instalador MSI o MSIX en Delphi

· Componentes
Cómo firmar un instalador MSI o MSIX en Delphi

Un instalador sin firmar es lo primero que un cliente ve salir mal. SmartScreen advierte sobre un publicador desconocido, el aviso de UAC no muestra ningún nombre, y un paquete MSIX no se instalará en absoluto, porque Windows solo instala un paquete que lleve una firma válida.

sgcSign 2026.10 añade dos componentes para esto. TsgcMSISigner firma archivos .msi y .msp de Windows Installer, y TsgcAppxSigner firma paquetes .msix y .appx y sus bundles. Ambos funcionan exactamente igual que el firmante Authenticode que quizá ya use para un EXE: un proveedor de clave, una autoridad de sellado de tiempo opcional, una sola llamada.

Un MSI y un MSIX firmados desde el programa Delphi, y la verificación de publicador deteniendo un paquete que nunca se instalaría. También en YouTube.

El programa

Un programa de consola que elige el firmante según la extensión del archivo, firma con SHA-256 y, cuando se indica una autoridad de sellado de tiempo, añade un sello 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.

Ambos firmantes descienden de TsgcAuthenticodeSigner, así que el proveedor de clave, el digest, el nivel del sello de tiempo y la descripción se establecen en la base común exactamente igual que para un EXE, y SignFile puede llamarse tanto a través del tipo base como en la clase concreta.

Firmar un paquete de Windows Installer

Un .msi no es un archivo PE. Es un archivo compuesto, un pequeño sistema de archivos de streams y storages, y el digest que Windows comprueba se calcula sobre esos streams en un orden preciso, dejando fuera los streams de firma. TsgcMSISigner calcula ese digest, construye la firma PKCS#7 y la escribe en el stream DigitalSignature del paquete. SHA-1, SHA-256, SHA-384 y SHA-512 se aceptan todos, y un sello de tiempo RFC 3161 mantiene la firma válida después de que el certificado caduque.

Los paquetes de parche (.msp) se firman de la misma manera. Con AppendSignature activado, un paquete ya firmado recibe una segunda firma junto a la primera, de la misma forma que signtool /as añade una a un EXE.

Firmar un paquete MSIX o APPX

Un MSIX es un ZIP con un mapa de bloques, y lo que Windows firma no es un hash del archivo, sino una pequeña estructura de digests sobre sus partes. TsgcAppxSigner construye esa estructura, la firma y escribe el resultado en el paquete como AppxSignature.p7x. Si el paquete todavía no declara la parte de firma, se añade. Windows solo acepta SHA-256 para un paquete, así que ese es el único digest ofrecido, y firmar un paquete que ya está firmado reemplaza la firma anterior.

La verificación de publicador

Este es el error que cuesta una tarde entera. El Publisher del manifiesto del paquete tiene que ser exactamente el titular del certificado de firma. Cuando los dos difieren, Windows no informa de una firma inválida. Deja de reconocer el paquete y lo llama un formato de archivo que no puede verificar, lo que parece una compilación dañada.

TsgcAppxSigner lo comprueba antes de firmar y se detiene con un mensaje que nombra ambos valores:

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

La comparación se hace por tipo y valor de atributo, no como una cadena de texto, así que las diferencias de espaciado y de orden entre el manifiesto y el certificado no provocan una falsa alarma. ValidatePublisher (True de forma predeterminada) desactiva la verificación, y sgcAppxReadPublisher y sgcAppxDNMatches le permiten ejecutar usted mismo la misma comparación, por ejemplo para hacer fallar una compilación antes de que llegue al paso de firma.

Comprobar el resultado

> 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 en Windows encuentra el mismo firmante y el sello de tiempo de DigiCert en ambos archivos, y el digest que calcula es el que se firmó. Su única queja es 0x800B010A, porque la cadena de demostración termina en una raíz de prueba autofirmada que no está instalada en la máquina. Un digest o una firma dañados darían 0x80096010 en su lugar. TsgcAuthenticodeVerifier reconoce por sí mismo un paquete de instalador y un paquete MSIX, así que la misma llamada que verifica un EXE también verifica estos.

En el servidor y en la línea de comandos

El sgcSign Server firma ambos formatos en /api/v1/sign/msi y /api/v1/sign/appx, y la herramienta de línea de comandos admite --format msi y --format appx. Ambos tienen además una ruta solo de hash, para que un instalador grande nunca cruce la red, tema de la próxima entrada de esta serie.

Disponibilidad

TsgcMSISigner y TsgcAppxSigner se incluyen en sgcSign 2026.10 para Delphi y C++Builder, junto con las rutas del servidor y los formatos de línea de comandos. Se compilan y funcionan en Linux, macOS, iOS y Android además de en Windows. Cada propiedad está documentada en la ayuda en línea de sgcSign.

¿Preguntas sobre un paquete que no se firma o no se instala? Póngase en contacto, y recibirá una respuesta de las personas que escribieron el código.