Assine um EXE do Windows a partir do Linux com Delphi

· Componentes
Assine um EXE do Windows a partir do Linux com Delphi

Assinar um programa Windows sempre significou precisar de uma máquina Windows. O signtool só funciona no Windows, então um pipeline de build que compila no Linux ainda precisa de um host Windows em algum lugar, só para colocar uma assinatura no arquivo no final.

A partir do sgcSign 2026.10 esse host é opcional. O formato Authenticode no sgcSign, que faz o hash do arquivo PE e monta a assinatura, agora é código Delphi em vez de uma chamada às APIs de assinatura do Windows. No Windows a operação de chave ainda passa pelo CNG, e em qualquer outro lugar ela roda na própria criptografia do sgcSign, de modo que os componentes que assinam um EXE no Windows compilam e rodam no Linux 64 bits, e o Windows aceita o arquivo que eles produzem como qualquer outro arquivo assinado. Este post mostra um pequeno programa de console que faz isso, como compilá-lo para Linux e como verificar o resultado.

O programa Delphi assinando um EXE no Ubuntu, e depois o mesmo arquivo verificado no Windows. Também no YouTube.

Por que assinar no Linux, afinal

O programa

Um programa de console que recebe o arquivo a assinar, o arquivo de saída, um PFX com sua senha e, opcionalmente, uma autoridade de carimbo de data/hora. É o mesmo código no Windows e no 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.

O TsgcPFXKeyProvider guarda o certificado e a chave privada. O TsgcAuthenticodeSigner faz o hash do arquivo PE da mesma forma que o Windows faz, monta a assinatura PKCS#7 e a grava na tabela de certificados do arquivo. Com um TsgcTSAClient conectado, ele também busca um carimbo de data/hora RFC 3161, para que a assinatura continue válida depois que o certificado expirar.

Compile para Linux 64 bits

Na janela Projects, adicione Linux 64-bit como plataforma de destino, com um SDK Linux configurado no SDK Manager, e então compile. Os pacotes de runtime do sgcSign são compilados para Linux64 a partir do Delphi 10.3. No Linux o PFX é interpretado em Pascal puro, já que não existe um repositório de certificados do Windows para importá-lo, e a operação RSA ou ECDSA roda na própria criptografia do sgcSign. O formato de assinatura ao redor dela é montado pelo mesmo código que roda no Windows.

Execute e verifique o 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 verificar sem o Windows, o osslsigncode verify recalcula o digest do arquivo e verifica a assinatura sobre ele: Signature verification: ok.

Depois copie o arquivo para o Windows. O PowerShell e o signtool verify /pa /v leem a mesma assinatura: o hash do arquivo que o Windows calcula corresponde ao que foi assinado, o assinante é o certificado de demonstração, e o carimbo de data/hora da DigiCert é verificado. O arquivo continua executando.

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

A única reclamação que o Windows faz é sobre o certificado de demonstração, não sobre a assinatura. A cadeia de demonstração termina em uma raiz de teste autoassinada que não está instalada na máquina, então o Windows não consegue vinculá-la a uma raiz confiável. O digest, a assinatura e o carimbo de data/hora são todos verificados.

Os mesmos bytes no Linux e no Windows

Assine o mesmo Hello.exe com o mesmo PFX e sem carimbo de data/hora, uma vez com a compilação Linux e outra com a compilação Windows do programa acima, e compare os dois arquivos:

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

Eles são idênticos, byte a byte. Uma assinatura RSA desse tipo é determinística, e o assinante deixa a hora de assinatura de fora a menos que você a solicite, de modo que o arquivo depende apenas da chave e da entrada, não da máquina que assinou. O carimbo de data/hora é a única parte que muda de execução para execução, porque a autoridade de carimbo registra o momento em que é solicitada.

De onde vem a chave

No Linux a chave pode vir de um arquivo PFX ou PEM, de um token PKCS#11 ou HSM, ou de um serviço de assinatura na nuvem: Azure Trusted Signing, AWS KMS, Google Cloud KMS, HashiCorp Vault ou um serviço de assinatura remota CSC. O provedor do repositório de certificados do Windows é o único que continua exclusivo do Windows, pelo motivo óbvio.

Não só EXE

O mesmo vale para todos os componentes de assinatura de código do sgcSign: TsgcAuthenticodeSigner para EXE e DLL, TsgcCatalogSigner para catálogos .cat, TsgcMSISigner para arquivos .msi e .msp do Windows Installer, TsgcAppxSigner para pacotes .msix e .appx, TsgcPowerShellSigner para scripts do PowerShell, e TsgcAuthenticodeVerifier para verificar qualquer um deles. Todos eles compilam e rodam em Linux, macOS, iOS e Android.

No servidor e na linha de comando

O mesmo vale além da biblioteca. Um sgcSign Server compilado para Linux assina e verifica solicitações de Authenticode, PowerShell, catálogo, MSI e MSIX exatamente como faz no Windows, e a ferramenta de linha de comando sgcsign as envia a partir de qualquer plataforma. Uma rota continua exclusiva do Windows na ferramenta de linha de comando Delphi: fazer o hash de um EXE ou de uma DLL localmente e enviar apenas o hash. A ferramenta de linha de comando .NET segue essa rota em qualquer plataforma.

Disponibilidade

A assinatura de código no Linux, macOS, iOS e Android chega no sgcSign 2026.10 para Delphi. Nada muda para um programa Windows: os mesmos componentes, as mesmas propriedades e a mesma saída. Cada propriedade está documentada na ajuda on-line do sgcSign.

Perguntas, ou um pipeline que você quer tirar do Windows? Fale conosco, e você receberá uma resposta de quem escreveu o código.