Tutorial: firma XML XAdES in Delphi

Una guida pratica che parte da una fattura XML semplice e produce un documento XAdES-B-T — il livello richiesto da FatturaPA, FACTUR-X e dalla maggior parte dei portali di fatturazione elettronica in Europa. Sceglierai tra packaging enveloped, enveloping e detached, allegherai un timestamp RFC 3161 e verificherai il risultato per sapere esattamente cosa vedrà la tua controparte.

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

Tre modalità di packaging, un solo engine

XAdES estende la specifica W3C XML-DSig con timestamping e dati per la validazione a lungo termine. Prima di firmare qualcosa, decidi dove vivrà l'elemento ds:Signature.

Enveloped

L'elemento ds:Signature viene aggiunto all'interno della radice del documento. È il formato usato da FatturaPA, FACTUR-X-XAdES e dalla maggior parte dei flussi di fatturazione elettronica. L'XML firmato resta una copia valida del documento originale — i lettori ignorano l'elemento firma che non comprendono.

Enveloping

L'XML originale diventa un figlio ds:Object all'interno del ds:Signature. Utile quando hai bisogno di un singolo envelope autocontenuto e non ti importa se i consumer esistenti possano continuare a fare il parsing del documento.

Detached

La firma sta in un file separato e punta al documento originale tramite un riferimento URI. Comune in SAML, ebXML e in qualunque flusso in cui il documento sorgente deve restare intatto byte per byte.

Aggiungi le unit

XAdES richiede il firmatario XML, il profilo XAdES e un key provider. In questo tutorial usiamo un PFX; sostituiscilo con uno qualsiasi dei 10 provider se hai una smart card o un HSM cloud.

Clausola uses di Delphi

sgcSign_XML è il core di firma XML. sgcSign_Profile_XAdES espone il livello, le trasformazioni e la configurazione del timestamp. sgcSign_KeyProvider_PFX carica un file PKCS#12 da disco — il punto di partenza più comune.

Per i livelli a lungo termine (B-LT, B-LTA) servono anche sgcSign_OCSP e sgcSign_CRL per recuperare e integrare i dati di revoca.

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

Carica l'XML e la chiave

Due input — l'XML che vuoi firmare e il certificato PFX con cui firmare.

Key provider PFX

TsgcPFXKeyProvider importa il file PKCS#12 via Windows CNG, il che significa che ottieni un handle di firma moderno indipendentemente da quale CSP è stato originariamente usato per emettere il certificato. Lo stesso provider funziona da Windows 7 in poi, a 32 e 64 bit.

Tieni in vita il provider per tutta l'operazione di firma — il firmatario XML referenzia l'handle della chiave sottostante finché SignXML non torna.

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;

Scegli XAdES-B-T o XAdES-B-LT

B-T aggiunge un timestamp di firma. B-LT integra inoltre la catena della CA e i dati di revoca, così la firma resta verificabile per anni.

Configurazione del profilo

Level sceglie il livello AdES: xalB, xalT, xalLT, xalLTA. Packaging sceglie xpkEnveloped (default), xpkEnveloping o xpkDetached.

La lista Transforms ha come default xtEnvelopedSignature + xtC14NExclusive, che è quanto la maggior parte degli schemi di fatturazione elettronica impone. Sovrascrivila solo quando uno specifico profilo nazionale richiede C14N inclusivo o una trasformazione XSLT personalizzata.

Per B-LT, OCSP.AutoFetch := True dice al firmatario di recuperare le risposte OCSP per ogni certificato della catena e integrarle nell'elemento RevocationValues. L'XML firmato porta quindi con sé tutto ciò di cui un verificatore ha bisogno — nessuna chiamata di rete necessaria al momento della validazione.

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;

Firma — enveloped, enveloping e detached

Lo stesso componente TsgcSignXML gestisce tutte e tre le modalità di packaging — cambia solo la proprietà Packaging.

Firma enveloped

Il default per le fatture. La firma viene appesa all'interno della radice del documento e usa la trasformazione enveloped-signature per escludersi dal digest.

Per payload schema-aware (FatturaPA, TicketBAI, KSeF), imposta RootNamespace in modo che la firma erediti il namespace XML corretto — i profili nazionali lo impostano automaticamente.

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 e detached

Cambia il packaging sul profilo, lascia tutto il resto invariato. Per le firme detached, imposta DetachedURI sul percorso o URL del documento firmato; il verificatore ha bisogno di quel riferimento per recuperare i dati.

Quando ricevi indietro una firma detached, passa sia l'XML della firma sia i byte del documento originale a VerifyDetached — sgcSign ricalcola il digest e conferma il legame.

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);

Verifica l'XML firmato

Ogni documento firmato deve essere verificato prima di mettere il workflow in produzione.

Una chiamata, report completo

VerifyXML restituisce un TsgcSignatureReport con il livello AdES rilevato, il subject del firmatario, il timestamp della firma ed eventuali problemi di catena o revoca. Per le firme enveloped il verificatore individua automaticamente l'elemento ds:Signature; per le firme detached usa VerifyDetached e fornisci i byte del documento originale.

Se Status è svValid il digest corrisponde, la catena di certificati si ancora a una radice fidata e il timestamp è intatto. svInvalid con il motivo in StatusDetail è la modalità tipica di fallimento; svUnknown significa che il verificatore non è riuscito a raggiungere un responder OCSP o un punto di distribuzione CRL.

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;

Cosa va storto di solito

Problemi che vediamo più spesso quando gli sviluppatori firmano il loro primo envelope XAdES.

Whitespace e canonicalizzazione

Riformattare un XML firmato — in un editor di testo, in un IDE, o passandolo attraverso un XSLT — rompe il digest. La C14N esclusiva è sensibile a ogni singolo byte. Memorizza l'XML firmato come byte, trasportalo come byte, non riformattarlo mai.

BOM nel documento di input

Il BOM UTF-8 all'inizio dell'XML è una frequente causa di digest discordanti, soprattutto quando il documento è scritto da Notepad. Rimuovi il BOM prima di firmare — sgcSign normalizza internamente, ma altri verificatori potrebbero non farlo.

FatturaPA richiede xalLT

Il portale italiano di fatturazione elettronica rifiuta XAdES-B-T perché non può eseguire lookup di revoca a lungo termine al momento dell'archiviazione. Usa xalLT con OCSP.AutoFetch = True in modo che la risposta OCSP viaggi con il documento.

Firme detached e risoluzione dell'URI

Se DetachedURI è un percorso relativo, il verificatore lo risolve rispetto alla propria directory di lavoro. Per flussi tra macchine diverse usa un URL assoluto o passa i byte del documento direttamente a VerifyDetached.

Dove andare da qui

I profili nazionali fanno la configurazione XAdES per te. Il server centralizza la firma su tutta la build farm.

Profili nazionali

VeriFactu, FatturaPA, KSeF, FACTUR-X, TicketBAI — una sola costante cambia ogni trasformazione, algoritmo e attributo richiesto.

Leggi di più →

Tutorial PAdES

Stesso engine, applicato a PDF invece che a XML. Leggi la guida PAdES per confrontare i due formati fianco a fianco.

Leggi di più →

sgcSign Server

API REST, integrazione GitHub Actions, Docker e Helm. Sposta la firma dai singoli sviluppatori a un servizio controllato.

Leggi di più →

Pronto a firmare il tuo primo XML?

Scarica la trial, esegui questo tutorial sulla tua fattura, e quando spedisci acquista la licenza di sgcSign.