Chyby validace XML Signature v Peppol
Peppol SMP odpovědi jsou podepsané XML dokumenty. Validace těchto podpisů by měla být přímočará: zparsujte dokument, najděte element Signature, ověřte referenční digest a hodnotu podpisu. V praxi selhává častěji, než by mělo, a způsob selhání je natolik jemný, že nad ním můžete strávit dny.
Kořenová příčina je téměř vždy Canonical XML (C14N) interagující s obsluhou prefixů jmenných prostorů. Tento článek vysvětluje přesně, co se pokazí a jak to opravit.
Proč existuje Canonical XML
XML dokument lze serializovat více způsoby, které jsou sémanticky identické, ale byte-za-bajt odlišné. Atributy mohou být v libovolném pořadí. Deklarace jmenných prostorů mohou být na libovolném předchůdcovském elementu. Mezery v obsahu elementu se mohou lišit. Jakákoli z těchto změn produkuje jiný bytový řetězec, a tedy jiný haš.
Canonical XML (C14N, standard W3C) definuje normalizační algoritmus, který transformuje jakýkoli sémanticky ekvivalentní XML dokument do jediné kanonické formy. XMLDSig používá C14N pro výpočet stabilního haše nad dokumentem nebo jeho částí.
Pokud vypočítáte digest nad kanonizovaným XML a někdo dokument před vaším ověřením znovu serializuje, re-serializovaná verze může produkovat jinou kanonickou formu, jiný haš a selhání ověření, i když je dokument sémanticky nezměněn.
Problém Peppol SMP podpisu
Když přístupový bod podepisuje odpověď SMP, vypočítá:
- Kanonizuje element
ServiceInformation(nebo celý dokument, v závislosti na referenčním URI) - Vypočítá SHA-256 nad kanonickými bajty
- Uloží digest v
Reference/DigestValue - Podepíše element
SignedInfosoukromým klíčem přístupového bodu
Když získáte odpověď SMP přes HTTP, od podpisu se mohlo stát několik věcí:
- SMP server mohl načíst dokument z databáze a znovu ho serializovat
- HTTP vrstva mohla znovu zparsovat a výstup XML
- SOAP/HTTP framework mohl normalizovat mezery
Jakákoli z těchto změn může změnit kanonickou formu, kterou vypočítáte při ověření. I když jsou algoritmus digestu a referenční URI správné, bajty, které kanonizujete, se liší od bajtů, které kanonizoval podepisující.
Povýšení prefixu jmenného prostoru
Nejčastější příčinou je povýšení prefixu jmenného prostoru.
SMP server může podepsat dokument s deklaracemi jmenných prostorů na podřízených elementech:
<ServiceMetadata>
<ServiceInformation xmlns:bdxr="http://docs.oasis-open.org/bdxr/ns/SMP/2016/05">
...
</ServiceInformation>
<Signature>...</Signature>
</ServiceMetadata>
Když je C14N aplikována na ServiceInformation, deklarace jmenného prostoru je zahrnuta do kanonické formy, protože je v rozsahu tohoto elementu.
Když SMP server znovu serializuje pro doručení, mnoho XML knihoven přesouvá deklarace jmenných prostorů na kořenový element:
<ServiceMetadata xmlns:bdxr="http://docs.oasis-open.org/bdxr/ns/SMP/2016/05">
<ServiceInformation>
...
</ServiceInformation>
<Signature>...</Signature>
</ServiceMetadata>
Nyní, když aplikujete C14N na ServiceInformation, jmenný prostor je deklarován na kořeni, nikoli na ServiceInformation. Podle Exclusive C14N (http://www.w3.org/2001/10/xml-exc-c14n#) jsou deklarace jmenných prostorů, které nejsou viditelně využity na kanonizovaném elementu, vyloučeny. Jmenný prostor již není na ServiceInformation; je na ServiceMetadata. Výstup C14N se mění. Digest již neodpovídá.
Dynamické prefixy jmenných prostorů (ns1, ns2, …)
Druhá třída problémů: některé implementace SMP používají dynamicky generované prefixy jmenných prostorů. Místo xmlns:bdxr emitují xmlns:ns1, xmlns:ns2 a tak dále. Tyto prefixy jsou přiřazovány v době serializace a mohou se lišit mezi průchodem podepisování a průchodem doručení.
Pod Inclusive C14N jsou vazby prefixů jmenných prostorů zahrnuty do kanonické formy. Pokud se prefix změní z bdxr: na ns1:, kanonická forma se změní a digest selže.
Pod Exclusive C14N jsou vazby prefixů jmenných prostorů pro prefixy nepoužité v kanonizovaném elementu vyloučeny. Ale pokud se změní samotný prefix (samotná vazba se liší), kanonické bajty pro elementy, které tento prefix používají, se změní.
Opravou je normalizace prefixů jmenných prostorů před ověřením. To vyžaduje přečtení podepsaného podstromu se známými, stabilními vazbami prefixů. Není možné to udělat a zároveň ověřit původní podpis; musíte ověřit proti tomu, co bylo skutečně podepsáno, což znamená pracovat se surovými bajty, které byly podepsány.
Problém .NET SignedXml
Třída .NET SignedXml má konkrétní omezení: vyřazuje reference relativní k dokumentu v paměti. Pokud načtete odpověď SMP do XmlDocument, deklarace jmenných prostorů mohou být přesunuty XML parserem ještě předtím, než SignedXml vůbec uvidí.
Oprava v .NET:
var doc = new XmlDocument
{
PreserveWhitespace = true // critical
};
doc.Load(responseStream); // load from raw bytes, not from a re-serialized string
PreserveWhitespace = true brání XML parseru v normalizaci textových uzlů. Důležitější je, že použití Load() z raw stream (nikoli z řetězce, který byl již zparsován a znovu emitován) zabraňuje průchodu re-serializace, který povyšuje deklarace jmenných prostorů.
Nedělejte toto:
var xml = await response.Content.ReadAsStringAsync();
doc.LoadXml(xml); // string parsing may already have re-serialized
Místo toho udělejte toto:
using var stream = await response.Content.ReadAsStreamAsync();
doc.Load(stream); // raw bytes, no intermediate string
Poté ověřte pomocí SignedXml:
var signedXml = new SignedXml(doc);
var sigElement = doc.GetElementsByTagName("Signature")[0] as XmlElement
?? throw new InvalidOperationException("No Signature element found");
signedXml.LoadXml(sigElement);
// Resolve the signing certificate from the SMP KeyInfo
var cert = GetCertificateFromKeyInfo(signedXml);
if (!signedXml.CheckSignature(cert, verifySignatureOnly: false))
throw new SecurityException("SMP response signature verification failed");
verifySignatureOnly: false znamená, že .NET také validuje referenční digesty, nejen podpis nad SignedInfo. Chcete obě kontroly.
Řetěz důvěry certifikátu
Ověření, že podpis je matematicky platný, nestačí. Musíte také ověřit, že podpisový certifikát je vydán kotevním bodem důvěry Peppol PKI.
OpenPEPPOL publikuje trust store (SML certifikát, kořeny SMP certifikátu) v produkčním a testovacím PKI. Načtěte trust store a ověřte řetěz certifikátů:
static bool IsInPeppolTrustChain(X509Certificate2 cert)
{
var chain = new X509Chain();
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy.CustomTrustStore.Add(peppolRootCert);
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
return chain.Build(cert);
}
Odpověď SMP s platným podpisem od nedůvěryhodného certifikátu je útok, nikoli chybná konfigurace. Selžte tvrdě.
Zpracování referenčního URI
Peppol SMP podpisy typicky používají prázdné referenční URI (URI=""), což znamená, že reference pokrývá celý dokument. Podle C14N to znamená, že celý dokument je kanonizován.
Pokud je referenční URI výraz XPointer (URI="#ID"), reference pokrývá pouze element s tímto ID. Toto je odolnější vůči povýšení jmenných prostorů, protože kanonizujete pouze podstrom, nikoli celý dokument.
Pokud podpis selhává a máte kontrolu nad SMP serverem, upřednostněte reference XPointer před prázdným URI. Pokud server nemůžete kontrolovat, pracujte se surovými bajty.
Testovací přístup
Nejspolehlivější způsob testování ověření SMP podpisu je proti dobře známé odpovědi SMP uložené jako surové bajty z produkčního nebo testovacího Peppol účastníka. Nevytvářejte testovací odpovědi programově; zpracování jmenných prostorů se bude lišit od toho, co produkují skutečné SMP servery.
Testovací síť Peppol (acc.edelivery.tech.ec.europa.eu SML) má skutečné účastníky, na které se lze dotazovat a od nichž lze získávat skutečné podepsané odpovědi SMP. Použijte tyto pro integrační testy.
Shrnutí
Způsoby selhání podle frekvence výskytu:
- Povýšení jmenného prostoru: XML parser přesouvá deklarace jmenných prostorů na kořen při načtení, čímž mění výstup C14N pro podepsaný podstrom
- Re-serializace: převod HTTP odpovědi na řetězec a zpět před ověřením
- Dynamické prefixy jmenných prostorů: SMP server používá různé prefixy při podepisování a doručení
- Chybějící
PreserveWhitespace: .NET XML parser normalizuje mezery, čímž mění haše textových uzlů - Řetěz certifikátů není ověřen: podpis je platný, ale certifikát není z Peppol PKI
Všechny tyto problémy jsou řešitelné bez záplatování SMP serveru. Načtěte ze surových bajtů, nastavte PreserveWhitespace = true a vždy ověřujte řetěz důvěry certifikátu.
SealDoc zpracovává SMP discovery a ověření podpisů interně, včetně všech výše uvedených okrajových případů. Pokud budujete vlastní integraci Peppol a narazíte na selhání podpisů, která nemůžete odladit, SealDoc API vystavuje výsledky vyhledávání SMP včetně stavu platnosti podpisu, který můžete porovnat s výstupem vlastní implementace.