sgcSign 2026.9.0 の新機能

· リリース
sgcSign 2026.9.0、Delphi および C++ Builder 向けの電子署名コンポーネント

sgcSign 2026.9.0 は大きなリリースです。その大半はお客様からのご要望から生まれたもので、内容は3つの領域にまたがります。これから署名に使う証明書がどれなのかを把握すること、10年後も検証ツールに受け入れられる署名を構築すること、そして署名をそれ自身以外のものに照らして検証することです。

この記事では、新機能をそれぞれの Delphi コードとともに順に紹介します。最後に、以前のバージョンで作成した署名のうち作り直すべきものについての短いセクションもあります。

選択できる証明書リスト

証明書の列挙は、これまで表示名のリストを返すだけでした。コンボボックスを埋めるには十分ですが、判断を下すには足りません。同じ認証局が同じ人物に対して発行した2枚のカードは、そのリストではまったく同じに見えます。

列挙結果には SHA-1 のサムプリント、納税者識別番号、シリアル番号、発行者、有効期間が含まれるようになり、Windows 証明書ストア、PKCS#11 トークン、PFX ファイルのいずれでも同じように動作します。期限切れの証明書や秘密鍵を持たない証明書は除外できます。サムプリントはそのまま SelectCertificateByThumbprint に渡せるので、ユーザーが選んだ証明書がそのまま署名に使われます。

uses
  sgcSign_KeyProvider_WinCertStore, sgcSign_X509, sgcSign_Types;

var
  oProvider: TsgcWindowsCertStoreProvider;
  oList: TsgcX509CertificateList;
  i: Integer;
begin
  oProvider := TsgcWindowsCertStoreProvider.Create(nil);
  Try
    // only certificates that are still valid and hold a private key
    oList := oProvider.EnumerateCertificateList([cfNotExpired, cfPrivateKey]);
    Try
      for i := 0 to oList.Count - 1 do
        Memo1.Lines.Add(Format('%s | %s | %s | %s .. %s | %s',
          [oList[i].Subject, oList[i].NIF, oList[i].SerialNumber,
           DateToStr(oList[i].NotBefore), DateToStr(oList[i].NotAfter),
           oList[i].Thumbprint]));

      // and sign with exactly the one that was chosen
      oProvider.SelectCertificateByThumbprint(oList[0].Thumbprint);
    Finally
      oList.Free;
    End;
  Finally
    oProvider.Free;
  End;
end;

パラメーターなしの呼び出しは変更されていないため、既存のコードはそのまま動作します。

PIN なしで一覧化できるマルチスロットカード

適格署名カードは、それぞれ個別の PIN で保護された複数の証明書を保持していることがよくあります。ポーランドのカードが典型で、2つのプロファイルを持つ Certum のカードや、3つのコンテナーを持つ PWPW Sigillum のカードがあります。リストを表示するためだけにユーザーへ3つの PIN を尋ねるのは、現実的なインターフェイスではありません。

PKCS#11 トークンは、まったくログインせずに一覧化できるようになりました。TokenSlotCount は実際にトークンが入っているスロットの数を返すので、対象とすべき範囲がわかります。また各エントリには取得元のスロットとトークンラベルが記録されるため、その証明書が選ばれたときにだけ正しい PIN を尋ねられます。

uses
  sgcSign_KeyProvider_PKCS11, sgcSign_X509, sgcSign_Types;

var
  oPKCS11: TsgcPKCS11Provider;
  oList: TsgcX509CertificateList;
  i: Integer;
begin
  oPKCS11 := TsgcPKCS11Provider.Create(nil);
  Try
    oPKCS11.LibraryPath := 'C:\Windows\System32\cryptoCertum3PKCS.dll';

    ShowMessage(Format('%d slots hold a token', [oPKCS11.TokenSlotCount]));

    // walks every slot that holds a token, never logs in, never needs a PIN
    oList := oPKCS11.EnumerateCertificateListAllSlots([cfNotExpired]);
    Try
      for i := 0 to oList.Count - 1 do
        Memo1.Lines.Add(Format('slot %d (%s): %s',
          [oList[i].SlotIndex, oList[i].TokenLabel, oList[i].Subject]));
    Finally
      oList.Free;
    End;
  Finally
    oPKCS11.Free;
  End;
end;

自分の証明書を発行した証明書を見つける

長期署名プロファイルには発行元の証明書が必要ですが、ほとんどの適格署名カードには自分自身の証明書しか入っていません。すべてのキープロバイダーに追加された2つの呼び出しがそれを見つけます。GetIssuerCertificate は署名に使う証明書を発行した証明書を返し、GetCertificateChain はその上位のパス全体を返します。照合は名前ではなく暗号的に検証されるため、署名鍵を更新した認証局がその前身と取り違えられることはありません。

