Podpisywanie pliku EXE dla Windows z Linuksa w Delphi

· Komponenty
Podpisywanie pliku EXE dla Windows z Linuksa w Delphi

Podpisanie programu dla Windows zawsze oznaczało konieczność posiadania maszyny z Windows. signtool działa wyłącznie na Windows, więc potok budowania kompilujący na Linuksie nadal potrzebował gdzieś hosta z Windows, tylko po to, by na końcu nałożyć podpis na plik.

Od sgcSign 2026.10 taki host jest opcjonalny. Format Authenticode w sgcSign, czyli haszowanie pliku PE i budowanie podpisu, to teraz kod Delphi, a nie wywołanie API podpisującego Windows. Na Windows operacja na kluczu nadal przechodzi przez CNG, a wszędzie indziej działa we własnej kryptografii sgcSign, dzięki czemu komponenty podpisujące plik EXE na Windows budują się i działają również na Linuksie 64 bitowym, a Windows akceptuje wytworzony przez nie plik jak każdy inny podpisany plik. Ten artykuł pokazuje krótki program konsolowy, który to robi, sposób zbudowania go dla Linuksa oraz sposób sprawdzenia wyniku.

Program Delphi podpisujący plik EXE na Ubuntu, a następnie ten sam plik sprawdzany na Windows. Także na YouTube.

Dlaczego w ogóle podpisywać na Linuksie

Program

Program konsolowy przyjmujący plik do podpisania, plik wynikowy, PFX z hasłem oraz opcjonalnie urząd znacznika czasu. To ten sam kod na Windows i na Linuksie.

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 przechowuje certyfikat i klucz prywatny. TsgcAuthenticodeSigner haszuje plik PE tak, jak robi to Windows, buduje podpis PKCS#7 i zapisuje go w tabeli certyfikatów pliku. Po podłączeniu TsgcTSAClient pobiera także znacznik czasu RFC 3161, dzięki czemu podpis pozostaje ważny po wygaśnięciu certyfikatu.

Budowanie dla Linuksa 64 bitowego

W oknie Projects dodaj Linux 64-bit jako platformę docelową, ze skonfigurowanym SDK dla Linuksa w SDK Manager, a następnie zbuduj projekt. Pakiety wykonawcze sgcSign są budowane dla Linux64 od Delphi 10.3 wzwyż. Na Linuksie PFX jest parsowany w czystym Pascalu, ponieważ nie ma magazynu certyfikatów Windows, do którego można by go zaimportować, a operacja RSA lub ECDSA działa we własnej kryptografii sgcSign. Format podpisu wokół niej jest budowany przez ten sam kod, który działa na Windows.

Uruchomienie i sprawdzenie wyniku

$ 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

Aby sprawdzić to bez Windows, osslsigncode verify ponownie oblicza skrót pliku i weryfikuje podpis nad nim: Signature verification: ok.

Następnie skopiuj plik na Windows. PowerShell i signtool verify /pa /v odczytują ten sam podpis: skrót pliku obliczony przez Windows zgadza się ze skrótem, który został podpisany, podpisującym jest certyfikat demonstracyjny, a znacznik czasu DigiCert weryfikuje się poprawnie. Plik nadal się uruchamia.

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

Jedyne, do czego Windows ma zastrzeżenia, to certyfikat demonstracyjny, a nie sam podpis. Łańcuch demonstracyjny kończy się na samopodpisanym korzeniu testowym, który nie jest zainstalowany na maszynie, więc Windows nie może powiązać go z korzeniem, któremu ufa. Skrót, podpis i znacznik czasu weryfikują się poprawnie.

Te same bajty na Linuksie i na Windows

Podpisz ten sam plik Hello.exe tym samym PFX i bez znacznika czasu, raz wersją zbudowaną na Linuksie i raz wersją zbudowaną na Windows powyższego programu, a następnie porównaj oba pliki:

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

Są identyczne co do bajtu. Podpis RSA tego rodzaju jest deterministyczny, a podpisujący pomija czas podpisania, chyba że się o niego poprosi, więc plik zależy wyłącznie od klucza i danych wejściowych, a nie od maszyny, która go podpisała. Znacznik czasu to jedyny element, który różni się między uruchomieniami, ponieważ urząd znacznika czasu odciska moment, w którym o niego poproszono.

Skąd pochodzi klucz

Na Linuksie klucz może pochodzić z pliku PFX lub PEM, z tokena PKCS#11 lub HSM, albo z chmurowej usługi podpisującej: Azure Trusted Signing, AWS KMS, Google Cloud KMS, HashiCorp Vault lub zdalnej usługi podpisującej CSC. Dostawca magazynu certyfikatów Windows to jedyny, który pozostaje wyłącznie dla Windows, z oczywistego powodu.

Nie tylko EXE

To samo dotyczy każdego komponentu do podpisywania kodu w sgcSign: TsgcAuthenticodeSigner dla EXE i DLL, TsgcCatalogSigner dla katalogów .cat, TsgcMSISigner dla plików Windows Installer .msi i .msp, TsgcAppxSigner dla pakietów .msix i .appx, TsgcPowerShellSigner dla skryptów PowerShell oraz TsgcAuthenticodeVerifier do sprawdzania każdego z nich. Wszystkie budują się i działają na Linuksie, macOS, iOS i Androidzie.

Na serwerze i w wierszu poleceń

To samo dotyczy również poza biblioteką. sgcSign Server zbudowany dla Linuksa podpisuje i weryfikuje żądania Authenticode, PowerShell, katalogów, MSI i MSIX dokładnie tak samo jak w Windows, a narzędzie wiersza poleceń sgcsign wysyła je z dowolnej platformy. Jedna ścieżka pozostaje zarezerwowana dla Windows w narzędziu wiersza poleceń Delphi: haszowanie pliku EXE lub DLL lokalnie i wysłanie tylko skrótu. Narzędzie wiersza poleceń .NET korzysta z tej ścieżki na każdej platformie.

Dostępność

Podpisywanie kodu na Linuksie, macOS, iOS i Androidzie jest dostarczane w sgcSign 2026.10 dla Delphi. Dla programu na Windows nic się nie zmienia: te same komponenty, te same właściwości i ten sam wynik. Każda właściwość jest udokumentowana w pomocy online sgcSign.

Masz pytania albo potok, który chcesz przenieść z dala od Windows? Skontaktuj się z nami, a otrzymasz odpowiedź od osób, które napisały ten kod.