Skip to content

Commit 3ec7505

Browse files
committed
Add note about comment canonicalization
1 parent 2d818c9 commit 3ec7505

File tree

2 files changed

+7
-4
lines changed

2 files changed

+7
-4
lines changed

README.rst

+3-2
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,9 @@ Assuming ``metadata.xml`` contains SAML metadata for the assertion source:
8686
It is important to understand and follow the best practice rule of "See what is signed" when verifying XML
8787
signatures. The gist of this rule is: if your application neglects to verify that the information it trusts is
8888
what was actually signed, the attacker can supply a valid signature but point you to malicious data that wasn't signed
89-
by that signature. Failure to follow this rule can lead to vulnerability against attacks like
90-
`SAML signature wrapping <https://www.usenix.org/system/files/conference/usenixsecurity12/sec12-final91.pdf>`_.
89+
by that signature. Failure to follow this rule can lead to vulnerabilities against attacks like
90+
`SAML signature wrapping <https://www.usenix.org/system/files/conference/usenixsecurity12/sec12-final91.pdf>`_ and
91+
`XML comment canonicalization induced text element truncation <https://duo.com/blog/duo-finds-saml-vulnerabilities-affecting-multiple-implementations>`_.
9192

9293
In SignXML, you can ensure that the information signed is what you expect to be signed by only trusting the
9394
data returned by ``XMLVerifier.verify()``. The ``signed_xml`` attribute of the return value is the XML node or string

signxml/verifier.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ def verify(
389389
root = self.get_root(data)
390390
signature_ref = self._get_signature(root)
391391

392-
# HACK: deep copy won't keep root's namespaces
392+
# We could do a deep copy here, but it wouldn't preserve root level namespaces
393393
signature = self._fromstring(self._tostring(signature_ref))
394394

395395
if validate_schema:
@@ -514,7 +514,9 @@ def _verify_reference(self, reference, index, root, uri_resolver, c14n_algorithm
514514
if b64decode(digest_value.text) != self._get_digest(payload_c14n, digest_alg):
515515
raise InvalidDigest(f"Digest mismatch for reference {index} ({reference.get('URI')})")
516516

517-
# We return the signed XML (and only that) to ensure no access to unsigned data happens
517+
# We return the signed XML (and only that) to ensure no access to unsigned data happens.
518+
# Note it is essential to roundtrip the payload and render it from canonicalized XML, to avoid returning
519+
# untrusted comments, avoid text nodes being broken up even after comments are excised, etc.
518520
try:
519521
payload_c14n_xml = self._fromstring(payload_c14n)
520522
except etree.XMLSyntaxError:

0 commit comments

Comments
 (0)