どこを探すかは判断が必要なので、プロパティになっています。既定では Windows 証明書ストアが検索され、iluLocalStore はアプリケーションと一緒に配布する PEM または DER ファイルを検索し、iluAIA は自分の証明書に記載されたアドレスから証明書をダウンロードします。最後のものはネットワークにアクセスするため、既定では無効です。

uses
  sgcSign_Classes, sgcSign_X509;

var
  vIssuerDER: TBytes;
  oChain: TsgcCertificateChain;
begin
  oProvider.IssuerLookup := [iluSystemStore, iluLocalStore];
  oProvider.IssuerFiles.Add('certs\ca-intermediate.pem');
  oProvider.IssuerFiles.Add('certs\ca-root.pem');

  vIssuerDER := oProvider.GetIssuerCertificate;
  oChain := oProvider.GetCertificateChain;
end;

2つの新しい PAdES プロファイル

spPAdESBasicT は、埋め込みタイムスタンプ付きで失効情報なしの署名を作成します。署名がいつ作成されたかを証明できればよい場合に適しています。spPAdESDocumentArchive はその逆で、長期プロファイルの上にアーカイブタイムスタンプを追加し、失効情報を含む文書全体を対象にします。これにより、最初のタイムスタンプ自体の有効期間が過ぎた後もファイルを検証できます。

uses
  sgcSign_PAdES, sgcSign_Types;

var
  oPAdES: TsgcPAdESSigner;
begin
  oPAdES := TsgcPAdESSigner.Create(nil);
  Try
    oPAdES.KeyProvider := oProvider;
    oPAdES.Profile.Profile := spPAdESDocumentArchive;
    oPAdES.TSAClient := TSAClient1;
    oPAdES.OCSPClient := OCSPClient1;
    // revocation lists you supply yourself, for an authority whose CRL is
    // issued by its own root. IssuerCertificate is resolved automatically
    // when it is left empty and OCSPClient is assigned
    oPAdES.CRLFiles.Add('crl\ca-intermediate.crl');

    oPAdES.SignPDFFile('contract.pdf', 'contract-signed.pdf');
  Finally
    oPAdES.Free;
  End;
end;

証明書が保持するすべての情報を返す

サブジェクトと発行者は、これまでパーサーが認識する7つの属性だけを返し、残りは破棄していました。現在は証明書内のすべての属性を返し、住所は読みやすい行にデコードされ、任意の属性を OID で読み取れます。

uses
  sgcSign_X509;

var
  i: Integer;
begin
  // by OID: organizationIdentifier
  ShowMessage(oCert.GetSubjectAttribute('2.5.4.97'));

  // or the full list, in the order the certificate declares it
  for i := 0 to oCert.SubjectAttributeCount - 1 do
    Memo1.Lines.Add(oCert.SubjectAttributeOID[i] + ' = ' +
      oCert.SubjectAttributeValue[i]);
end;

署名付きタイムスタンプ要求

一部の適格タイムスタンプ局、特にポーランドのものは、素の RFC 3161 要求に応答しません。要求そのものを CMS SignedData で包んで署名することを求めます。これが、手作業で組み立てるものではなくプロパティになりました。

uses
  sgcSign_TSA;

begin
  oTSA.URL := 'https://tsa.example.com';
  oTSA.RequestFormat := trfCMS;        // plain RFC 3161 is still the default
  oTSA.KeyProvider := oProvider;       // trfCMS needs one

  // authorities differ in what they expect inside the wrapper
  oTSA.SignOptions.IncludeSignedAttributes := True;

  // and the exact bytes are available when an authority needs checking
  oTSA.OnBeforeSendRequest := DoBeforeSendRequest;
  oTSA.OnAfterReceiveResponse := DoAfterReceiveResponse;
end;

既定の形式は、PWPW Sigillum タイムスタンプ局が受け付ける要求に合わせてあります。既存のコードは引き続き素の要求を送信し、RequestFormat を設定しない限り何も変わりません。

Authenticode のクロス証明書

カーネルモードドライバーの署名は、クロス証明書を経由して Microsoft Code Verification Root まで連鎖する必要があります。signtool /ac が埋め込むのがこれです。sgcSign でも同じように追加の証明書を埋め込めるようになりました。

uses
  sgcSign_Authenticode;

var
  oSigner: TsgcAuthenticodeSigner;
begin
  oSigner := TsgcAuthenticodeSigner.Create(nil);
  Try
    oSigner.KeyProvider := oProvider;
    oSigner.AddCertificateFromFile('MSCV-VSClass3.cer');
    // or from bytes you already hold
    // oSigner.AddCertificate(vCrossCertDER);

    oSigner.SignFile('driver.sys', 'driver-signed.sys');
  Finally
    oSigner.Free;
  End;
end;

