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:
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 SignedProperties → Invalid(ReferenceDigestMismatch { ref_index: 1 }), confirming every reference is verified rather than only the first
- one character of
SignatureValue → Invalid(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.
Problem
NODE_SET_ENTRY_CEILINGis simultaneously the default and the maximum forVerificationPolicy::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.The field is public, which suggests it is tunable, but raising it is rejected against the same constant:
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:
node-set entries exceeds policy maximum 65536: got 65540node-set entries exceeds policy maximum 65536: got 65541Setting
policy.resources.max_node_set_entries = 1_000_000and passing it viaVerifyContext::policyfails 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 XAdESSignedProperties, across documents carrying six or more namespace declarations with varying prefixes.Negative cases behaved correctly too. Tampering applied in memory at equal length:
Invalid(ReferenceDigestMismatch { ref_index: 0 })SigningTimeinside the XAdESSignedProperties→Invalid(ReferenceDigestMismatch { ref_index: 1 }), confirming every reference is verified rather than only the firstSignatureValue→Invalid(SignatureMismatch)The
ref_index: 1case 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:
NODE_SET_ENTRY_CEILINGto a value with headroom for real-world documents of this class.VerificationPolicyto 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.