Firmar un EXE de Windows desde Linux con Delphi

· Componentes
Firmar un EXE de Windows desde Linux con Delphi

Firmar un programa Windows siempre ha significado necesitar una máquina Windows. signtool solo funciona en Windows, así que un pipeline de compilación que se ejecuta en Linux todavía necesita un host Windows en algún punto, solo para poner una firma en el archivo al final.

Desde sgcSign 2026.10 ese host es opcional. El formato Authenticode en sgcSign, que calcula el hash del archivo PE y construye la firma, es ahora código Delphi en lugar de una llamada a las API de firma de Windows. En Windows la operación de clave sigue pasando por CNG, y en cualquier otro lugar se ejecuta en la propia criptografía de sgcSign, de modo que los componentes que firman un EXE en Windows compilan y se ejecutan en Linux de 64 bits, y Windows acepta el archivo que producen como cualquier otro archivo firmado. Este artículo muestra un pequeño programa de consola que lo hace, cómo compilarlo para Linux y cómo comprobar el resultado.

El programa Delphi firmando un EXE en Ubuntu, y luego el mismo archivo comprobado en Windows. También en YouTube.

Por qué firmar en Linux

El programa

Un programa de consola que recibe el archivo a firmar, el archivo de salida, un PFX con su contraseña y, opcionalmente, una autoridad de sellado de tiempo. Es el mismo código en Windows y en Linux.

program SignExe;

{$APPTYPE CONSOLE}

uses
  SysUtils, sgcSign_Authenticode, sgcSign_KeyProvider_PFX, sgcSign_TSA;

var
  PFX: TsgcPFXKeyProvider;
  TSA: TsgcTSAClient;
  Signer: TsgcAuthenticodeSigner;
begin
  if ParamCount < 4 then
  begin
    Writeln('usage: SignExe <input.exe> <output.exe> <file.pfx> <password> [tsa-url]');
    Halt(1);
  end;
  PFX := TsgcPFXKeyProvider.Create(nil);
  TSA := TsgcTSAClient.Create(nil);
  Signer := TsgcAuthenticodeSigner.Create(nil);
  try
    try
      PFX.LoadFromFile(ParamStr(3), ParamStr(4));
      Signer.KeyProvider := PFX;
      Signer.Hash := ahSHA256;
      Signer.Level := alBES;
      Signer.Description := 'sgcSign demo';
      if ParamCount >= 5 then
      begin
        TSA.URL := ParamStr(5);
        Signer.TSAClient := TSA;
        Signer.Level := alT;
      end;
      Signer.SignFile(ParamStr(1), ParamStr(2));
      Writeln('Signer: ', PFX.Certificate.Subject);
      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.

TsgcPFXKeyProvider guarda el certificado y la clave privada. TsgcAuthenticodeSigner calcula el hash del archivo PE de la misma forma que lo hace Windows, construye la firma PKCS#7 y la escribe en la tabla de certificados del archivo. Con un TsgcTSAClient conectado, también obtiene una marca de tiempo RFC 3161, para que la firma siga siendo válida después de que el certificado caduque.

Compilarlo para Linux de 64 bits

En la ventana Projects, añade Linux 64-bit como plataforma de destino, con un SDK de Linux configurado en el SDK Manager, y luego compila. Los paquetes de runtime de sgcSign se compilan para Linux64 desde Delphi 10.3 en adelante. En Linux el PFX se analiza en Pascal puro, ya que no existe un almacén de certificados de Windows en el que importarlo, y la operación RSA o ECDSA se ejecuta en la propia criptografía de sgcSign. El formato de firma que lo envuelve lo construye el mismo código que se ejecuta en Windows.

Ejecutarlo y comprobar el resultado

$ file Hello.exe
PE32+ executable (console) x86-64, for MS Windows

$ ./SignExe Hello.exe Hello-signed-linux.exe ../certs/demo.pfx demo http://timestamp.digicert.com
Signer: O=eSeGeCe Demo, CN=sgcSign Demo Code Signing
Signed: Hello-signed-linux.exe

