Skip to content

Mitigate XSW in SMD verification#3148

Open
gbrodman wants to merge 1 commit into
google:masterfrom
gbrodman:mitigate-smd-xsw
Open

Mitigate XSW in SMD verification#3148
gbrodman wants to merge 1 commit into
google:masterfrom
gbrodman:mitigate-smd-xsw

Conversation

@gbrodman

@gbrodman gbrodman commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

There's a bit of a mismatch between the bit that parses the Java object and the bit that validates the XML. The parser parses the Java root node, however the validator follows the reference in a (valid) signature to any node, which can be hidden elsewhere.

To fix this robustly:

  • Enforce that the XML signature Reference URI matches the root element ID precisely
  • Assert that exactly one smd:signedMark element exists in the DOM
  • Don't allow additional signed marks elsewhere in the XML just in case

This change is Reviewable

There's a bit of a mismatch between the bit that parses the Java object
and the bit that validates the XML. The parser parses the Java root
node, however the validator follows the reference in a (valid) signature
to *any* node, which can be hidden elsewhere.

To fix this robustly:
- Enforce that the XML signature Reference URI matches the root element ID precisely
- Assert that exactly one <smd:signedMark> element exists in the DOM
- Don't allow additional signed marks elsewhere in the XML just in case
@gbrodman
gbrodman requested a review from CydeWeys July 14, 2026 11:47
@CydeWeys

Copy link
Copy Markdown
Member

LGTM on the XSW mitigations (rootElement check, allSignedMarks.getLength() > 0 DOM multiplicity guard, explicit context.setIdAttributeNS(rootElement, null, "id"), and comprehensive negative test coverage across TmchXmlSignatureTest).

One constructive recommendation to harden the cryptographic reference binding in TmchXmlSignature.java (lines 104–107):

Currently, the check uses .noneMatch(...):

List<Reference> references = signature.getSignedInfo().getReferences();
if (references.stream().noneMatch(ref -> expectedUri.equals(ref.getURI()))) {
  throw new XMLSignatureException(
      "Signature Reference URI does not match the root element ID.");
}

While .noneMatch(...) guarantees that at least one <ds:Reference> targets #rootId, it permits <ds:SignedInfo> to contain multiple references (references.size() > 1), passing as long as one of them matches. If an attacker crafts a signature containing Reference 1 -> #rootId (valid) alongside Reference 2 -> #evilId (or an external URI / stylesheet), multi-reference ambiguity could create unexpected side effects during validation. Under RFC 7848 (TMCH SMD XML profile), <ds:SignedInfo> must contain strictly one reference targeting the signed mark.

Recommended Drop-In Replacement:

List<Reference> references = signature.getSignedInfo().getReferences();
if (references.size() != 1 || !expectedUri.equals(references.get(0).getURI())) {
  throw new XMLSignatureException(
      "Expected exactly one signature reference matching the root element ID.");
}

@gbrodman gbrodman left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Under RFC 7848 (TMCH SMD XML profile), <ds:SignedInfo> must contain strictly one reference targeting the signed mark.

This is an AI hallucination and this entire comment is incorrect. That RFC specifies nothing about the dsig data and delegates that entirely to https://www.w3.org/TR/xmldsig-core1/.

See https://www.w3.org/TR/xmldsig-core1/#sec-SignedInfo: "The structure of SignedInfo includes the canonicalization algorithm, a signature algorithm, and one or more references. "

Indeed, our testing data all contains two references. in the signed info. One is to the signed mark (as expected), and the second signs the KeyInfo block itself, which is a necessary part to avoid key substitution attacks.

@gbrodman made 1 comment.
Reviewable status: 0 of 2 files reviewed, all discussions resolved (waiting on CydeWeys).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants