Delphi で MSI や MSIX インストーラーに署名する方法

· コンポーネント
Delphi で MSI や MSIX インストーラーに署名する方法

署名されていないインストーラーは、顧客が真っ先に気づく不具合です。SmartScreen は発行元不明の警告を出し、UAC のプロンプトには名前が表示されず、MSIX パッケージはまったくインストールできません。Windows は有効な署名を持つパッケージしかインストールしないからです。

sgcSign 2026.10 では、このために2つのコンポーネントが追加されました。TsgcMSISigner は Windows Installer の .msi ファイルと .msp ファイルに署名し、TsgcAppxSigner.msix.appx のパッケージおよびそのバンドルに署名します。どちらも、EXE 向けにすでにお使いかもしれない Authenticode 署名者とまったく同じように動作します。キープロバイダー、任意のタイムスタンプ局、そして1回の呼び出しです。

Delphi プログラムから署名された MSI と MSIX、そして絶対にインストールできないパッケージを止める発行者チェック。YouTube でも公開中

プログラム

ファイルの拡張子から署名者を選び、SHA-256 で署名し、タイムスタンプ局が指定されている場合は 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.

両方の署名者は TsgcAuthenticodeSigner を継承しているため、キープロバイダー、ダイジェスト、タイムスタンプのレベル、説明は、EXE の場合とまったく同じように共通の基底クラスで設定され、SignFile は具象クラスだけでなく基底型を通じても呼び出せます。

Windows Installer パッケージへの署名

.msi は PE ファイルではありません。ストリームとストレージからなる小さなファイルシステム、つまりコンパウンドファイルです。Windows が検証するダイジェストは、署名用のストリームを除いた上で、これらのストリームに対して決まった順序で計算されます。TsgcMSISigner はこのダイジェストを計算し、PKCS#7 署名を構築して、パッケージの DigitalSignature ストリームに書き込みます。SHA-1、SHA-256、SHA-384、SHA-512 のいずれも受け付けられ、RFC 3161 タイムスタンプにより証明書の有効期限が切れた後も署名は有効なままになります。

パッチパッケージ(.msp)も同じ方法で署名されます。AppendSignature を設定すると、すでに署名済みのパッケージに、signtool /as が EXE に対して行うのと同じように、最初の署名の隣に2つ目の署名が追加されます。

MSIX または APPX パッケージへの署名

MSIX はブロックマップを持つ ZIP であり、Windows が署名するのはファイルのハッシュではなく、その各部分に対するダイジェストの小さな構造体です。TsgcAppxSigner はこの構造体を構築し、署名して、その結果を AppxSignature.p7x としてパッケージに書き込みます。パッケージがまだ署名パートを宣言していない場合は追加されます。Windows はパッケージに対して SHA-256 しか受け付けないため、提供されるダイジェストはこれだけであり、すでに署名済みのパッケージに署名すると古い署名が置き換えられます。

発行者チェック

これは1日の午後をまるごと無駄にする間違いです。パッケージマニフェスト内の Publisher は、署名証明書のサブジェクトと完全に一致していなければなりません。両者が異なる場合、Windows は署名が無効であるとは報告しません。パッケージを認識できなくなり、検証できないファイル形式だと判断します。これは、ビルドが壊れているように見えます。

TsgcAppxSigner は署名前にこれをチェックし、両方の値を示すメッセージとともに停止します。

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

この比較は文字列としてではなく、属性の型と値によって行われるため、マニフェストと証明書の間のスペースや順序の違いが誤検知を引き起こすことはありません。ValidatePublisher(デフォルトは True)でこのチェックを無効にできます。また sgcAppxReadPublishersgcAppxDNMatches を使えば、たとえば署名の工程に到達する前にビルドを失敗させるといった目的で、同じ比較を自分で実行できます。

結果の確認

> 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

Windows 上の signtool verify /pa /v は、両方のファイルで同じ署名者と DigiCert のタイムスタンプを見つけ、計算するダイジェストは署名されたものと一致します。唯一の不満は 0x800B010A で、これはデモ用のチェーンが、このマシンにインストールされていない自己署名のテストルートで終わっているためです。ダイジェストや署名が壊れている場合は、代わりに 0x80096010 になります。TsgcAuthenticodeVerifier はインストーラーパッケージと MSIX パッケージを自動的に認識するため、EXE を検証するのと同じ呼び出しでこれらも検証できます。

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

sgcSign Server は /api/v1/sign/msi/api/v1/sign/appx の両方の形式に署名し、コマンドラインツールは --format msi--format appx を受け付けます。どちらもハッシュのみのルートを備えているため、大きなインストーラーがネットワークを経由することは一切なく、これはこのシリーズの次の記事のテーマです。

提供状況

TsgcMSISignerTsgcAppxSigner は、サーバーのルートおよびコマンドラインの形式とともに、Delphi と C++Builder 向けの sgcSign 2026.10 に搭載されています。Windows だけでなく、Linux、macOS、iOS、Android でもビルドして実行できます。すべてのプロパティは sgcSign オンラインヘルプに記載されています。

署名できない、またはインストールできないパッケージについてご質問がありますか?お問い合わせください。コードを書いた本人から返信が届きます。