In a signed SAML 2.0 assertion, what does the single <ds:Reference> inside <ds:SignedInfo> actually cover?
answer
- the signature signs a small block
- one same-document reference, one digest
- URI hash-id names the ID attribute
- strip the signature, then canonicalise
- exclusive c14n so a lifted subtree matches
basics
~20 sIt covers exactly one element: the ds:Reference carries a same-document URI of the form #id naming that element's ID attribute, and the DigestValue beside it is the hash of that element and its descendants after the listed transforms have run.
solid answer
~40 sSAML requires an **enveloped** signature containing a **single** `<ds:Reference>`, and that reference must be a same-document reference to the `ID` attribute of the element being signed — an assertion with `ID="_a1b2"` is referenced as `URI="#_a1b2"`. The digest is not taken over the raw bytes as they arrived. Two transforms run first: `http://www.w3.org/2000/09/xmldsig#enveloped-signature`, which removes the `<ds:Signature>` element itself so the digest is not chasing its own tail, and a canonicalisation transform — SAML says implementations SHOULD use exclusive canonicalisation, `http://www.w3.org/2001/10/xml-exc-c14n#` — which serialises the subtree to a defined octet stream. `<ds:DigestValue>` is the hash of that stream. `<ds:SignatureValue>` is then computed over the canonicalised `<ds:SignedInfo>`, not over the document, so the signature protects the reference and digest, and the digest protects the element.
code
xml · 22 lines<saml:Assertion ID="_a1b2c3" Version="2.0" IssueInstant="2026-09-19T10:14:02Z">
<saml:Issuer>https://idp.research-federation.example/idp</saml:Issuer>
<ds:Signature>
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="...agreed with the partner..."/>
<ds:Reference URI="#_a1b2c3">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="...agreed with the partner..."/>
<ds:DigestValue>Kx9rQ2h1bmtvZmRpZ2VzdA==</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>Qm9ndXNzaWduYXR1cmV2YWx1ZQ==</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data><ds:X509Certificate>MIIDdDCCAlyg...</ds:X509Certificate></ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
<saml:Subject>...</saml:Subject>
</saml:Assertion>go deeper
Recall the shape: one reference, one digest, and the URI is a hash character followed by the ID attribute of the element being signed.
Explain the two-hash chain — digest over the transformed element, signature over SignedInfo — and say what each of the two transforms is for.
Draw the consequence: the signature reaches exactly one subtree, so state what lies outside it and how a receiver decides which element it acted on.
Weigh the interoperability surface — algorithm agility, canonicalisation variants, partners who cannot move — against the cost of a bilateral change you cannot schedule.
## Two hashes, not one An XML signature does not sign a document. It signs a small block, `<ds:SignedInfo>`, that *names* what was hashed and *states* the hash. Reading it as a chain is the fastest way to get it right: 1. Take the element to be protected — for SAML, a `<saml:Assertion>` or a `<samlp:Response>`, identified by its `ID` attribute. 2. Run the transforms listed in `<ds:Transforms>` over it, in order. 3. Hash the resulting octets; that value goes in `<ds:DigestValue>`, inside the `<ds:Reference>`. 4. Canonicalise `<ds:SignedInfo>` using the algorithm its own `<ds:CanonicalizationMethod>` names, and sign *that*; the result is `<ds:SignatureValue>`. So `<ds:SignatureValue>` covers `<ds:SignedInfo>`, and `<ds:SignedInfo>` covers the element only indirectly, through the digest. Break either link and verification fails — but note what the structure implies: **the signature's reach is exactly one subtree, the one the URI names.** Anything outside that subtree is present in the document and unprotected. ## What SAML pins down Generic XML Signature is far more permissive than SAML allows. The profile narrows it hard: - the signature MUST be **enveloped** — the `<ds:Signature>` sits inside the element it protects, rather than alongside it or in a separate document; - it MUST contain a **single** `<ds:Reference>`, so there is never an argument about which of several digests applies; - that reference MUST be a **same-document reference** to the root element's `ID` attribute value, written `URI="#_a1b2"` against `ID="_a1b2"`. The narrowing exists because multi-reference, detached and externally referenced signatures make it genuinely hard for a receiver to state what it verified. One element, one digest, one answer. ## The two transforms and why each is there | Transform | Algorithm | Job | |---|---|---| | Enveloped signature | `http://www.w3.org/2000/09/xmldsig#enveloped-signature` | Removes the `<ds:Signature>` element from the subtree before hashing, resolving the circularity of a signature that sits inside what it signs | | Exclusive canonicalisation | `http://www.w3.org/2001/10/xml-exc-c14n#` | Produces one defined octet stream from a subtree whose serialisation may legitimately vary | The second deserves the explanation, because it is what interviewers push on. XML has many byte sequences for the same information content: attribute order, whitespace inside a tag, `<a/>` against `<a></a>`, which namespace declarations are written where, character references. A hash over raw bytes would break whenever a parser round-tripped a document. Canonicalisation defines one serialisation so both parties hash the same octets. **Exclusive** canonicalisation goes further, and that is the point for SAML specifically. Inclusive canonicalisation pulls in namespace declarations inherited from ancestors; exclusive canonicalisation includes only those the subtree actually uses. That is what lets a signed `<saml:Assertion>` be lifted out of the `<samlp:Response>` it arrived in and placed inside a different document — a very normal thing to do — and still digest to the same value, because the ancestors' declarations were never part of the hash. SAML states this as a **SHOULD**, not a MUST; the variant `http://www.w3.org/2001/10/xml-exc-c14n#WithComments` retains comments. ## What the reference does not tell you Three limits are worth stating out loud, because each is a real production failure: - **It does not say which element the application will read.** Verification selects a subtree by `ID` lookup. Consumption usually selects by position — "the first assertion in the response". When the two selections disagree, the receiver verifies one element and acts on another. - **It does not cover the rest of the document.** A signature over one `<saml:Assertion>` says nothing about a second assertion, about the enclosing `<samlp:Response>`, or about anything added beside it. - **It does not tell you the key was the right one.** `<ds:KeyInfo>` and the `<ds:X509Certificate>` it may carry are attacker-supplied in the sense that matters: a receiver that verifies against the certificate the message brought has established only that the message is self-consistent. The verification key must come from the trust the two parties established beforehand. The digest algorithm also ages. Deployments built when SHA-1 was standard still carry SHA-1 digests and signature methods; current federations use SHA-256-based algorithms, and mismatched algorithm support between two partners is a routine cause of a signature that verifies on one side and not the other.
- Why must the enveloped-signature transform run before the digest is taken?Because the `<ds:Signature>` element lives inside the element being signed. Without removing it, the digest would have to cover a `<ds:DigestValue>` that depends on the digest — unsatisfiable. The transform deletes the signature subtree from the input, so the hash covers the assertion as it would be without its signature.
- What breaks if a receiver hashes the bytes as they arrived instead of canonicalising first?Verification becomes fragile for legitimate documents. Any parse-and-reserialise on the path — attribute reordering, whitespace normalisation, a different empty-element form, a namespace declaration moved — changes the octets without changing the information, and the digest no longer matches. Canonicalisation defines the one serialisation both sides hash.
- A receiver verifies against the <ds:X509Certificate> carried in <ds:KeyInfo>. What has it actually checked?Only that the document is internally consistent: the certificate it shipped matches the signature it shipped. An attacker supplying both passes. The verification key must come from the trust relationship established out of band with that partner, and `<ds:KeyInfo>` is at most a hint about which of those known keys was used.
saying these in an interview costs you the question
- Says the signature covers the whole XML document.
- Thinks <ds:SignatureValue> is computed over the assertion directly.
- Treats canonicalisation as cosmetic formatting, not hash input.
- Claims SAML allows several <ds:Reference> elements in one signature.
- Trusts the certificate carried in <ds:KeyInfo> as the trust anchor.