Para comprobarlo sin Windows, osslsigncode verify vuelve a calcular el digest del archivo y verifica la firma sobre él: Signature verification: ok.

Después copia el archivo a Windows. PowerShell y signtool verify /pa /v leen la misma firma: el hash del archivo que calcula Windows coincide con el que se firmó, el firmante es el certificado de demostración, y la marca de tiempo de DigiCert se verifica. El archivo se sigue ejecutando.

PS> Get-AuthenticodeSignature .\Hello-signed-linux.exe | Format-List SignatureType,
      @{n='Signer';e={$_.SignerCertificate.Subject}},
      @{n='TimeStamper';e={$_.TimeStamperCertificate.Subject}}

SignatureType : Authenticode
Signer        : CN=sgcSign Demo Code Signing, O=eSeGeCe Demo
TimeStamper   : CN=DigiCert SHA256 RSA4096 Timestamp Responder 2026 1, O="DigiCert, Inc.", C=US

La única queja que pone Windows es sobre el certificado de demostración, no sobre la firma. La cadena de demostración termina en una raíz de prueba autofirmada que no está instalada en la máquina, así que Windows no puede vincularla a una raíz de confianza. El digest, la firma y la marca de tiempo se verifican todos.

Los mismos bytes en Linux y en Windows

Firma el mismo Hello.exe con el mismo PFX y sin marca de tiempo, una vez con la compilación de Linux y otra con la compilación de Windows del programa anterior, y compara los dos archivos:

Linux    82344 bytes  SHA-256 6FD63BB01A8E911AB099431BC76F3BFA4EF557082BFB8BC18CCBFA97A9E8079D
Windows  82344 bytes  SHA-256 6FD63BB01A8E911AB099431BC76F3BFA4EF557082BFB8BC18CCBFA97A9E8079D

Son idénticos, byte a byte. Una firma RSA de este tipo es determinista, y el firmante omite la hora de firma a menos que se solicite, así que el archivo depende solo de la clave y de la entrada, no de la máquina que firmó. La marca de tiempo es la única parte que cambia de una ejecución a otra, porque la autoridad de sellado registra el momento en que se le pide.

De dónde viene la clave

En Linux la clave puede venir de un archivo PFX o PEM, de un token PKCS#11 o HSM, o de un servicio de firma en la nube: Azure Trusted Signing, AWS KMS, Google Cloud KMS, HashiCorp Vault o un servicio de firma remota CSC. El proveedor del almacén de certificados de Windows es el único que se queda exclusivo de Windows, por el motivo obvio.

No solo EXE

Lo mismo vale para todos los componentes de firma de código de sgcSign: TsgcAuthenticodeSigner para EXE y DLL, TsgcCatalogSigner para catálogos .cat, TsgcMSISigner para archivos .msi y .msp de Windows Installer, TsgcAppxSigner para paquetes .msix y .appx, TsgcPowerShellSigner para scripts de PowerShell, y TsgcAuthenticodeVerifier para comprobar cualquiera de ellos. Todos compilan y se ejecutan en Linux, macOS, iOS y Android.

En el servidor y en la línea de comandos

Lo mismo vale más allá de la biblioteca. Un sgcSign Server compilado para Linux firma y verifica solicitudes de Authenticode, PowerShell, catálogo, MSI y MSIX exactamente igual que en Windows, y la herramienta de línea de comandos sgcsign las envía desde cualquier plataforma. Una vía sigue siendo exclusiva de Windows en la herramienta de línea de comandos de Delphi: calcular el hash de un EXE o de una DLL localmente y enviar solo el hash. La herramienta de línea de comandos .NET sigue esa vía en cualquier plataforma.

Disponibilidad

La firma de código en Linux, macOS, iOS y Android llega en sgcSign 2026.10 para Delphi. Nada cambia para un programa Windows: los mismos componentes, las mismas propiedades y la misma salida. Cada propiedad está documentada en la ayuda en línea de sgcSign.

¿Preguntas, o un pipeline que quieres sacar de Windows? Ponte en contacto, y recibirás una respuesta de las personas que escribieron el código.