チュートリアル: Delphi での PAdES PDF 署名

未署名の請求書 PDF から、組み込み RFC 3161 タイムスタンプ付き PAdES-B-T ドキュメントを生成する完全なウォークスルー — eIDAS、AdES 検証ソフト、ほとんどの公共部門ポータルが期待するレベルです。鍵プロバイダー(PKCS#11 スマートカードまたは Windows 証明書ストア)を構成し、適切な PAdES プロファイルを選び、署名済みファイルをエンド・ツー・エンドで検証します。

PAdES-B-B / B-T / B-LT / B-LTA
PKCS#11 / Windows ストア / PFX
Delphi 7 – RAD Studio 13

作成するもの

PDF を読み込み、スマートカードまたは PFX で署名し、信頼できるタイムスタンプを埋め込み、検証可能な PAdES ファイルをディスクに書き出す VCL フォームアプリケーション。

PAdES レベルを選ぶ

PAdES-B-B(基本)、PAdES-B-T(タイムスタンプ付き)、PAdES-B-LT(失効データ付き長期)または PAdES-B-LTA(アーカイブタイムスタンプ付き長期)。PAdES-B-T は請求書、契約、ほとんどの公共部門ワークフローの妥当なデフォルトです。

鍵を読み込む

sgcSign は 10 の鍵プロバイダーをサポートします。このチュートリアルはハードウェアトークン用の TsgcPKCS11KeyProvider と、Windows にすでにインストールされた証明書用の TsgcWindowsStoreKeyProvider をカバーします。PFX、PEM、Azure Trusted Signing、AWS KMS、Google KMS でも同じコードパスが動作します。

署名と検証

TsgcSignPDF が署名パイプラインを駆動し、TsgcSignProfile_PAdES がプロファイルを構成し、TsgcSignatureVerifier が最終ドキュメントを検証します。各ステップはイベントを公開しているため、各段階で何が起きているかを検査できます。

ユニットを追加する

各鍵プロバイダーは自身のユニットにあります。使用しないユニットをリンクしないよう、必要なプロバイダーのみを参照してください。

Delphi の uses

このチュートリアルでは 2 つの鍵プロバイダー(PKCS#11 と Windows ストア)と PAdES 署名ユニットを取り込みます。sgcSign_TSA は RFC 3161 タイムスタンプクライアントです — TSA URL を設定すると PAdES プロファイルが自動的に使用します。

PFX ファイルしかない場合は、sgcSign_KeyProvider_PKCS11sgcSign_KeyProvider_PFX に置き換え、5 分クイックスタート で示すように TsgcPFXKeyProvider を使用してください。

uPDFSigning.pas
uses
  Classes, SysUtils,
  // sgc
  sgcSign_KeyProvider_PKCS11,
  sgcSign_KeyProvider_WindowsStore,
  sgcSign_PDF,
  sgcSign_Profile_PAdES,
  sgcSign_TSA,
  sgcSign_Verifier;

鍵プロバイダーを構成する

2 つの一般的な選択肢 — PKCS#11 ハードウェアトークン(スマートカード、USB HSM)または Windows 証明書ストアにすでにある証明書。

PKCS#11 スマートカード

ModulePath をベンダー PKCS#11 DLL に向けます(例: SafeNet では C:\Windows\System32\eTPKCS11.dll)。Slot プロパティはトークンスロットを選択します — 最初に利用可能なトークンには 0 を使用。PIN は OnPinPrompt イベント経由で配信されるため、ソースに保存されることはありません。

LoadFromToken を呼ぶとセッションが開かれ、ラベルまたはサムプリントで署名証明書が検索され、秘密鍵ハンドルが準備されます。バイトはトークンを離れません — 実際の RSA-PSS または ECDSA 署名はデバイス上で行われます。

step1-pkcs11.pas
var
  vKeyProvider: TsgcPKCS11KeyProvider;
begin
  vKeyProvider := TsgcPKCS11KeyProvider.Create(nil);
  try
    vKeyProvider.ModulePath := 'C:\Windows\System32\eTPKCS11.dll';
    vKeyProvider.Slot := 0;
    vKeyProvider.CertificateLabel := 'My Signing Cert';
    vKeyProvider.OnPinPrompt :=
      procedure(Sender: TObject; var aPIN: string)
      begin
        aPIN := InputBox('Smart card', 'PIN:', '');
      end;
    vKeyProvider.LoadFromToken;
    // vKeyProvider is now ready to sign
  finally
    // keep alive until signing finishes, then Free
  end;
end;

Windows 証明書ストア

証明書がすでに Windows ストアにインストールされている場合 — 例えば、ベンダーのミニドライバー付きの適格 eID カードを使用している場合 — は、代わりに TsgcWindowsStoreKeyProvider を使用してください。プロバイダーは CNG と通信するため、元の CSP に関係なく SHA-256/SHA-384 が動作します。

サブジェクト、サムプリント、シリアル番号で照合します。StoreLocationslCurrentUser に設定すると個人ストアを使用、slLocalMachine はマシンストアを使用し、通常管理者権限を必要とします。

step1-winstore.pas
var
  vKeyProvider: TsgcWindowsStoreKeyProvider;
begin
  vKeyProvider := TsgcWindowsStoreKeyProvider.Create(nil);
  vKeyProvider.StoreLocation := slCurrentUser;
  vKeyProvider.StoreName := 'MY';
  vKeyProvider.Thumbprint :=
    '1234ABCD5678EF901234567890ABCDEF12345678';
  vKeyProvider.LoadFromStore;
end;

PAdES-B-T プロファイルを構成する

PAdES-B-T は署名に RFC 3161 タイムスタンプを追加するため、検証者は — 署名証明書が後に失効または取り消された場合でも — ドキュメントが既知の時点より前に署名されたことを証明できます。

プロファイルプロパティ

Level は AdES レベルを選択します — palBpalTpalLT または palLTA。PAdES-B-T には palT を設定。プロファイルは空でない TSA.URL を期待します。

HashAlgorithm はデフォルトで shaSHA256 で、すべてのモダンな検証者が期待するものです。SHA-384 と SHA-512 も利用可能。eIDAS がもはや受け付けないため SHA-1 は意図的に提供されません。

SignatureField サブオブジェクトは、PDF 内のどこに可視署名ウィジェットを配置するかを制御します — ページ番号、矩形座標、オプションの理由/場所/連絡先テキストフィールド。不可視署名(デフォルト)には空のままにしてください。

step2-profile.pas
var
  vProfile: TsgcSignProfile_PAdES;
begin
  vProfile := TsgcSignProfile_PAdES.Create(nil);
  vProfile.Level := palT;
  vProfile.HashAlgorithm := shaSHA256;

  // RFC 3161 timestamp authority
  vProfile.TSA.URL := 'http://timestamp.digicert.com';
  vProfile.TSA.HashAlgorithm := shaSHA256;

  // Optional visible signature widget
  vProfile.SignatureField.Visible := True;
  vProfile.SignatureField.Page := 1;
  vProfile.SignatureField.Rect := Rect(50, 50, 250, 120);
  vProfile.SignatureField.Reason := 'Approved';
  vProfile.SignatureField.Location := 'Madrid';
end;

PDF に署名する

鍵プロバイダーとプロファイルを TsgcSignPDF に結線し、入力と出力のファイル名で SignFile を呼び出します。

完全な署名呼び出し

SignFile は入力 PDF を読み、ドキュメントダイジェストを計算し、鍵プロバイダーを呼んで署名を生成し、TSA からタイムスタンプを取得し、結果を書き出します。入力にすでに署名が含まれている場合、sgcSign は増分更新を追加します — 元の署名は有効なまま残ります。

OnProgress イベントは各フェーズ(readinghashingsigningtimestampingwriting)を報告するため、複数 MB のファイルでプログレスバーに便利です。

step3-sign.pas
var
  vSigner: TsgcSignPDF;
begin
  vSigner := TsgcSignPDF.Create(nil);
  try
    vSigner.KeyProvider := vKeyProvider;
    vSigner.Profile := vProfile;
    vSigner.OnProgress :=
      procedure(Sender: TObject; const aPhase: string; aPct: Integer)
      begin
        Memo1.Lines.Add(Format('%s — %d%%', [aPhase, aPct]));
      end;
    vSigner.SignFile('invoice.pdf', 'invoice-signed.pdf');
  finally
    vSigner.Free;
  end;
end;

署名済み PDF を検証する

署名済みドキュメントは検証できて初めて有用です。TsgcSignatureVerifier は 1 回の呼び出しで暗号署名、証明書チェーン、組み込みタイムスタンプを検証します。

結果の読み方

VerifyFile メソッドは署名ごとの内訳を持つ TsgcSignatureReport を返します。各エントリには StatussvValidsvInvalidsvUnknown)、署名者サブジェクト、実際に検出された AdES レベル、タイムスタンプ値、チェーンや失効問題が含まれます。

Report.Level を期待するレベルと比較してください — palT を望んだので、palB を報告する検証は、タイムスタンプ要求が静かに失敗したことを意味します。通常の容疑者は TSA URL です。

step4-verify.pas
var
  vVerifier: TsgcSignatureVerifier;
  vReport: TsgcSignatureReport;
  i: Integer;
begin
  vVerifier := TsgcSignatureVerifier.Create(nil);
  try
    vReport := vVerifier.VerifyFile('invoice-signed.pdf');
    for i := 0 to vReport.SignatureCount - 1 do
    begin
      Memo1.Lines.Add('Signer:    ' + vReport.Signatures[i].Subject);
      Memo1.Lines.Add('Level:     ' + vReport.Signatures[i].LevelAsString);
      Memo1.Lines.Add('Status:    ' + vReport.Signatures[i].StatusAsString);
      Memo1.Lines.Add('TSA time:  ' +
        DateTimeToStr(vReport.Signatures[i].TimestampUTC));
    end;
  finally
    vVerifier.Free;
  end;
end;

通常何がうまくいかないか

開発者が最初の PAdES ドキュメントに署名するときに最もよく見る問題の短いリスト。

TSA が HTTP 200 を返すが不正な応答

一部の TSA は HTTPS 専用で、平文の http:// URL をソフトエラーで拒否します。palT を要求したのに Report.LevelpalB として戻る場合、TSA URL を https:// 相当に切り替えてください。ほとんどの公開 TSA は両方のスキームを公開しています。

証明書に拡張鍵使用がない

テスト証明書はしばしば id-kp-documentSigning EKU を欠きます。暗号署名が有効でも、Adobe Reader は警告を表示します。本番には CA から明示的なドキュメント署名 EKU を持つ証明書を要求してください。

PDF/A ドキュメントには PAdES-B-LT が必要

PDF/A-2 と PDF/A-3 アーカイブは、すべての失効情報をドキュメント自体に埋め込むことを要求します。palLT または palLTA を使用 — sgcSign は署名時に OCSP 応答と CRL を収集し、DSS 辞書に埋め込みます。

スマートカードセッションがタイムアウト

一部の PKCS#11 ドライバーは数秒の非活動後にセッションを閉じます。署名操作全体の間 TsgcPKCS11KeyProvider インスタンスを生かしておき、SignFile が戻った後にのみ Free を呼んでください。

ここからどこへ

長期アーカイブ、国別プロファイル、または署名をデスクトップから集中サーバーへ移行。

21 の国別プロファイル

VeriFactu、FatturaPA、KSeF、FACTUR-X、EU 雇用契約プロファイル向けのワンライナー — すべて同じ PAdES / XAdES エンジンの上に構築されています。

続きを読む →

sgcSign Server

署名をセルフホスト型デーモンに移行 — PAdES の上の REST API、GitHub Actions 統合、Authenticode、ClickOnce。

続きを読む →

10 の鍵プロバイダー

PFX と Windows ストアを超えて — Azure Trusted Signing、AWS KMS、Google KMS、HashiCorp Vault、Certum、CSC v2 クラウドプロバイダー。

続きを読む →

最初の PDF に署名する準備はできましたか?

トライアルをダウンロードし、このチュートリアルを自分の請求書に対して実行し、出荷時に sgcSign のライセンスを取得してください。