Tutorial: assinatura XML XAdES em Delphi

Um passo a passo prático que pega uma nota fiscal em XML simples e produz um documento XAdES-B-T — o nível exigido pela FatturaPA, FACTUR-X e pela maioria dos portais de faturamento eletrônico na Europa. Você vai escolher entre enveloped, enveloping e detached como empacotamento da assinatura, anexar um timestamp RFC 3161 e verificar o resultado para saber exatamente o que sua contraparte verá.

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

Três modos de empacotamento, um motor

O XAdES estende a especificação W3C XML-DSig com timestamping e dados de validação de longo prazo. Antes de assinar qualquer coisa, decida onde o elemento ds:Signature vai morar.

Enveloped

O elemento ds:Signature é anexado dentro da raiz do documento. É o formato usado por FatturaPA, FACTUR-X-XAdES e pela maioria dos fluxos de faturamento eletrônico. O XML assinado continua sendo uma cópia válida do documento original — os leitores ignoram o elemento de assinatura que não entendem.

Enveloping

O XML original se torna um filho ds:Object dentro do ds:Signature. Útil quando você precisa de um único envelope autônomo e não se importa se os consumidores existentes ainda conseguirão fazer parse do documento.

Detached

A assinatura fica em um arquivo separado e aponta para o documento original por uma referência URI. Comum em SAML, ebXML e em qualquer fluxo em que o documento de origem precise ficar byte a byte intacto.

Adicione as units

O XAdES precisa do signer XML, do perfil XAdES e de um provedor de chave. Usamos PFX neste tutorial; troque por qualquer um dos 10 provedores se tiver um smart card ou um HSM em nuvem.

Cláusula uses Delphi

O sgcSign_XML é o núcleo de assinatura XML. O sgcSign_Profile_XAdES expõe o nível, transforms e configuração de timestamp. O sgcSign_KeyProvider_PFX carrega um arquivo PKCS#12 do disco — o ponto de partida mais comum.

Para níveis de longo prazo (B-LT, B-LTA), você também precisa de sgcSign_OCSP e sgcSign_CRL para que os dados de revogação possam ser buscados e embutidos.

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

Carregue o XML e a chave

Duas entradas — o XML que você quer assinar e o certificado PFX com o qual você assina.

Provedor de chave PFX

O TsgcPFXKeyProvider importa o arquivo PKCS#12 via Windows CNG, o que significa que você ganha um handle de assinatura moderno independentemente do CSP em que o certificado foi originalmente emitido. O mesmo provedor funciona do Windows 7 em diante, 32 e 64 bits.

Mantenha o provedor vivo durante toda a operação de assinatura — o signer XML referencia o handle de chave subjacente até SignXML retornar.

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;

Escolha XAdES-B-T ou XAdES-B-LT

B-T adiciona um timestamp de assinatura. B-LT, adicionalmente, embute a cadeia da CA e os dados de revogação para que a assinatura permaneça verificável por anos.

Configuração do perfil

Level escolhe o nível AdES: xalB, xalT, xalLT, xalLTA. Packaging escolhe xpkEnveloped (padrão), xpkEnveloping ou xpkDetached.

A lista de Transforms tem como padrão xtEnvelopedSignature + xtC14NExclusive, que é o que a maioria dos esquemas de faturamento eletrônico exige. Sobrescreva apenas quando um perfil de país específico pedir C14N inclusivo ou uma transformação XSLT customizada.

Para B-LT, OCSP.AutoFetch := True diz ao signer para buscar respostas OCSP para cada certificado da cadeia e embuti-las dentro do elemento RevocationValues. O XML assinado então carrega tudo o que um verificador precisa — nenhuma chamada de rede é necessária no momento da validação.

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;

Assine — enveloped, enveloping e detached

O mesmo componente TsgcSignXML trata os três modos de empacotamento — só muda a propriedade Packaging.

Assinatura enveloped

O padrão para notas fiscais. A assinatura é anexada dentro da raiz do documento e usa o transform enveloped-signature para se excluir do digest.

Para payloads schema-aware (FatturaPA, TicketBAI, KSeF), defina RootNamespace para que a assinatura herde o namespace XML correto — os perfis por país fazem isso 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

Troque o empacotamento no perfil, mantenha o resto igual. Para assinaturas detached, defina DetachedURI como o caminho ou URL do documento assinado; o verificador precisa dessa referência para buscar os dados.

Quando você receber uma assinatura detached de volta, passe ao mesmo tempo o XML da assinatura e os bytes do documento original para VerifyDetached — o sgcSign recalcula o digest e confirma o vínculo.

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

Verifique o XML assinado

Todo documento assinado precisa ser verificado antes de subir o fluxo.

Uma chamada, relatório completo

VerifyXML retorna um TsgcSignatureReport com o nível AdES detectado, o subject do signatário, o timestamp da assinatura e quaisquer problemas de cadeia ou revogação. Para assinaturas enveloped, o verificador localiza o elemento ds:Signature automaticamente; para assinaturas detached use VerifyDetached e forneça os bytes do documento original.

Se Status for svValid, o digest bate, a cadeia de certificados ancora em uma raiz confiável e o timestamp está intacto. svInvalid com a razão em StatusDetail é o modo de falha típico; svUnknown significa que o verificador não conseguiu alcançar um responder OCSP ou ponto de distribuição de 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;

O que costuma dar errado

Problemas que mais vemos quando desenvolvedores assinam seu primeiro envelope XAdES.

Espaços em branco e canonicalização

Pretty-printing de um XML assinado — em um editor de texto, em uma IDE ou passando por um XSLT — quebra o digest. C14N exclusivo é sensível a cada byte. Armazene o XML assinado como bytes, transporte como bytes, nunca reformate.

BOM no documento de entrada

BOM UTF-8 no início do XML é uma fonte frequente de digests incompatíveis, especialmente quando o documento é escrito pelo Notepad. Remova o BOM antes de assinar — o sgcSign normaliza internamente, mas outros verificadores podem não fazê-lo.

FatturaPA precisa de xalLT

O portal italiano de faturamento eletrônico rejeita XAdES-B-T porque não consegue fazer lookups de revogação de longo prazo no momento do arquivamento. Use xalLT com OCSP.AutoFetch = True para que a resposta OCSP viaje junto com o documento.

Assinaturas detached e resolução de URI

Se o DetachedURI for um caminho relativo, o verificador o resolve contra seu próprio diretório de trabalho. Para fluxos entre máquinas, use uma URL absoluta ou passe os bytes do documento diretamente para VerifyDetached.

Para onde ir a partir daqui

Os perfis por país fazem a configuração XAdES por você. O servidor centraliza a assinatura em todo o build farm.

Perfis por país

VeriFactu, FatturaPA, KSeF, FACTUR-X, TicketBAI — uma única constante troca todo transform, algoritmo e atributo obrigatório.

Leia mais →

Tutorial PAdES

Mesmo motor, aplicado a PDF em vez de XML. Leia o walkthrough PAdES para comparar os dois formatos lado a lado.

Leia mais →

sgcSign Server

API REST, integração com GitHub Actions, Docker e Helm. Tire a assinatura de desenvolvedores individuais e a coloque em um serviço controlado.

Leia mais →

Pronto para assinar seu primeiro XML?

Baixe o trial, rode este tutorial contra sua própria nota fiscal, licencie o sgcSign quando subir para produção.