Tutorial: XAdES XML Signing in Delphi

A practical walkthrough that takes a plain XML invoice and produces a XAdES-B-T document — the level required by FatturaPA, FACTUR-X and most e-invoicing portals across Europe. You will choose between enveloped, enveloping and detached signature packaging, attach an RFC 3161 timestamp, and verify the result so you know exactly what your counter-party will see.

XAdES-B-B / B-T / B-LT / B-LTA
Enveloped / Enveloping / Detached
Delphi 7 – RAD Studio 13

Three packaging modes, one engine

XAdES extends the W3C XML-DSig specification with timestamping and long-term validation data. Before you sign anything, decide where the ds:Signature element will live.

Enveloped

The ds:Signature element is appended inside the document root. This is the format used by FatturaPA, FACTUR-X-XAdES and most e-invoicing flows. The signed XML is still a valid copy of the original document — readers ignore the signature element they do not understand.

Enveloping

The original XML becomes a ds:Object child inside the ds:Signature. Useful when you need a single self-contained envelope and do not care whether existing consumers can keep parsing the document.

Detached

The signature sits in a separate file and points at the original document via a URI reference. Common in SAML, ebXML and any flow where the source document must be byte-for-byte untouched.

Add the units

XAdES needs the XML signer, the XAdES profile and a key provider. We use PFX in this tutorial; swap in any of the 10 providers if you have a smart card or a cloud HSM.

Delphi uses clause

sgcSign_XML is the XML signing core. sgcSign_Profile_XAdES exposes the level, transforms and timestamp configuration. sgcSign_KeyProvider_PFX loads a PKCS#12 file from disk — the most common starting point.

For long-term levels (B-LT, B-LTA) you also need sgcSign_OCSP and sgcSign_CRL so revocation data can be fetched and embedded.

uXAdESSigning.pas
uses
  Classes, SysUtils,
  // sgc
  sgcSign_KeyProvider_PFX,
  sgcSign_XML,
  sgcSign_Profile_XAdES,
  sgcSign_TSA,
  sgcSign_OCSP,
  sgcSign_CRL,
  sgcSign_Verifier;

Load the XML and key

Two inputs — the XML you want to sign and the PFX certificate you sign with.

PFX key provider

TsgcPFXKeyProvider imports the PKCS#12 file via Windows CNG, which means you get a modern signing handle regardless of which CSP the certificate was originally issued for. The same provider works on Windows 7 onward, 32-bit and 64-bit.

Keep the provider alive for the full signing operation — the XML signer references the underlying key handle until SignXML returns.

step1-load.pas
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;

Choose XAdES-B-T or XAdES-B-LT

B-T adds a signature timestamp. B-LT additionally embeds the CA chain and revocation data so the signature stays verifiable for years.

Profile configuration

Level picks the AdES level: xalB, xalT, xalLT, xalLTA. Packaging chooses xpkEnveloped (default), xpkEnveloping or xpkDetached.

The Transforms list defaults to xtEnvelopedSignature + xtC14NExclusive, which is what most e-invoicing schemas mandate. Override only when a specific country profile asks for inclusive C14N or for a custom XSLT transform.

For B-LT, OCSP.AutoFetch := True tells the signer to retrieve OCSP responses for every certificate in the chain and embed them inside the RevocationValues element. The signed XML then carries everything a verifier needs — no network call required at validation time.

step2-profile.pas
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;

Sign — enveloped, enveloping and detached

The same TsgcSignXML component handles all three packaging modes — only the Packaging property differs.

Enveloped signature

The default for invoices. The signature is appended inside the document root and uses the enveloped-signature transform to exclude itself from the digest.

For schema-aware payloads (FatturaPA, TicketBAI, KSeF), set RootNamespace so the signature inherits the right XML namespace — the country profiles set this automatically.

step3-enveloped.pas
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;

Enveloping and detached

Switch the packaging on the profile, keep everything else the same. For detached signatures, set DetachedURI to the path or URL of the document being signed; the verifier needs that reference to fetch the data.

When you receive a detached signature back, pass both the signature XML and the original document bytes to VerifyDetached — sgcSign re-computes the digest and confirms the binding.

step3-other.pas
// 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);

Verify the signed XML

Every signed document must be verified before you ship the workflow.

One call, full report

VerifyXML returns a TsgcSignatureReport with the AdES level detected, the signer subject, the signature timestamp and any chain or revocation issues. For enveloped signatures the verifier locates the ds:Signature element automatically; for detached signatures use VerifyDetached and supply the original document bytes.

If Status is svValid the digest matches, the certificate chain anchors at a trusted root and the timestamp is intact. svInvalid with the reason in StatusDetail is the typical failure mode; svUnknown means the verifier could not reach an OCSP responder or CRL distribution point.

step4-verify.pas
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;

What usually goes wrong

Issues we see most often when developers sign their first XAdES envelope.

Whitespace and canonicalisation

Pretty-printing a signed XML — in a text editor, in an IDE, or by passing it through an XSLT — breaks the digest. Exclusive C14N is sensitive to every byte. Store the signed XML as bytes, transport it as bytes, never reformat.

BOM in the input document

UTF-8 BOM at the start of the XML is a frequent source of mismatched digests, especially when the document is written by Notepad. Strip the BOM before signing — sgcSign normalises internally but other verifiers may not.

FatturaPA needs xalLT

The Italian e-invoicing portal rejects XAdES-B-T because it cannot perform long-term revocation lookups at archive time. Use xalLT with OCSP.AutoFetch = True so the OCSP response travels with the document.

Detached signatures and URI resolution

If the DetachedURI is a relative path, the verifier resolves it against its own working directory. For cross-machine flows use an absolute URL or pass the document bytes directly to VerifyDetached.

Where to go from here

Country profiles do the XAdES configuration for you. The server centralises signing across the build farm.

Country Profiles

VeriFactu, FatturaPA, KSeF, FACTUR-X, TicketBAI — one constant switches every transform, algorithm and required attribute.

Read more →

PAdES Tutorial

Same engine, applied to PDF instead of XML. Read the PAdES walkthrough to compare the two formats side by side.

Read more →

sgcSign Server

REST API, GitHub Actions integration, Docker and Helm. Offload signing from individual developers to a controlled service.

Read more →

Ready to sign your first XML?

Download the trial, run this tutorial against your own invoice, license sgcSign when you ship.