K čemu je podpis XML
Elektronický podpis dokazuje dvě věci: kdo dokument podepsal (podpis jde ověřit jen jeho certifikátem) a že se dokument od podpisu nezměnil (i změna jediného znaku podpis zneplatní). U XML se k tomu používá standard W3C XML Signature (XMLDSig). Podpis je v něm zapsaný jako XML, takže může být přímo součástí podepsaného dokumentu a ten zůstane čitelný pro člověka i pro program.
V Česku se s podepsaným XML potkáš u elektronických faktur ISDOC, u dokumentů pro úřady a u zpráv z datových schránek — tam je ale XML podepsané jinou technikou (viz níže).
Element Signature
Podpis je element <Signature> ve jmenném prostoru
http://www.w3.org/2000/09/xmldsig#. Ukázka má podpis uvnitř podepsaného
dokumentu, jako je to u faktury ISDOC:
<faktura xmlns="http://example.cz/faktura">
<cislo>FV2026-042</cislo>
<castka mena="CZK">12100.00</castka>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<DigestValue>…otisk dokumentu v Base64…</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>…podpis v Base64…</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>…certifikát podepisujícího v Base64…</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
</faktura> | Element | Co obsahuje |
|---|---|
| SignedInfo | co se podepisuje a jak — právě tenhle element se ve skutečnosti podepisuje klíčem |
| CanonicalizationMethod | jak převést SignedInfo na jednotný tvar, než se podepíše |
| SignatureMethod | algoritmus podpisu, např. RSA nebo ECDSA se SHA-256 |
| Reference | odkaz na podepsaná data (URI="" je celý dokument) a jejich otisk |
| Transforms | úpravy dat před výpočtem otisku, např. vynechání samotného podpisu |
| DigestMethod, DigestValue | hashovací algoritmus a otisk podepsaných dat |
| SignatureValue | samotný podpis prvku SignedInfo |
| KeyInfo | certifikát nebo klíč, kterým se podpis ověří |
Program při podpisu nejdřív spočítá otisk (hash) podepisovaných dat a zapíše
ho do DigestValue. Potom soukromým klíčem podepíše celý
SignedInfo, takže podpis chrání otisk dat i volbu algoritmů. Při ověření se
postup opakuje: program znovu spočítá otisk dat, porovná ho s DigestValue a
veřejným klíčem z certifikátu ověří SignatureValue.
Obálkovaný, obalující a oddělený podpis
Podle toho, kde podpis leží vůči datům, rozlišuje XMLDSig tři případy:
| Druh | Kde je podpis | Typické použití |
|---|---|---|
| Obálkovaný (enveloped) | uvnitř podepsaného dokumentu | faktury ISDOC, dokumenty, které mají zůstat čitelné jako dřív |
| Obalující (enveloping) | data jsou uvnitř elementu Signature | podpis libovolných dat včetně ne-XML |
| Oddělený (detached) | vedle dat nebo v jiném souboru | podpis souboru, který se nesmí měnit, nebo části zprávy (SOAP) |
U obálkovaného podpisu nastává zdánlivý paradox: podpis je součástí dokumentu, který
podepisuje. Řeší ho transformace enveloped-signature, která element
Signature před výpočtem otisku z dat vyřadí.
Kanonizace: proč na zápisu záleží
Stejná data jdou v XML zapsat mnoha způsoby: jiné pořadí atributů, jednoduché místo dvojitých
uvozovek, <poznamka/> místo <poznamka></poznamka>,
číselný odkaz místo znaku. Pro parser jde o totéž, pro hashovací funkci o úplně jiné bajty.
Proto se data před výpočtem otisku převádějí do kanonického tvaru (Canonical
XML, C14N). Tyhle dva zápisy:
<?xml version="1.0" encoding="UTF-8"?>
<faktura mena='CZK' cislo="42" >
<poznamka/>
<castka>100</castka>
</faktura> <faktura cislo="42" mena="CZK">
<poznamka></poznamka>
<castka>100</castka>
</faktura>
mají po kanonizaci shodný tvar — ten, který ukazuje zápis B. Kanonizace odstraní deklaraci
XML, seřadí atributy, sjednotí uvozovky a prázdné elementy a nahradí číselné odkazy znaky.
Vyzkoušet si to můžeš příkazem xmllint --c14n soubor.xml.
Pro podpis části dokumentu se používá exkluzivní kanonizace (Exclusive C14N). Ta do podepsaného fragmentu nezahrne deklarace jmenných prostorů, které fragment nepoužívá, takže podpis vydrží i přesunutí fragmentu do jiného dokumentu — typicky u zpráv SOAP.
Podepsané XML neformátuj. Kanonizace neřeší bílé znaky v obsahu elementů. Když podepsaný soubor odsadíš ve formátovači, uložíš s jiným kódováním nebo ho otevřeš a uložíš v editoru, který zapisuje po svém, podpis přestane platit. Ke kontrole obsahu formátuj kopii a originál nech beze změny.
XAdES: podpis s časem a dlouhou platností
Samotný XMLDSig neříká, kdy byl dokument podepsán, a po vypršení certifikátu je těžké
podpis ověřit. Rozšíření XAdES (XML Advanced Electronic Signatures) od
evropského institutu ETSI přidává do elementu Signature podepsané vlastnosti a
časová razítka. Spolu s CAdES a PAdES je to jeden z formátů, které pro zaručené a
kvalifikované podpisy uznává nařízení eIDAS.
| Úroveň (ETSI EN 319 132) | Starší označení | Co přidává |
|---|---|---|
| XAdES-B-B | XAdES-BES | podepsané vlastnosti: čas podpisu a otisk podpisového certifikátu |
| XAdES-B-T | XAdES-T | časové razítko od důvěryhodné autority — dokazuje, že podpis existoval v daném okamžiku |
| XAdES-B-LT | XAdES-X-L | certifikáty a informace o jejich platnosti, aby šel podpis ověřit i po letech |
| XAdES-B-LTA | XAdES-A | archivní razítka, která se obnovují dřív, než zastarají použité algoritmy |
Vlastnosti XAdES leží v elementu <xades:QualifyingProperties> uvnitř
<Object> a mají vlastní jmenný prostor
http://uri.etsi.org/01903/v1.3.2#.
Příklad z praxe: podpis faktury ISDOC
Specifikace ISDOC 6.0.2 předepisuje, jak má podepsaná faktura vypadat:
- podpis odpovídá XMLDSig a je obálkovaný (transformace
enveloped-signature), - element
Signatureje vždy posledním elementem uvnitř kořene faktury, - otisk se počítá algoritmem z rodiny SHA-2,
- fakturu může podepsat vystavitel i příjemce; každý další podpis pomocí transformace XPath vynechá podpisy přidané po něm, takže nový podpis neporuší ty předchozí,
- časové razítko má formu XAdES-T.
Pozor: datová zpráva ZFO je podepsaná jinak
Soubor ZFO z datové schránky obsahuje XML, ale podpis v něm není XMLDSig. Celé XML je zabalené do binární obálky s podpisem CAdES (formát odvozený z PKCS#7). Proto ZFO v textovém editoru ani XML nástroji neotevřeš a potřebuješ aplikaci, která obálku rozbalí.
Ověření podpisu a bezpečnost
Podpis XML si nepiš sám. Vedle kryptografie musíš správně provést kanonizaci, transformace a
vyhodnocení odkazů a každá odchylka znamená buď falešné chyby, nebo díru. Použij hotovou
knihovnu: xmlsec1 (nad libxml2), Apache Santuario v Javě,
System.Security.Cryptography.Xml.SignedXml v .NET nebo signxml v
Pythonu. Běžný uživatel ověří podpis faktury nebo dokumentu v programu, který s ním pracuje.
Útok přebalením podpisu: platný podpis říká jen to, že odkazovaná data jsou nezměněná. Útočník může dokument upravit tak, že podpis dál ukazuje na původní element, ale aplikace čte jiný, podvržený. Po ověření proto zpracuj přesně ten element, na který podpis odkazuje, a ne první, který najdeš.
Jak zabránit signature wrapping
Úspěšné kryptografické ověření nesmí vracet jen true. Aplikace potřebuje ověřený referencovaný uzel. Odmítni duplicitní ID, omez očekávaný počet a umístění podpisů i povolené transformace. Nepoužívej po ověření nový obecný XPath, který může vybrat podvrženou kopii. Zároveň ověř důvěru certifikátu podle profilu aplikace.
W3C XML Signature Best Practices popisuje tuto vazbu mezi ověřením a zpracováním. Podrobnosti bajtové reprezentace najdeš v kanonizaci XML.
Spustitelný podpis a odmítnutí změněného obsahu
import java.security.*;
import java.util.List;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.crypto.dsig.*;
import javax.xml.crypto.dsig.dom.*;
import javax.xml.crypto.dsig.spec.*;
import org.w3c.dom.*;
public class SignDemo {
static boolean verify(Document doc, PublicKey key) throws Exception {
Node signature = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature").item(0);
var context = new DOMValidateContext(key, signature);
context.setProperty("org.jcp.xml.dsig.secureValidation", Boolean.TRUE);
var factory = XMLSignatureFactory.getInstance("DOM");
return factory.unmarshalXMLSignature(context).validate(context);
}
public static void main(String[] args) throws Exception {
var builder = DocumentBuilderFactory.newDefaultInstance();
builder.setNamespaceAware(true);
var doc = builder.newDocumentBuilder().newDocument();
var root = doc.createElement("zprava"); doc.appendChild(root);
var text = doc.createElement("text"); text.setTextContent("XML"); root.appendChild(text);
var generator = KeyPairGenerator.getInstance("RSA"); generator.initialize(2048);
var pair = generator.generateKeyPair();
var factory = XMLSignatureFactory.getInstance("DOM");
var ref = factory.newReference("", factory.newDigestMethod(DigestMethod.SHA256, null),
List.of(factory.newTransform(Transform.ENVELOPED, (TransformParameterSpec)null)), null, null);
var info = factory.newSignedInfo(
factory.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE, (C14NMethodParameterSpec)null),
factory.newSignatureMethod(SignatureMethod.RSA_SHA256, null), List.of(ref));
factory.newXMLSignature(info, null).sign(new DOMSignContext(pair.getPrivate(), root));
System.out.println(verify(doc, pair.getPublic()));
text.setTextContent("Změněno");
System.out.println(verify(doc, pair.getPublic()));
}
} javac SignDemo.java
java SignDemo Program vytvoří vlastní dokument a pár klíčů. Reference s prázdným URI zde odkazuje na dokument; enveloped transform odstraní podpis ze vstupu jeho vlastního otisku. SignedInfo stanoví SHA-256 a RSA-SHA256 i kanonizaci. Ověření používá předem známý veřejný klíč, nikoli slepě převzatý klíč z KeyInfo cizího dokumentu.
První kontrola projde. Změna textu poruší digest a nové ověření vrátí false. Při druhé kontrole vytváříme nový validační objekt, abychom nepoužili dřívější uložený výsledek. Jde o demonstraci integrity s klíčem v paměti, nikoli kvalifikovaný elektronický podpis či ověření certifikačního řetězce.
Pro přijímané dokumenty bezpečně nastav XML parser, omez algoritmy a externí URI a ověř počet, umístění a reference podpisu podle konkrétního profilu. Teprve úspěšně ověřená referencovaná data předávej aplikaci. Tento příklad podepisuje vlastní celý dokument, takže neřeší obecný výběr uzlu podle ID. Zdrojem API je JDK XML Signature tutorial, pravidel XML Signature 1.1.