追加した証明書はすべての入れ子署名に反映され、何も追加しなければ署名はバイト単位でこれまでと同一のままです。署名サーバーは add_certs フィールドを受け取り、CLI は繰り返し指定できる --add-cert オプションを受け取ります。

トラストアンカーによる検証

これは本リリースで最も重要な変更です。これまで検証側は、検査対象の文書から署名証明書を取り出し、その鍵がその文書に署名したことを確認していました。それが証明するのは、文書を作成した者が中の署名も作成したということだけで、それ以上ではありません。誰でも検証を通る文書を作れてしまいます。

検証にトラストアンカーを渡せるようになり、それに対して証明書チェーンを構築して検証します。アンカーの照合は SHA-256 サムプリント、またはそのアンカー自身の鍵による検証で行われ、名前による照合は行いません。

uses
  sgcSign_Verifier, sgcSign_Types;

var
  oVerifier: TsgcSignatureVerifier;
begin
  oVerifier := TsgcSignatureVerifier.Create(nil);
  Try
    oVerifier.TrustedCertificates.Add('certs\qualified-root.pem');
    // or a Windows certificate store by name
    oVerifier.TrustedCertificateStore := 'ROOT';

    oVerifier.RequireTrustedChain := True;
    oVerifier.CheckKeyUsage := True;
    oVerifier.RequireCompleteRevocationCheck := True;

    if oVerifier.Verify(vSignedXML) = vsValid then
      ShowMessage('signed, and chained to a root you trust');
  Finally
    oVerifier.Free;
  End;
end;

アンカーを指定しない検証はこれまでと同じ判定を返すため、アップグレードで壊れるものはありません。ただし1点だけ変わります。ETSI TS 119 102-2 のレポートは、アンカーまで連鎖しなかった署名に対して total-passed を返さなくなり、NO_CERTIFICATE_CHAIN_FOUND を伴う indeterminate を返します。以前のバージョンで保存したレポートは再生成が必要です。

プロキシに対応した単一の HTTP トランスポート

ライブラリは複数の場所からネットワーク要求を行います。タイムスタンプクライアント、OCSP と失効リストのクライアント、EU トラストリストのダウンロード、そしてクラウドキープロバイダーです。これまでは、それぞれが独自のやり方で要求を送っていました。現在はすべてが単一のトランスポートを共有し、1つの HTTPOptions プロパティで設定できます。

uses
  sgcSign_WinHTTP;

begin
  oTSA.HTTPOptions.Proxy.Mode := pxCustom;   // pxSystem is the default
  oTSA.HTTPOptions.Proxy.URL := 'proxy.corp.local:8080';
  oTSA.HTTPOptions.Proxy.Username := 'user';
  oTSA.HTTPOptions.Proxy.Password := 'secret';

  // the certificate presented when the gateway asks for client authentication
  oTSA.HTTPOptions.ClientCertificate.StoreName := 'MY';
  oTSA.HTTPOptions.ClientCertificate.Thumbprint := 'a1b2c3...';

  oTSA.HTTPOptions.MinTLSVersion := tlsTLS1_2;
end;

プロキシには、マシン全体の設定、プロキシなし、明示的なアドレス、あるいは WPAD や PAC スクリプトで解決されるユーザーごとの設定を選べます。最後のものはブラウザーと同じ動作です。すべての設定の既定値は、これまでの各要求の動作に合わせてあります。これらの設定では表現できないゲートウェイに対しては、新しい OnHTTPRequest イベントでトランスポートそのものを置き換えられます。

知っておきたい細かな改善

OCSP のナンス。 失効確認の要求には、システムの暗号乱数生成器から取得したランダムなナンスが含まれるようになり、応答がそれと照合されます。ナンスを返さない応答は引き続き受け入れられます。RFC 6960 が事前生成された応答を認めているためです。一方、異なるナンスを返す応答は拒否されます。NonceEnabled を使うと、この拡張を受け付けないレスポンダー向けに機能を無効化できます。

EU トラストリストのピボットのピン留め。 新しい RequirePinnedPivot プロパティは、信頼リストの一覧がピン留めされた官報のピボットフィンガープリントのいずれかまで連鎖する必要があるかどうかを決めます。結果は LOTLPivotPinnedLastPivotFingerprint が報告します。このチェック自体は存在していましたが、どこからも呼び出されていませんでした。同梱されるピン留め定数は、いまのところドキュメントに記載されたプレースホルダーのままなので、実際のフィンガープリントを設定してプロパティを有効にするまでは不一致として報告されます。

