Skip to content

feat(trustlist): consume TS 119 602 LoTE (JSON trusted lists, WRPAC/WRPRC)#136

Merged
luisgf merged 2 commits into
mainfrom
feat/lote
Jul 22, 2026
Merged

feat(trustlist): consume TS 119 602 LoTE (JSON trusted lists, WRPAC/WRPRC)#136
luisgf merged 2 commits into
mainfrom
feat/lote

Conversation

@luisgf

@luisgf luisgf commented Jul 22, 2026

Copy link
Copy Markdown
Owner

feat(trustlist): consume TS 119 602 LoTE (JSON trusted lists, WRPAC/WRPRC)

ETSI TS 119 602 is the successor trusted-list data model whose EU profiles
carry the EUDI wallet anchor lists: Annex F (WRPAC providers) and Annex G
(WRPRC providers, the registrar anchors verify_rp_registration_certificate
consumes). One interface, two encodings: parse_lote / consume_lote /
walk_lote distil into the same TrustAnchorSet as walk_lotl.

A JSON LoTE travels as a compact JAdES baseline-B JWS, so verification runs
on the existing JOSE primitives: the {ES256, ES384, EdDSA, Ed25519}
allow-list before any crypto, the WRPRC lane's allow-listed crit, the x5c
signer authenticated against caller-pinned certificates, plus the clause
6.8 DN binding (organizationName / countryName vs the scheme). Parsing is
strict and fail-closed on every field feeding a trust decision; the EU
profiles enforce Tables F.1-G.3, including the spec's literal
WRPRCrovidersList StatusDetn typo (accepted alongside the corrected
spelling). Self-made signed vectors pin the behaviour until the Commission
publishes the real lists.

Closes #135

luisgf added 2 commits July 22, 2026 09:46
…RPRC)

ETSI TS 119 602 is the successor trusted-list data model whose EU profiles
carry the EUDI wallet anchor lists: Annex F (WRPAC providers) and Annex G
(WRPRC providers, the registrar anchors verify_rp_registration_certificate
consumes). One interface, two encodings: parse_lote / consume_lote /
walk_lote distil into the same TrustAnchorSet as walk_lotl.

A JSON LoTE travels as a compact JAdES baseline-B JWS, so verification runs
on the existing JOSE primitives: the {ES256, ES384, EdDSA, Ed25519}
allow-list before any crypto, the WRPRC lane's allow-listed crit, the x5c
signer authenticated against caller-pinned certificates, plus the clause
6.8 DN binding (organizationName / countryName vs the scheme). Parsing is
strict and fail-closed on every field feeding a trust decision; the EU
profiles enforce Tables F.1-G.3, including the spec's literal
WRPRCrovidersList StatusDetn typo (accepted alongside the corrected
spelling). Self-made signed vectors pin the behaviour until the Commission
publishes the real lists.

Closes #135
M1 (medium): a profiled walk_lote now defaults its selection to the
profile's issuance service types — the review proved a WRPRC signed under a
provider's REVOCATION-service CA validated end-to-end through the
documented walk -> .certificates -> verify flow. It also only follows
pointers whose qualifier LoTEType matches the profile and consumes pointed
lists under the same profile, so a foreign list type cannot leak anchors
into a profiled walk. Explicit select=None remains the deliberate
everything switch.

L1 (low): a far-future ListIssueDateTime (year 9999) made _plus_months
raise an uncaught ValueError that crashed consume_lote and walk_lote;
now a typed TrustListProfileError / a staged problem.

L2 (low): "ServiceStatus": "" slipped the profile's shall-not-be-used gate
(truthiness check); presence is now the violation, read from the wire
object like StatusStartingTime.

I2 (info): _datetime_z accepted looser ISO forms than its docstring
claimed; date-times are now pinned to the exact YYYY-MM-DDThh:mm:ssZ form
clause 6.1.3 mandates.

L3 was assessed a false positive (Table G.3's History row imposes no
additional requirements and clause 6.7 requires StatusStartingTime inside
history instances); L4 (consume_lote does no freshness check) is the
documented 119 612-lane split, now stated in the docstring.

Regression tests named for each attack; wiki + CHANGELOG updated.
@luisgf

luisgf commented Jul 22, 2026

Copy link
Copy Markdown
Owner Author

Adversarial review (2026-07-22, pre-merge) — verdict: the core trust decision held (authentication, alg-binding, SSRF, walk caps: every probe in those categories failed closed, ~120 hostile parser inputs produced only the typed error family); 1 medium + 2 low confirmed with repros, fixed in d37c082 with a regression test named for each attack:

  • M1 (medium, fixed) — the documented walk_lote → .certificates → verify_rp_registration_certificate flow admitted a provider's revocation-service CA into the issuance anchor set (proven: a WRPRC signed under the revocation CA validated end-to-end), and pointer hops escaped the profile (a WRPAC-typed list could enter a WRPRC walk). Fix: a profiled walk defaults to the profile's issuance service types, only follows same-LoTEType pointers, and consumes hops under the same profile; explicit select=None remains the deliberate wide switch.
  • L1 (low, fixed) — year-9999 ListIssueDateTime → uncaught ValueError crashing consume_lote/walk_lote; now TrustListProfileError / a staged problem.
  • L2 (low, fixed)"ServiceStatus": "" slipped the shall-not-be-used gate; presence is now the violation (wire-object check).
  • I2 (info, fixed)_datetime_z accepted looser ISO forms than clause 6.1.3; now pinned to YYYY-MM-DDThh:mm:ssZ exactly.
  • L3 — assessed false positive: StatusStartingTime inside ServiceHistory is structurally required there (clause 6.7) and Table G.3's History row imposes no additional requirements; flagging it would reject conformant lists. Documented in the profile check.
  • L4 — documented, not changed: consume_lote performs no freshness check by design — the exact consume_trust_list/walk_lotl split of the 119 612 lane; now stated in the docstring.

Confirmed fail-closed under attack (no change needed): ES256-header/Ed25519-leaf and every other alg/key confusion, crit smuggling, same-DN unpinned signer, [attacker-leaf, pinned-CA] without a real path, expired pinned leaf, zero-loadable-cert pointers, duplicate-JSON-key split-brain, file:///http:///link-local/metadata fetch targets, and 20 query-string pointer variants against the max_lists cap (exactly 8 fetches).

@luisgf
luisgf merged commit c027662 into main Jul 22, 2026
13 of 14 checks passed
@luisgf
luisgf deleted the feat/lote branch July 22, 2026 08:09
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.

feat(trustlist): consume ETSI TS 119 602 LoTE (JSON trusted lists) as an EU anchor source (WRPAC/WRPRC providers)

1 participant