The trusted list reader fetches and parses, and verifies nothing. It yields UnverifiedTrustedList, which is sound for answering "which service types is this provider listed under?" and unsound for any compliance verdict. SealChecks::QualifiedValidation is consequently unreachable — no adapter can honestly produce it.
This issue is to decide how that changes, because it is a fork with real cost on every branch and no obviously right answer.
What has to be verified, and it is two levels
- The list of lists.
https://ec.europa.eu/tools/lotl/eu-lotl.xml, XAdES-signed by the Commission.
- Each national list. 27+ documents, each XAdES-signed by its own Member State scheme operator, whose certificates are carried in the LOTL.
So the LOTL is the pivot: verify it, and it tells you which certificates sign each national list. Fail to verify it, and everything below inherits the doubt — which is exactly where the reader stands today.
🚨 The trust anchor is a document a human reads
This is the part that shapes the operational design, and it is not what a reader familiar with web PKI expects.
The LOTL's signing certificates do not chain to a commercial root. They are published in the Official Journal, C series. The LOTL declares its own SchemeInformationURI as https://eur-lex.europa.eu/eli/C/2026/1944/oj — CELEX 52026XC01944, "Compilation of Member States' trusted lists as notified under Commission Implementing Decision (EU) 2015/1505 as amended by Commission Implementing Decision (EU) 2025/2164".
Two consequences:
- The anchor must be pinned from an OJ notice, not discovered. There is no chain to walk up to.
- It changes. The Commission republishes the compilation, and a pinned set that is never refreshed will eventually reject a perfectly good LOTL. So this is a recurring operational obligation, not a one-off constant — and getting it wrong fails closed in a way that looks like an outage.
Worth noting the governing act while we are here: trusted list format and publication sit under CID (EU) 2015/1505, the surviving sibling of the 2015/1506 that was repealed — and it was itself amended in October 2025 by CIR (EU) 2025/2164, which moved the standard version the common template is based on.
Why this is not just "parse a signature"
XAdES sits on XML Signature, and XML Signature verifies a hash over the canonicalised form of the signed subtree — not over the bytes as received. Canonicalisation has to reproduce, exactly, the transform the signer applied: namespace inheritance and rendering, attribute ordering, whitespace, entity handling, and which of several C14N algorithms the signature names.
That is where XML signature bugs live, and they are the dangerous kind: a canonicaliser that differs from the signer's in some edge case either rejects valid documents, or — much worse — accepts a document whose signed portion is not what a verifier thinks it is. XML signature wrapping is a well-documented attack family for precisely this reason.
Writing this from scratch against 27 publishers' output is a serious undertaking, and appearing to work is the easy part.
The options, with what is actually available
Surveyed on crates.io rather than assumed:
| Option |
Cost |
Risk |
xml-sec — "Pure Rust XML Security: XMLDSig, XMLEnc, C14N. Drop-in replacement for libxmlsec1." v0.1.16, ~12.6k downloads, Apache-2.0 |
Lowest, if it works |
Young. A 0.1.x crate on a security-critical verification path. Would need real evaluation against the actual LOTL and several national lists before being trusted, not a version-bump adoption |
xml_c14n (libxml2 binding, ~91k downloads) plus our own DSig layer |
Native dependency; we still write the signature half |
Mature C14N, but the part we write is still the part that must be exactly right |
| libxmlsec1 via FFI |
Native dependency on a C library with a real CVE history |
Mature and widely deployed; the deployment cost is the issue |
| Out-of-process EU DSS (the Commission's own validator) |
A JVM alongside the node |
Authoritative — it is the reference implementation — but see the deployment note below |
| Do not verify |
Nothing |
The reader stays an unverified convenience for ever, and QualifiedValidation is never produced by anything |
Note the pure-Rust XAdES crates (xades, ades-rs) are at double-digit download counts and are not production material.
The deployment constraint that rules on this
This node is a self-hosted single binary, one deployment per operator. Every option above except the first and the last adds either a native library or a JVM to that deployment — for every self-hoster, on every platform we support. That is a materially different product, not just a different dependency, and it should weigh heavily.
It also argues against the obvious "just use DSS" answer: the Commission's validator being authoritative does not help if requiring it makes the node unshippable for the people meant to run it.
Recommendation
Evaluate xml-sec first, against the real LOTL and a spread of national lists, before committing to anything. It is the only option that keeps the single-binary property, and the evaluation is cheap relative to every alternative. If it holds up, the rest of this issue is small; if it does not, the choice is between a native dependency and never verifying, and that is a decision worth making deliberately rather than by default.
Whatever is chosen, the anchor-pinning and refresh story needs designing alongside it — a verifier with no maintained anchor set is a verifier that starts failing on a date nobody scheduled.
What it unblocks
SealChecks::QualifiedValidation becomes producible — the reason it exists.
- The Art. 39a check becomes machine-answerable rather than a question put to a provider by email.
- Art. 22's whole promise: qualified status as a fact this software establishes, rather than a claim it accepts.
Scope note
Verification is necessary and not sufficient for a qualified verdict. Art. 32(1), applied to seals by Art. 40, also needs the seal's certificate matched to a service entry, and the Annex III(j) creation-device indication read. Those are separate pieces; this one is the gate in front of them.
The trusted list reader fetches and parses, and verifies nothing. It yields
UnverifiedTrustedList, which is sound for answering "which service types is this provider listed under?" and unsound for any compliance verdict.SealChecks::QualifiedValidationis consequently unreachable — no adapter can honestly produce it.This issue is to decide how that changes, because it is a fork with real cost on every branch and no obviously right answer.
What has to be verified, and it is two levels
https://ec.europa.eu/tools/lotl/eu-lotl.xml, XAdES-signed by the Commission.So the LOTL is the pivot: verify it, and it tells you which certificates sign each national list. Fail to verify it, and everything below inherits the doubt — which is exactly where the reader stands today.
🚨 The trust anchor is a document a human reads
This is the part that shapes the operational design, and it is not what a reader familiar with web PKI expects.
The LOTL's signing certificates do not chain to a commercial root. They are published in the Official Journal, C series. The LOTL declares its own
SchemeInformationURIashttps://eur-lex.europa.eu/eli/C/2026/1944/oj— CELEX52026XC01944, "Compilation of Member States' trusted lists as notified under Commission Implementing Decision (EU) 2015/1505 as amended by Commission Implementing Decision (EU) 2025/2164".Two consequences:
Worth noting the governing act while we are here: trusted list format and publication sit under CID (EU) 2015/1505, the surviving sibling of the 2015/1506 that was repealed — and it was itself amended in October 2025 by CIR (EU) 2025/2164, which moved the standard version the common template is based on.
Why this is not just "parse a signature"
XAdES sits on XML Signature, and XML Signature verifies a hash over the canonicalised form of the signed subtree — not over the bytes as received. Canonicalisation has to reproduce, exactly, the transform the signer applied: namespace inheritance and rendering, attribute ordering, whitespace, entity handling, and which of several C14N algorithms the signature names.
That is where XML signature bugs live, and they are the dangerous kind: a canonicaliser that differs from the signer's in some edge case either rejects valid documents, or — much worse — accepts a document whose signed portion is not what a verifier thinks it is. XML signature wrapping is a well-documented attack family for precisely this reason.
Writing this from scratch against 27 publishers' output is a serious undertaking, and appearing to work is the easy part.
The options, with what is actually available
Surveyed on crates.io rather than assumed:
xml-sec— "Pure Rust XML Security: XMLDSig, XMLEnc, C14N. Drop-in replacement for libxmlsec1." v0.1.16, ~12.6k downloads, Apache-2.0xml_c14n(libxml2 binding, ~91k downloads) plus our own DSig layerQualifiedValidationis never produced by anythingNote the pure-Rust XAdES crates (
xades,ades-rs) are at double-digit download counts and are not production material.The deployment constraint that rules on this
This node is a self-hosted single binary, one deployment per operator. Every option above except the first and the last adds either a native library or a JVM to that deployment — for every self-hoster, on every platform we support. That is a materially different product, not just a different dependency, and it should weigh heavily.
It also argues against the obvious "just use DSS" answer: the Commission's validator being authoritative does not help if requiring it makes the node unshippable for the people meant to run it.
Recommendation
Evaluate
xml-secfirst, against the real LOTL and a spread of national lists, before committing to anything. It is the only option that keeps the single-binary property, and the evaluation is cheap relative to every alternative. If it holds up, the rest of this issue is small; if it does not, the choice is between a native dependency and never verifying, and that is a decision worth making deliberately rather than by default.Whatever is chosen, the anchor-pinning and refresh story needs designing alongside it — a verifier with no maintained anchor set is a verifier that starts failing on a date nobody scheduled.
What it unblocks
SealChecks::QualifiedValidationbecomes producible — the reason it exists.Scope note
Verification is necessary and not sufficient for a qualified verdict. Art. 32(1), applied to seals by Art. 40, also needs the seal's certificate matched to a service entry, and the Annex III(j) creation-device indication read. Those are separate pieces; this one is the gate in front of them.