DelphiでLinuxからWindowsのEXEに署名する

· コンポーネント
DelphiでLinuxからWindowsのEXEに署名する

Windowsプログラムに署名するには、これまで常にWindowsマシンが必要でした。signtoolはWindows上でしか動作しないため、Linuxでコンパイルするビルドパイプラインでも、最後にファイルへ署名を付けるためだけにどこかにWindowsホストが必要でした。

sgcSign 2026.10からは、そのホストは不要になりました。PEファイルのハッシュ化と署名の構築を行うsgcSignのAuthenticode形式は、Windowsの署名APIを呼び出すのではなく、Delphiコードそのものになりました。Windows上では鍵の操作は引き続きCNGを経由しますが、それ以外の環境ではsgcSign独自の暗号処理で実行されます。そのため、Windows上でEXEに署名するコンポーネントはLinux 64ビットでもビルドおよび実行でき、Windowsはそれらが生成したファイルを他の署名済みファイルと同じように受け入れます。この記事では、それを行う短いコンソールプログラム、Linux向けのビルド方法、そして結果の確認方法を紹介します。

UbuntuでEXEに署名するDelphiプログラムと、その同じファイルをWindowsで確認する様子です。YouTubeでも公開中

そもそもなぜLinuxで署名するのか

プログラム

署名対象のファイル、出力ファイル、パスワード付きのPFX、そして任意でタイムスタンプ局を受け取るコンソールプログラムです。Windowsでも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が証明書と秘密鍵を保持します。TsgcAuthenticodeSignerはWindowsと同じ方法でPEファイルをハッシュ化し、PKCS#7署名を構築してファイルの証明書テーブルに書き込みます。TsgcTSAClientを接続すると、RFC 3161タイムスタンプも取得するため、証明書の有効期限が切れた後も署名は有効なままです。

Linux 64ビット向けにビルドする

Projectsウィンドウで、SDK Managerに設定したLinux SDKを使ってLinux 64-bitをターゲットプラットフォームとして追加し、ビルドします。sgcSignのランタイムパッケージは、Delphi 10.3以降でLinux64向けにビルドされます。Linux上ではインポート先となるWindowsの証明書ストアが存在しないため、PFXは純粋なPascalで解析され、RSAまたはECDSAの演算はsgcSign独自の暗号処理で実行されます。その周囲の署名形式は、Windows上で動くのと同じコードによって構築されます。

実行して結果を確認する

$ 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

Windowsなしで確認するには、osslsigncode verifyがファイルのダイジェストを再計算し、それに対する署名を検証します。結果はSignature verification: okです。

続いてファイルをWindowsにコピーします。PowerShellとsigntool verify /pa /vは同じ署名を読み取ります。Windowsが計算するファイルハッシュは署名されたものと一致し、署名者はデモ証明書であり、DigiCertのタイムスタンプも検証されます。ファイルは引き続き実行できます。

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

Windowsが唯一指摘するのは署名についてではなく、デモ証明書についてです。デモのチェーンは、そのマシンにインストールされていない自己署名のテストルートで終わっているため、Windowsはそれを信頼するルートに結び付けられません。ダイジェスト、署名、タイムスタンプはすべて検証されます。

LinuxとWindowsで同じバイト列

同じHello.exeを同じPFXでタイムスタンプなしに、上記プログラムのLinux版とWindows版でそれぞれ一度ずつ署名し、2つのファイルを比較します。

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

両者はバイト単位で完全に一致します。この種のRSA署名は決定的であり、署名者は求めない限り署名時刻を含めないため、ファイルは鍵と入力だけに依存し、署名を行ったマシンには依存しません。実行のたびに異なるのはタイムスタンプだけで、それはタイムスタンプ局が要求された瞬間を記録するためです。

鍵はどこから来るのか

Linux上では、鍵はPFXまたはPEMファイル、PKCS#11トークンやHSM、あるいはクラウド署名サービス(Azure Trusted Signing、AWS KMS、Google Cloud KMS、HashiCorp Vault、またはCSCリモート署名サービス)から取得できます。Windowsの証明書ストアプロバイダーだけは、当然の理由からWindows専用のままです。

EXEだけではない

これはsgcSignのすべてのコード署名コンポーネントに当てはまります。EXEとDLL向けのTsgcAuthenticodeSigner.catカタログ向けのTsgcCatalogSigner、Windows Installerの.msiおよび.mspファイル向けのTsgcMSISigner.msixおよび.appxパッケージ向けのTsgcAppxSigner、PowerShellスクリプト向けのTsgcPowerShellSigner、そしてそれらすべてを検証するTsgcAuthenticodeVerifierです。これらはすべてLinux、macOS、iOS、Androidでビルドおよび実行できます。

サーバーとコマンドラインでも

同じことはライブラリの外でも当てはまります。Linux向けにビルドされたsgcSign Serverは、Windowsの場合とまったく同じようにAuthenticode、PowerShell、カタログ、MSI、MSIXのリクエストに署名および検証を行い、sgcsignコマンドラインツールは任意のプラットフォームからそれらを送信します。Delphiのコマンドラインツールでは、EXEやDLLをローカルでハッシュ化し、そのハッシュだけを送信する経路が引き続きWindows専用です。.NETのコマンドラインツールは、どのプラットフォームでもその経路を使います。

提供状況

Linux、macOS、iOS、Androidでのコード署名は、Delphi向けsgcSign 2026.10で提供されます。Windowsプログラムについては何も変わりません。同じコンポーネント、同じプロパティ、同じ出力です。各プロパティはsgcSignオンラインヘルプにすべて記載されています。

ご質問がある方、あるいはパイプラインをWindowsから移行したい方は、お問い合わせください。実際にそのコードを書いた人から返信が届きます。