チュートリアル: Delphi での XAdES XML 署名
プレーンな XML 請求書から、FatturaPA、FACTUR-X、欧州中のほとんどの電子請求書ポータルが要求するレベルである XAdES-B-T ドキュメントを生成する実践的ウォークスルー。enveloped、enveloping、detached の署名パッケージングから選び、RFC 3161 タイムスタンプを添付し、結果を検証することで、取引先が何を見るかを正確に把握できます。
プレーンな XML 請求書から、FatturaPA、FACTUR-X、欧州中のほとんどの電子請求書ポータルが要求するレベルである XAdES-B-T ドキュメントを生成する実践的ウォークスルー。enveloped、enveloping、detached の署名パッケージングから選び、RFC 3161 タイムスタンプを添付し、結果を検証することで、取引先が何を見るかを正確に把握できます。
XAdES は W3C XML-DSig 仕様をタイムスタンピングと長期検証データで拡張します。何かに署名する前に、ds:Signature 要素がどこに住むかを決めてください。
ds:Signature 要素はドキュメントルートの内側に追加されます。これは FatturaPA、FACTUR-X-XAdES、ほとんどの電子請求書フローで使用される形式です。署名済み XML は依然として元のドキュメントの有効なコピーです — リーダーは理解しない署名要素を無視します。
元の XML が ds:Signature の内側の ds:Object 子になります。単一の自己完結型エンベロープが必要で、既存のコンシューマーがドキュメントをパースし続けられるかどうかを気にしない場合に便利。
署名は別ファイルに置かれ、URI 参照経由で元のドキュメントを指します。SAML、ebXML、ソースドキュメントがバイト単位で変更されてはならない任意のフローで一般的。
XAdES には XML 署名者、XAdES プロファイル、鍵プロバイダーが必要です。このチュートリアルでは PFX を使用。スマートカードやクラウド HSM がある場合は 10 のプロバイダーのいずれかに置き換えてください。
uses 句sgcSign_XML は XML 署名コアです。sgcSign_Profile_XAdES はレベル、変換、タイムスタンプ構成を公開します。sgcSign_KeyProvider_PFX はディスクから PKCS#12 ファイルを読み込みます — 最も一般的な出発点。
長期レベル(B-LT、B-LTA)には、失効データを取得して埋め込むために sgcSign_OCSP と sgcSign_CRL も必要です。
uses Classes, SysUtils, // sgc sgcSign_KeyProvider_PFX, sgcSign_XML, sgcSign_Profile_XAdES, sgcSign_TSA, sgcSign_OCSP, sgcSign_CRL, sgcSign_Verifier;
2 つの入力 — 署名したい XML と署名に使う PFX 証明書。
TsgcPFXKeyProvider は Windows CNG 経由で PKCS#12 ファイルをインポートします。これにより、証明書が元々どの CSP 向けに発行されたかに関係なく、モダンな署名ハンドルが得られます。同じプロバイダーは Windows 7 以降、32-bit と 64-bit で動作します。
プロバイダーを完全な署名操作中に生かしておいてください — XML 署名者は SignXML が戻るまで基盤の鍵ハンドルを参照します。
var vXML: string; vKeyProvider: TsgcPFXKeyProvider; begin vXML := TFile.ReadAllText('invoice.xml', TEncoding.UTF8); vKeyProvider := TsgcPFXKeyProvider.Create(nil); vKeyProvider.FileName := 'certificate.pfx'; vKeyProvider.Password := 'secret'; vKeyProvider.LoadFromFile; end;
B-T は署名タイムスタンプを追加。B-LT はさらに CA チェーンと失効データを埋め込むため、署名は何年も検証可能なまま保たれます。
Level は AdES レベルを選びます: xalB、xalT、xalLT、xalLTA。Packaging は xpkEnveloped(デフォルト)、xpkEnveloping、xpkDetached を選びます。
Transforms リストはデフォルトで xtEnvelopedSignature + xtC14NExclusive で、ほとんどの電子請求書スキーマが必須とするものです。特定の国別プロファイルが包括的 C14N やカスタム XSLT 変換を要求する場合のみオーバーライドしてください。
B-LT では、OCSP.AutoFetch := True により、署名者がチェーン内のすべての証明書の OCSP レスポンスを取得し、RevocationValues 要素内に埋め込むよう指示します。署名済み XML は、検証者が必要とするすべてを運ぶようになります — 検証時のネットワーク呼び出しは不要。
var vProfile: TsgcSignProfile_XAdES; begin vProfile := TsgcSignProfile_XAdES.Create(nil); vProfile.Level := xalT; // or xalLT, xalLTA vProfile.Packaging := xpkEnveloped; vProfile.HashAlgorithm := shaSHA256; // Timestamp authority for B-T and above vProfile.TSA.URL := 'https://freetsa.org/tsr'; vProfile.TSA.HashAlgorithm := shaSHA256; // For B-LT: embed full chain and revocation data vProfile.OCSP.AutoFetch := True; vProfile.CRL.AutoFetch := True; end;
同じ TsgcSignXML コンポーネントが 3 つのパッケージングモードすべてを処理します — Packaging プロパティのみが異なります。
請求書のデフォルト。署名はドキュメントルート内に追加され、enveloped-signature 変換を使用してダイジェストから自身を除外します。
スキーマ対応ペイロード(FatturaPA、TicketBAI、KSeF)では、署名が正しい XML 名前空間を継承するよう RootNamespace を設定してください — 国別プロファイルが自動的にこれを設定します。
var vSigner: TsgcSignXML; vSigned: string; begin vSigner := TsgcSignXML.Create(nil); try vSigner.KeyProvider := vKeyProvider; vSigner.Profile := vProfile; // Packaging = xpkEnveloped vSigned := vSigner.SignXML(vXML); TFile.WriteAllText('invoice-signed.xml', vSigned, TEncoding.UTF8); finally vSigner.Free; end; end;
プロファイルのパッケージングを切り替え、それ以外は同じに保ちます。detached 署名では、DetachedURI を署名されるドキュメントのパスまたは URL に設定してください。検証者がデータを取得するためにその参照を必要とします。
detached 署名を受け取ったときは、署名 XML と元のドキュメントバイトの両方を VerifyDetached に渡します — sgcSign がダイジェストを再計算し、バインディングを確認します。
// Enveloping: original XML wrapped in a ds:Object vProfile.Packaging := xpkEnveloping; vSigned := vSigner.SignXML(vXML); // Detached: signature file points at invoice.xml vProfile.Packaging := xpkDetached; vProfile.DetachedURI := 'invoice.xml'; vSigned := vSigner.SignXML(vXML); TFile.WriteAllText('invoice.sig.xml', vSigned, TEncoding.UTF8);
すべての署名済みドキュメントは、ワークフローを出荷する前に検証しなければなりません。
VerifyXML は検出された AdES レベル、署名者サブジェクト、署名タイムスタンプ、チェーンや失効問題を持つ TsgcSignatureReport を返します。enveloped 署名では、検証者が自動的に ds:Signature 要素を見つけます。detached 署名では VerifyDetached を使用し、元のドキュメントバイトを供給してください。
Status が svValid ならダイジェストは一致し、証明書チェーンは信頼されたルートに固定され、タイムスタンプは無傷です。StatusDetail に理由が入った svInvalid が典型的な失敗モード。svUnknown は検証者が OCSP レスポンダーまたは CRL 配布ポイントに到達できなかったことを意味します。
var vVerifier: TsgcSignatureVerifier; vReport: TsgcSignatureReport; begin vVerifier := TsgcSignatureVerifier.Create(nil); try vReport := vVerifier.VerifyXML(vSigned); Memo1.Lines.Add('Level: ' + vReport.Signatures[0].LevelAsString); Memo1.Lines.Add('Status: ' + vReport.Signatures[0].StatusAsString); Memo1.Lines.Add('Signer: ' + vReport.Signatures[0].Subject); Memo1.Lines.Add('TSA: ' + DateTimeToStr(vReport.Signatures[0].TimestampUTC)); finally vVerifier.Free; end; end;
開発者が最初の XAdES エンベロープに署名するときに最もよく見る問題。
署名済み XML をきれいに整形すること — テキストエディタ、IDE、または XSLT を通すこと — はダイジェストを壊します。Exclusive C14N はあらゆるバイトに敏感です。署名済み XML はバイトとして保存し、バイトとして転送し、決して再整形しないでください。
XML の先頭の UTF-8 BOM は、特にドキュメントが Notepad で書かれたときの、ダイジェスト不一致の頻繁な原因です。署名前に BOM を除去してください — sgcSign は内部で正規化しますが、他の検証者はしないかもしれません。
イタリアの電子請求書ポータルは、アーカイブ時に長期失効ルックアップを実行できないため XAdES-B-T を拒否します。OCSP.AutoFetch = True 付きの xalLT を使用すると、OCSP レスポンスがドキュメントと共に旅します。
DetachedURI が相対パスの場合、検証者は自身の作業ディレクトリに対して解決します。マシン間のフローでは、絶対 URL を使うか、ドキュメントバイトを直接 VerifyDetached に渡してください。
国別プロファイルが XAdES の構成を代行します。サーバーはビルドファーム全体で署名を集約します。