ダイジェストを選べる。 CAdES と PKCS#11 の両方に HashAlgorithm プロパティが追加されました。既定は SHA-256 なので、既存のコードはこれまでと同じバイト列を生成します。CAdES はすべてのダイジェストアルゴリズムに SHA-256 をリテラルとして書き込んでおり、PKCS#11 は渡されたデータの長さから DigestInfo ヘッダーを選んでいたため、他のダイジェストを表現できませんでした。カードは指定どおりに SHA-1、SHA-256、SHA-384、SHA-512 で署名できるようになりました。

署名コールバックに対応した ASiC。 新しい BuildCAdES のオーバーロードは、完成した署名バイト列の代わりにコールバックを受け取ります。まず META-INF/ASiCManifest.xml を構築し、そのバイト列をそのままコールバックに渡して、返ってきたものを META-INF/signature.p7s として格納します。署名がマニフェストを対象にできるのは、この順序だけです。マニフェストを持たない ASiC-S では、コールバックはデータ文書そのものを受け取ります。GetCAdESSignedData は、2つの明示的な手順に分けたい場合に同じバイト列を返します。

クラウド KMS の証明書。 AWS KMS と Google Cloud KMS に SetCertificateSetCertificateFromFile が追加され、HashiCorp Vault がすでに備えていた組み合わせと揃いました。どちらのサービスも公開鍵だけを返すため、これまではどの X.509 証明書が対応するのかをプロバイダーに伝える手段がありませんでした。

CLI を実行するマシンから直接取得するタイムスタンプ。 sgcsign コマンドラインに --tsa-direct が追加され、sgcSign Server を経由せずにタイムスタンプ局へ直接要求します。--tsa と併せて指定してください。

署名サーバー

サーバー側にも独自の改善点があります。Authenticode 署名は、sha1,sha256,sha384 のような順序付きの hash_algorithms リスト、または certA:sha256,certB:sha1 のような順序付きの providers リストによって、それぞれ異なる証明書を使う3つ以上の入れ子署名を持てるようになりました。いずれの方法でも最大4エントリまでです。これは、期限切れが近い証明書とその後継証明書の両方で署名した1つのファイルを配布するためのものです。すべての証明書は、署名を開始する前に API キーの権限と照合されます。

Windows のカタログファイルにも署名できます。アップロードエンドポイントは形式として catalog を受け付け、makecat が生成するような既存の .cat ファイルに署名するため、ドライバーパッケージもプログラムと同じ方法で署名できます。

新しい /api/v1/sign/raw エンドポイントは、すでに計算済みのダイジェストに署名し、署名値だけを返します。PKCS#7 のラッパーも、署名属性も、タイムスタンプもありません。これは signtool/dlib コールバックを通じて求めるものそのものです。渡されたダイジェストであれば何でも署名してしまうため、既定では無効になっており、allow_raw_sign によってプロバイダーごとに個別に有効化します。

API キーとそれを作成するユーザーは、プロジェクトごとに分離されるようになりました。プロジェクト管理者は自分のプロジェクト内のキーを管理でき、キーは一方向に失効させるだけでなく、有効化と無効化を切り替えられます。キーごとのレート制限と1日あたりのクォータは、キーの作成後に編集できます。新しい SessionAbsoluteMaxMin 設定は、管理者セッションの合計有効時間を既定で12時間に制限します。これまでは、認証済みの要求のたびに有効期限が上限なく延長されていたためです。監査ログは、コンソールでも CSV エクスポートでもクライアントアドレスで絞り込めるようになり、アドレスの先頭からの部分一致に対応します。さらに、既定では無効の新しい転送ヘッダー設定により、サーバーがリバースプロキシの背後で動作している場合に実際のクライアントアドレスを復元できます。この値は、接続が一覧に登録された信頼済みプロキシから届いた場合にのみ信用されます。

作り直すべき署名

以前のバージョンにあった3つの不具合により、構造的に誤ったファイルが生成されていました。アップグレードしても、すでに書き出されたファイルは修復されません。署名したものが次のいずれかに当てはまる場合は、2026.9.0 で署名し直してください。

検証も同じ方向に変わりました。Authenticode の検証は署名をまったく検査しておらず、ファイルのハッシュを再計算して署名内のハッシュと比較するだけでした。そのため、sgcSign が正しく署名済みと判定するファイルを偽造するのに秘密鍵は不要でした。失効応答とタイムスタンプトークンは、検証されないまま埋め込まれていました。EU トラストリストは、何も検証せずにダウンロードして使用していました。これらはすべて、名前が示すとおりの検査を行うようになりました。それぞれの詳細は変更履歴に記載しています。

入手方法

sgcSign 2026.9.0 は、完全なソースコードと1年間のアップデート付きで提供中です。対応環境は Delphi 7 から Delphi 13 Florence まで、対応する C++ Builder の各バージョン、そして .NET です。

製品ページ · 体験版をダウンロード · 変更履歴

ご質問やご意見がありましたら、お問い合わせください。コードを書いた本人から返信します。