Skip to content

feat(seal): read and verify EU Trusted Lists - #316

Open
LKSNDRTMLKV wants to merge 7 commits into
mainfrom
feat/trusted-list-revived
Open

LKSNDRTMLKV wants to merge 7 commits into
mainfrom
feat/trusted-list-revived

Conversation

@LKSNDRTMLKV

Copy link
Copy Markdown
Member

Revives #294, which GitHub auto-closed when its base branch feat/seal-evidenced-level merged as #292. The work was finished and pushed; nothing was wrong with it except that its PR closed underneath it. Stacked on #312 — retarget to main before that one merges.

just check is green (1121/1121).

Why this is stacked rather than independent

It needs dpp_domain::trusted_list and SealedEnvelope::conformance_level, both new in dpp-core 0.20.0, so it does not build against the pin on main. That is the dependency the original note recorded as "blocked on the core repin", and #312 clears it.

What it adds

dpp_seal::trustlistverify_lotl for the List of Trusted Lists, verify_trusted_list for a Member State's, plus parsers, fetcher and typed rejections. Nothing calls it yet. This is the reader, not a policy.

The trust anchor is an Official Journal notice, not a certificate authority. The LOTL's signing certificates chain to no commercial root — they are published in the OJ C series, which the LOTL names through its own SchemeInformationURI. There is nothing to walk up to, so the anchor is six SHA-256 digests taken from notice 52026XC01944, compiled in with the location, CELEX and pin date beside them. Compiled in and never configuration: an operator who can repoint the anchor can make any list verify.

Those six digests are the entire pinned surface. No Member State's certificates live in the repository — they arrive inside a document that has already been verified, so a country joining or rotating needs no code change and no release.

authorises is a precondition, not a verdict: anyone can copy the genuine certificate into a forgery and pass it, and only the signature separates them. The converse is the sharper half — Finland's list is genuine and correctly signed by Finland, which does not sign the LOTL, and it verifies cleanly with the anchor check disabled. That pair is why both halves exist, and there is a test for each.

What reviewers should look at hardest

The recurring operational obligation. The pin must be refreshed when the Commission republishes the notice, and the current signer expires 2027-11-17 — a calendar date, not a discovery. A stale pin fails closed and reads as an outage. The reader carries the early signal (the LOTL's first SchemeInformationURI entry is the current notice), but nothing acts on it yet.

The xml-sec fork. Italy (2.86 MB) and France (2.55 MB) exceed a compile-time node-set ceiling in the published crate and cannot be verified at any configuration — the constant is pub(crate) and the policy knob that looks like it raises the limit is validated against the same number. They miss by four and five nodes, and trusted lists only grow. The fork changes that one constant and nothing else, pinned by rev rather than vendored because the crate is ~66k LOC.

Changes made during the revival

A deny.toml source allowance. The sources policy denies git dependencies deliberately — "a git dependency is how unreviewed code enters a build" — so the fork needs an explicit entry. Added with the justification and the exit condition: when structured-world/xml-sec#158 resolves, the [patch.crates-io] stanza and the deny.toml entry go together. The rule still stands for everything else; this one is allowed because the review is a single constant, not because forks are fine.

A CHANGELOG entry. The work had none.

One conflict that was a real disagreement, not a textual one. This branch recorded a seal's conformance_level as what the bytes carry; #312 records what was requested. Core settles it — the field's doc says it is "a record of what was asked for, not proof of what arrived", and the conformance kit's seal.misrecorded_level rule fails an envelope whose stored level disagrees with the request. I kept the request.

The concern behind the other version was legitimate and is preserved in the comment: echoing a request could launder a broken can_produce guard. What actually catches that is cades::evidenced_level, read independently by the drain, which alarms on the disagreement rather than letting either side vouch for itself.

Not in this PR

#297 — the mandatory TS 119 612 signature profile from CID (EU) 2025/2164 (one ds:Reference with URI="", exactly one ds:Transforms, exactly two named algorithms) is not asserted. That work lands on top of this, because it edits files this PR creates.

@LKSNDRTMLKV

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 15, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Head commit changed.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@LKSNDRTMLKV
LKSNDRTMLKV changed the base branch from chore/repin-core-0-20-0 to main September 15, 2026 05:05
@LKSNDRTMLKV
LKSNDRTMLKV force-pushed the feat/trusted-list-revived branch from 79d0082 to 7503b7e Compare September 15, 2026 05:06
@coderabbitai

coderabbitai Bot commented Sep 15, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/trusted-list-revived

Comment @coderabbitai help to get the list of available commands.

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

Labels

review-ready Opt this PR into a CodeRabbit review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant