Skip to content

fix(xmldsig): NODE_SET_ENTRY_CEILING blocks real EU trusted lists by four nodes #158

Description

@LKSNDRTMLKV

Problem

NODE_SET_ENTRY_CEILING is simultaneously the default and the maximum for VerificationPolicy::resources::max_node_set_entries, so there is no way to verify a document that exceeds it. Two publicly published EU Member State Trusted Lists do, by four and five nodes.

// src/hard_limits.rs:103
pub(crate) const NODE_SET_ENTRY_CEILING: usize = 65_536;
// src/policy.rs:483
max_node_set_entries: crate::hard_limits::NODE_SET_ENTRY_CEILING,

The field is public, which suggests it is tunable, but raising it is rejected against the same constant:

verification policy violation: node-set entries exceeds policy maximum 65536: got 1000000

So the ceiling is effectively fixed at compile time, and a caller has no escape hatch short of vendoring a patched build.

Reproduction

Both documents are public, XAdES-signed (exclusive C14N, RSA-SHA512), and published by their respective supervisory bodies under ETSI TS 119 612:

Document Size Result
https://eidas.agid.gov.it/TL/TSL-IT.xml 2 855 744 B node-set entries exceeds policy maximum 65536: got 65540
https://messervices.cyber.gouv.fr/visas/tl-fr_v6.xml 2 545 157 B node-set entries exceeds policy maximum 65536: got 65541
let resolver = DefaultKeyResolver::default();
let result = VerifyContext::new().key_resolver(&resolver).verify(&xml);
// Err(...) rather than Ok(VerifyResult)

Setting policy.resources.max_node_set_entries = 1_000_000 and passing it via VerifyContext::policy fails in the same place, before any parsing work.

Context, in case it helps prioritise

I evaluated 0.1.16 against 16 documents — the EU List of Trusted Lists plus 15 national lists fetched from its own pointers. 13 verified, including the LOTL itself; the two above failed on this ceiling, and the sixteenth was a truncated download on my side, which the crate correctly rejected as malformed rather than guessing.

The 13 cover exclusive C14N, RSA-SHA512, SHA-512 digests, an enveloped-signature transform on URI="", and a second reference to the XAdES SignedProperties, across documents carrying six or more namespace declarations with varying prefixes.

Negative cases behaved correctly too. Tampering applied in memory at equal length:

  • one byte inside signed content → Invalid(ReferenceDigestMismatch { ref_index: 0 })
  • SigningTime inside the XAdES SignedPropertiesInvalid(ReferenceDigestMismatch { ref_index: 1 }), confirming every reference is verified rather than only the first
  • one character of SignatureValueInvalid(SignatureMismatch)
    The ref_index: 1 case in particular is the check I most wanted to find, and it was there — a verifier that only checked the first reference would leave the signing time and signing-certificate digest unprotected while still reporting a pass.

The only thing standing between this crate and a complete pass over the EU trusted list corpus is the constant above, and the two documents that hit it miss by four nodes out of 65 536.

Worth noting these lists grow over time as Member States add trust service providers, so the number of affected countries will increase on its own. The next largest list in the set is 859 KB, so there is a wide gap below the threshold today.

Implementation

Either would resolve it; the second seems more in keeping with how the rest of the policy surface is shaped:

  • Raise NODE_SET_ENTRY_CEILING to a value with headroom for real-world documents of this class.
  • Keep the current value as the default and allow VerificationPolicy to raise it up to a separate, higher documented hard maximum — so the safe default still applies to callers who do not think about it, while a caller who knows their input can opt into more.

Happy to supply the full corpus and the harness used, or to test a branch against it.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions