Samouczek: Podpisywanie XML XAdES w Delphi

Praktyczny przewodnik, który bierze zwykłą fakturę XML i produkuje dokument XAdES-B-T — poziom wymagany przez FatturaPA, FACTUR-X i większość portali e-fakturowania w Europie. Wybierzesz między pakowaniem enveloped, enveloping i detached, dołączysz znacznik czasu RFC 3161 i zweryfikujesz wynik, abyś dokładnie wiedział, co zobaczy Twój kontrahent.

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

Trzy tryby pakowania, jeden silnik

XAdES rozszerza specyfikację W3C XML-DSig o znaczniki czasu i dane długoterminowej walidacji. Zanim cokolwiek podpiszesz, zdecyduj, gdzie element ds:Signature będzie żył.

Enveloped

Element ds:Signature jest dołączany wewnątrz root dokumentu. To format używany przez FatturaPA, FACTUR-X-XAdES i większość przepływów e-fakturowania. Podpisany XML jest nadal ważną kopią oryginalnego dokumentu — czytniki ignorują element podpisu, którego nie rozumieją.

Enveloping

Oryginalny XML staje się dzieckiem ds:Object wewnątrz ds:Signature. Przydatne, gdy potrzebujesz pojedynczej samodzielnej koperty i nie zależy Ci, czy istniejący konsumenci mogą nadal parsować dokument.

Detached

Podpis siedzi w osobnym pliku i wskazuje na oryginalny dokument przez referencję URI. Powszechne w SAML, ebXML i każdym przepływie, w którym dokument źródłowy musi być nietknięty bajt po bajcie.

Dodaj jednostki

XAdES potrzebuje XML signer, profilu XAdES i dostawcy kluczy. Używamy PFX w tym samouczku; podmień dowolnego z 10 dostawców, jeśli masz kartę inteligentną lub chmurowy HSM.

Klauzula uses w Delphi

sgcSign_XML to rdzeń podpisywania XML. sgcSign_Profile_XAdES udostępnia konfigurację poziomu, transformacji i znacznika czasu. sgcSign_KeyProvider_PFX ładuje plik PKCS#12 z dysku — najczęstszy punkt startu.

Dla poziomów długoterminowych (B-LT, B-LTA) potrzebujesz również sgcSign_OCSP i sgcSign_CRL, aby dane unieważnienia mogły być pobrane i osadzone.

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

Załaduj XML i klucz

Dwa wejścia — XML, który chcesz podpisać, i certyfikat PFX, którym podpisujesz.

Dostawca kluczy PFX

TsgcPFXKeyProvider importuje plik PKCS#12 przez Windows CNG, co oznacza, że dostajesz nowoczesny uchwyt podpisywania niezależnie od tego, dla którego CSP certyfikat został pierwotnie wystawiony. Ten sam dostawca działa na Windows 7 i nowszych, 32-bit i 64-bit.

Trzymaj dostawcę żywego przez pełną operację podpisywania — XML signer odwołuje się do bazowego uchwytu klucza do czasu zwrócenia SignXML.

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;

Wybierz XAdES-B-T lub XAdES-B-LT

B-T dodaje znacznik czasu podpisu. B-LT dodatkowo osadza łańcuch CA i dane unieważnienia, więc podpis pozostaje weryfikowalny przez lata.

Konfiguracja profilu

Level wybiera poziom AdES: xalB, xalT, xalLT, xalLTA. Packaging wybiera xpkEnveloped (domyślnie), xpkEnveloping lub xpkDetached.

Lista Transforms domyślnie ma xtEnvelopedSignature + xtC14NExclusive, czego wymaga większość schematów e-fakturowania. Nadpisuj tylko wtedy, gdy konkretny profil krajowy prosi o inclusive C14N lub o niestandardową transformację XSLT.

Dla B-LT, OCSP.AutoFetch := True mówi signerowi, by pobrał odpowiedzi OCSP dla każdego certyfikatu w łańcuchu i osadził je wewnątrz elementu RevocationValues. Podpisany XML przenosi wtedy wszystko, czego potrzebuje weryfikator — bez wywołania sieciowego w czasie walidacji.

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;

Podpisz — enveloped, enveloping i detached

Ten sam komponent TsgcSignXML obsługuje wszystkie trzy tryby pakowania — różni się tylko właściwość Packaging.

Podpis enveloped

Domyślny dla faktur. Podpis jest dołączony wewnątrz root dokumentu i używa transformacji enveloped-signature, by wykluczyć siebie z digestu.

Dla ładunków świadomych schematu (FatturaPA, TicketBAI, KSeF) ustaw RootNamespace, aby podpis odziedziczył właściwą przestrzeń nazw XML — profile krajowe ustawiają to automatycznie.

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

Przełącz pakowanie na profilu, resztę zostaw taką samą. Dla podpisów detached ustaw DetachedURI na ścieżkę lub URL podpisywanego dokumentu; weryfikator potrzebuje tej referencji, aby pobrać dane.

Kiedy otrzymasz z powrotem podpis detached, przekaż zarówno XML podpisu, jak i oryginalne bajty dokumentu do VerifyDetached — sgcSign ponownie oblicza digest i potwierdza powiązanie.

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

Zweryfikuj podpisany XML

Każdy podpisany dokument musi być zweryfikowany, zanim wdrożysz przepływ pracy.

Jedno wywołanie, pełny raport

VerifyXML zwraca TsgcSignatureReport z wykrytym poziomem AdES, podmiotem podpisującego, znacznikiem czasu podpisu i wszelkimi problemami z łańcuchem lub unieważnieniem. Dla podpisów enveloped weryfikator automatycznie lokalizuje element ds:Signature; dla podpisów detached użyj VerifyDetached i dostarcz oryginalne bajty dokumentu.

Jeśli Status to svValid, digest się zgadza, łańcuch certyfikatów zakotwicza się na zaufanym roocie, a znacznik czasu jest nietknięty. svInvalid z powodem w StatusDetail to typowy tryb awarii; svUnknown oznacza, że weryfikator nie mógł osiągnąć respondera OCSP lub punktu dystrybucji 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;

Co zwykle idzie nie tak

Problemy, które widzimy najczęściej, gdy deweloperzy podpisują swoją pierwszą kopertę XAdES.

Białe znaki i kanonikalizacja

Pretty-printing podpisanego XML — w edytorze tekstu, w IDE lub przez przepuszczenie go przez XSLT — psuje digest. Exclusive C14N jest wrażliwa na każdy bajt. Przechowuj podpisany XML jako bajty, transportuj jako bajty, nigdy nie formatuj ponownie.

BOM w dokumencie wejściowym

BOM UTF-8 na początku XML to częste źródło niedopasowanych digestów, zwłaszcza gdy dokument jest zapisany przez Notepad. Usuń BOM przed podpisaniem — sgcSign normalizuje wewnętrznie, ale inni weryfikatorzy mogą nie.

FatturaPA potrzebuje xalLT

Włoski portal e-fakturowania odrzuca XAdES-B-T, ponieważ nie może przeprowadzić długoterminowych wyszukiwań unieważnień w czasie archiwizacji. Użyj xalLT z OCSP.AutoFetch = True, aby odpowiedź OCSP podróżowała z dokumentem.

Podpisy detached i rozwiązywanie URI

Jeśli DetachedURI jest ścieżką względną, weryfikator rozwiązuje ją względem swojego własnego katalogu roboczego. Dla przepływów między maszynami użyj absolutnego URL lub przekaż bajty dokumentu bezpośrednio do VerifyDetached.

Dokąd dalej

Profile krajowe wykonują konfigurację XAdES za Ciebie. Serwer centralizuje podpisywanie w całej farmie kompilacji.

Profile krajowe

VeriFactu, FatturaPA, KSeF, FACTUR-X, TicketBAI — jedna stała przełącza każdą transformację, algorytm i wymagany atrybut.

Czytaj więcej →

Samouczek PAdES

Ten sam silnik zastosowany do PDF zamiast XML. Przeczytaj przewodnik PAdES, aby porównać oba formaty obok siebie.

Czytaj więcej →

sgcSign Server

REST API, integracja z GitHub Actions, Docker i Helm. Odciąż podpisywanie z pojedynczych deweloperów do kontrolowanej usługi.

Czytaj więcej →

Gotowy podpisać swój pierwszy XML?

Pobierz wersję próbną, uruchom ten samouczek na swojej własnej fakturze, licencjonuj sgcSign przy wdrożeniu.