Ga naar inhoud

PuB-EAA provider

The PuB-EAA provider (PubEAA-voorziening) issues an attestation with source data to the EUDI wallet of the citizen. It is flow A of interaction pattern A in the Globaal Ontwerp. GBO operates the technical issuance facility, and each bronhouder stays the PuB-EAA provider of its own attestations (ADR-0028, proposed). The facility runs on the nl-wallet issuance server (ADR-0018). The EUDI issuance runtime page has every hop and the links to the code.

Overview › EUDI wallet channel › PuB-EAA provider

Components and interfaces

flowchart LR subgraph front["Citizen side"] direction TB portal["Developer portal<br/>QR panel"] wallet["NL Wallet"] end subgraph fac["Issuance facility"] direction TB is("Issuance server<br/>nl-wallet v0.5.0") init("Issuance init<br/>writes the server<br/>configuration") certs[("Certificate set<br/>per source")] subgraph ad["EUDI adapter"] direction TB off("Offers<br/>GET /eudi-offers.json") cb("Callback<br/>POST /attestations") tm("Type Metadata<br/>GET /types/...") rt("Release runtime") end lb[("Adapter logbook<br/>LDV")] end subgraph cfg["Source configuration"] direction TB rec("Source reconciler") reg[("Source registry<br/>releases")] end doc[("Source document<br/>/.well-known/gbo")] pdp("GBO Basis<br/>PEP + PDP") portal -- "offers" --> off wallet -- "PID, then attestation" --> is wallet -. "VCT" .-> tm is -- "disclosed PID" --> cb cb -- "query + BSN<br/>over FSC" --> pdp cb -.-> lb certs -.-> init init -- "at restart" --> is doc -- "over FSC" --> rec rec -- "validate,<br/>promote" --> reg reg -. "poll" .-> rt reg -. "read" .-> init %% GBO diagram palette: the same colors as the Globaal Ontwerp classDef basis fill:#DCFCE7,stroke:#16A34A,color:#0F172A classDef dvtp fill:#FEDCD8,stroke:#E28F80,color:#1E293B classDef eudi fill:#FFEDD5,stroke:#EA580C,color:#0F172A classDef source fill:#DDEBF7,stroke:#6F94B8,color:#1E293B classDef ext fill:#F1EFE8,stroke:#5F5E5A,color:#444441 classDef store fill:#EDE9FE,stroke:#7C3AED,color:#1E1B4B classDef mock fill:#F3F4F6,stroke:#9CA3AF,color:#374151,stroke-dasharray:4 3 classDef planned fill:#FFFFFF,stroke:#9CA3AF,color:#6B7280,stroke-dasharray:6 4 classDef zoom stroke-width:3px class wallet ext class portal mock class is,init,off,cb,tm,rt eudi class certs,lb,reg store class rec,pdp basis class doc source class cb,tm,certs,rec,reg,pdp zoom style front fill:#F4F5F7,stroke:#D1D5DB style fac fill:#F4F5F7,stroke:#EA580C style ad fill:#FFFFFF,stroke:#EA580C style cfg fill:#F4F5F7,stroke:#16A34A click cb "#the-callback-gap" "The callback gap" click tm "#from-source-description-to-claims" "Claims and Type Metadata" click certs "../../../concepts/source-onboarding/#a-certificate-set-per-source" "Certificate set" click rec "../../../concepts/source-onboarding/" "Source onboarding" click reg "../../../concepts/source-onboarding/" "Source onboarding" click pdp "../../gbo-basis/authorization/#the-rules" "The rules"

Figure 1: The components of the PuB-EAA provider and their interfaces. Thick border: click to zoom in.

Each component has one job:

Component Job Never does
Issuance server Asks the wallet for the PID over OpenID4VP and checks it against its PID trust anchors. Issues the attestation over OpenID4VCI, signed with the issuer certificate of the source. Calls the source
EUDI adapter Takes the BSN from the PID. Builds the query from the active release and calls the source through its own FSC Outway. Turns the answer into claims and writes two LDV records. Keeps source data, or has a query or mapping of its own (REQ-44)
Release runtime Polls the active release and rebuilds the bindings of the adapter. If an error occurs, it keeps the previous snapshot. Fetches source metadata itself
Source reconciler and registry The reconciler fetches and validates the source document. Promotion writes an immutable release and sets the active pointer. Activates a source because of a valid FSC contract (REQ-65)
Certificate set Issuer, reader and status-list certificates of one source. The wallet shows the issuer and the purpose from these certificates. Serves another source (REQ-58)
Issuance init Writes the configuration of the issuance server from the active release. The server reads it only at startup, so a new release needs a controlled restart. —

The issuance flow

One issuance of the income statement (inkomensverklaring) for 2024 takes these steps:

sequenceDiagram autonumber actor B as Citizen box rgb(241,239,232) Wallet and demo portal participant P as Developer portal participant W as NL Wallet end box rgb(255,237,213) PuB-EAA provider participant IS as Issuance server participant AD as EUDI adapter end box rgb(237,233,254) Records participant LB as Adapter logbook end box rgb(220,252,231) GBO Basis at the source participant PDP as PEP + PDP participant GW as Source gateway end box rgb(221,235,247) Source participant S as Source API end P->>AD: GET /eudi-offers.json AD-->>P: offers of the active release B->>W: scan the QR code of an offer W->>IS: fetch the disclosure request IS-->>W: ask for the PID claim bsn, with the reader certificate of the source W->>IS: PID as SD-JWT VC, after the citizen approves Note over IS: check the PID against<br/>issuer_trust_anchors IS->>AD: POST /attestations/belastingdienst/inkomensverklaring?jaar=2024 Note over AD: type in the release, BSN from<br/>the PID, parameters in an offer AD->>LB: record pid-bsn-extractie AD->>PDP: query with the BSN, over FSC Note over PDP: PID regime, EUD0001 PDP->>GW: allow GW->>S: query, unchanged S-->>AD: income data, back through FSC Note over AD: claims with gbo-simple-v1 AD->>LB: record attestatie-samenstellen AD-->>IS: claims, VCT, format dc+sd-jwt IS-->>W: attestation over OpenID4VCI W-->>B: accept the attestation

Figure 2: One issuance, at essence level.

Three properties of this flow matter for the design:

  • The issuance server checks the PID. The adapter trusts the callback without a check of the caller (The callback gap).
  • The adapter writes an LDV record before it calls the source. If the logbook does not acknowledge the record, the adapter does not call the source. Neither record holds the BSN (REQ-72).
  • The offer and the policy are separate limits. The source offers 2024 and 2025, and EUD0001 allows the same years. Publication by the source does not authorize the data (F-74).

For every hop, with the data and the code, see EUDI issuance.

From source description to claims

The source describes each attestation once, in the eudi capability of its source document. GBO derives the rest:

flowchart LR subgraph doc["Source document: capabilities.eudi"] direction TB off["offers<br/>for example year 2024"] gq["graphql<br/>query, subject variable,<br/>result_pointer"] disp["display<br/>name, summary per language"] cl["claims<br/>pointer, datatype, label, sd"] end subgraph gen["Generated by GBO"] direction TB cfg("Issuance configuration<br/>offers, disclosure") tm("Type Metadata<br/>VCT with an integrity hash") prof("gbo-simple-v1<br/>projection rules") end subgraph run["For each issuance"] direction TB ans[("Answer of<br/>the source")] flat("Flat scalar claims") att["Attestation<br/>SD-JWT VC"] end off --> cfg disp --> tm cl --> tm cl --> prof gq --> ans ans --> prof prof --> flat flat --> att tm -. "type" .-> att %% GBO diagram palette: the same colors as the Globaal Ontwerp classDef basis fill:#DCFCE7,stroke:#16A34A,color:#0F172A classDef dvtp fill:#FEDCD8,stroke:#E28F80,color:#1E293B classDef eudi fill:#FFEDD5,stroke:#EA580C,color:#0F172A classDef source fill:#DDEBF7,stroke:#6F94B8,color:#1E293B classDef ext fill:#F1EFE8,stroke:#5F5E5A,color:#444441 classDef store fill:#EDE9FE,stroke:#7C3AED,color:#1E1B4B classDef mock fill:#F3F4F6,stroke:#9CA3AF,color:#374151,stroke-dasharray:4 3 classDef planned fill:#FFFFFF,stroke:#9CA3AF,color:#6B7280,stroke-dasharray:6 4 classDef zoom stroke-width:3px class off,gq,disp,cl,ans source class cfg,tm,prof,flat eudi class att ext class tm zoom style doc fill:#FFFFFF,stroke:#6F94B8,stroke-dasharray:4 3 style gen fill:#F4F5F7,stroke:#EA580C style run fill:#F4F5F7,stroke:#D1D5DB click tm "../../../concepts/source-onboarding/#the-eudi-capability-claims-once-type-metadata-generated" "The eudi capability"

Figure 3: From the eudi capability to an attestation.

A claim has a pointer into the answer, a datatype, a label and a setting for selective disclosure. GBO derives the projection from the claims. It also generates the SD-JWT VC Type Metadata from display and claims (ADR-0017, REQ-64). As a result, the schema in the Type Metadata always matches what the projection fills.

The URL of the Type Metadata is the VCT of the type version. A change to the claims or the display changes the bytes behind that URL, so the change needs a new type_version. The profile gbo-simple-v1 allows only scalar claims. Nested claims are not built (REQ-78).

The demo has two attestation types:

Type Source and service Rule Offers Subject of the data
Income statement Belastingdienst, bri EUD0001 jaar (year) 2024 and 2025 The PID holder
Death certificate (akte van overlijden) RvIG, brp EUD0002 One, without parameters The deceased partner of the PID holder

Error contract

The adapter checks a callback in a fixed order. The first failed check decides the HTTP status:

flowchart LR in(["Callback"]) c1{"Release<br/>loaded?"} c2{"Type in the<br/>release?"} c3{"Source metadata<br/>fresh or in grace?"} c4{"PID with<br/>a BSN?"} c5{"Parameters<br/>in an offer?"} c6{"Source answer<br/>without errors?"} c7{"One result that<br/>fits the claims?"} ok(["200<br/>claims"]) e503(["503"]) e404(["404"]) e400(["400"]) e502(["502"]) in --> c1 --> c2 --> c3 --> c4 --> c5 --> c6 --> c7 c7 -- "yes" --> ok c1 & c3 -. "no" .-> e503 c2 -. "no" .-> e404 c4 & c5 -. "no" .-> e400 c6 -. "no, or a PDP deny" .-> e502 c7 -. "no result" .-> e404 c7 -. "more, or no fit" .-> e502 %% GBO diagram palette: the same colors as the Globaal Ontwerp classDef basis fill:#DCFCE7,stroke:#16A34A,color:#0F172A classDef dvtp fill:#FEDCD8,stroke:#E28F80,color:#1E293B classDef eudi fill:#FFEDD5,stroke:#EA580C,color:#0F172A classDef source fill:#DDEBF7,stroke:#6F94B8,color:#1E293B classDef ext fill:#F1EFE8,stroke:#5F5E5A,color:#444441 classDef store fill:#EDE9FE,stroke:#7C3AED,color:#1E1B4B classDef mock fill:#F3F4F6,stroke:#9CA3AF,color:#374151,stroke-dasharray:4 3 classDef planned fill:#FFFFFF,stroke:#9CA3AF,color:#6B7280,stroke-dasharray:6 4 classDef zoom stroke-width:3px class in ext class c1,c2,c3,c4,c5,c6,c7,ok eudi class e503,e404,e400,e502 dvtp

Figure 4: The checks of the adapter, in order. Red: the HTTP status that the issuance server gets.

The issuance server gets only the HTTP status. The reason is in the adapter log (stage), and for a PDP deny also in the ADL. The FSC Inway removes the reason of a deny on the way back. These rules hold for every error path:

  • No attestation reaches the wallet, and a partial attestation does not exist (REQ-116).
  • A PDP deny (ACTOR_NOT_ALLOWED, PID_NOT_PRESENT, YEAR_NOT_ALLOWED, FIELD_NOT_PERMITTED) gives 502, the same status as a chain that does not answer.
  • If the logbook does not acknowledge an LDV record, the adapter answers 500 and does not continue.
  • A missing or incomplete certificate set blocks only that source (REQ-58).

The offer list is an exception to the isolation per source. If one source is past its grace period, /eudi-offers.json answers 503 for every source (#606). For all triggers, see Error paths.

The callback gap

The issuance server checks the PID. The adapter does not know that a callback comes from the issuance server:

flowchart LR wallet["NL Wallet"] x["Any caller that<br/>reaches the adapter"] is("Issuance server<br/>checks the PID") cb("Adapter callback<br/>no caller authentication") pdp("PDP<br/>checks the caller peer<br/>and a BSN in the query") src[("Source")] f1("Adapter checks<br/>the SD-JWT itself") f2("Authenticated callback<br/>with signed evidence") wallet -- "PID" --> is is -- "BSN from the PID" --> cb x -- "any BSN" --> cb cb -- "as the designated issuer" --> pdp pdp --> src f1 -.-> cb f2 -.-> cb %% GBO diagram palette: the same colors as the Globaal Ontwerp classDef basis fill:#DCFCE7,stroke:#16A34A,color:#0F172A classDef dvtp fill:#FEDCD8,stroke:#E28F80,color:#1E293B classDef eudi fill:#FFEDD5,stroke:#EA580C,color:#0F172A classDef source fill:#DDEBF7,stroke:#6F94B8,color:#1E293B classDef ext fill:#F1EFE8,stroke:#5F5E5A,color:#444441 classDef store fill:#EDE9FE,stroke:#7C3AED,color:#1E1B4B classDef mock fill:#F3F4F6,stroke:#9CA3AF,color:#374151,stroke-dasharray:4 3 classDef planned fill:#FFFFFF,stroke:#9CA3AF,color:#6B7280,stroke-dasharray:6 4 classDef zoom stroke-width:3px class wallet ext class x mock class is,cb eudi class pdp basis class src source class f1,f2 planned

Figure 5: The callback gap. Dashed: the two fixes that REQ-45 names. Demo v0.9.0 builds neither.

No mechanism binds the BSN in the callback to a checked disclosure (REQ-45, Q-18, #133). The PDP sees the designated issuer peer and a BSN, so it allows. The list of designated issuers is the only gate of the PID regime (F-70). The demo uses the gap itself: a playground in the developer portal posts a typed BSN to the adapter.

The demo dropped a PID check in the adapter, because the issuance server checks the signature (SL-25). The PID trust list of the issuance server is also too broad. Next to the PID provider, it trusts the GBO development CA and the issuer CA of every source. Both fixes of REQ-45 need a binding of the disclosure to the request.

The death certificate holds data about the deceased partner of the citizen who shares the PID. The subject of the claims differs from the person who discloses the PID (F-47, REQ-57):

flowchart LR holder(["PID holder"]) ad("EUDI adapter<br/>BSN of the holder") pdp("PDP, rule EUD0002<br/>sees only the flat output") brp[("BRP source<br/>resolves the relation")] rel["Marriage ended by<br/>the death of the partner"] att["Death certificate<br/>claims about the partner"] holder -- "PID" --> ad ad -- "akteVanOverlijden(bsn)" --> pdp pdp --> brp brp --- rel brp -. "fields of the<br/>deceased partner" .-> att %% GBO diagram palette: the same colors as the Globaal Ontwerp classDef basis fill:#DCFCE7,stroke:#16A34A,color:#0F172A classDef dvtp fill:#FEDCD8,stroke:#E28F80,color:#1E293B classDef eudi fill:#FFEDD5,stroke:#EA580C,color:#0F172A classDef source fill:#DDEBF7,stroke:#6F94B8,color:#1E293B classDef ext fill:#F1EFE8,stroke:#5F5E5A,color:#444441 classDef store fill:#EDE9FE,stroke:#7C3AED,color:#1E1B4B classDef mock fill:#F3F4F6,stroke:#9CA3AF,color:#374151,stroke-dasharray:4 3 classDef planned fill:#FFFFFF,stroke:#9CA3AF,color:#6B7280,stroke-dasharray:6 4 classDef zoom stroke-width:3px class holder,brp,rel source class ad eudi class pdp basis class att ext

Figure 6: A related-person attestation. The source, and not GBO, knows the relation.

The source selects the marriage that ended by the death of the partner. If the partner died on another date than the dissolution, the source returns nothing, and the adapter answers 404. The PDP does not see the relation. The deceased is not a data subject under the GDPR, so the source logs the requester and the living parents (AV-10).

The preferred design puts the relation in filter arguments of the published schema. The policy then requires these arguments with fixed values (#425, Q-14). In demo v0.9.0, a caller can ask for the death certificate of any BSN, because of the callback gap.

Trust roles and the PuB-EAA provider

Each bronhouder needs its own certificate per role, and one certificate never fills two roles. The following figure shows the roles around one bronhouder:

flowchart LR subgraph legal["Legal role"] bh["Bronhouder<br/>PuB-EAA provider"] end subgraph tech["Technical role"] fac("GBO issuance facility<br/>probably the EEAIssuer") end subgraph certs["Certificate set of one bronhouder"] direction TB iss[("Issuer certificate<br/>production: qualified seal<br/>in a QSCD")] rdr[("Reader certificate<br/>production: access certificate")] stl[("Status-list certificate")] regc("Registration certificate<br/><i>not in the demo</i>") end subgraph open["Open decisions"] direction TB q28("Designation and<br/>supervision: Q-28") q30("Trust list for<br/>PuB-EAA providers: Q-30") q17("Custody of the seal and<br/>status-list keys: Q-17") end bh -- "instructs: validity,<br/>revocation" --> fac fac -- "signs with" --> iss fac --- rdr fac --- stl certs ~~~ open %% GBO diagram palette: the same colors as the Globaal Ontwerp classDef basis fill:#DCFCE7,stroke:#16A34A,color:#0F172A classDef dvtp fill:#FEDCD8,stroke:#E28F80,color:#1E293B classDef eudi fill:#FFEDD5,stroke:#EA580C,color:#0F172A classDef source fill:#DDEBF7,stroke:#6F94B8,color:#1E293B classDef ext fill:#F1EFE8,stroke:#5F5E5A,color:#444441 classDef store fill:#EDE9FE,stroke:#7C3AED,color:#1E1B4B classDef mock fill:#F3F4F6,stroke:#9CA3AF,color:#374151,stroke-dasharray:4 3 classDef planned fill:#FFFFFF,stroke:#9CA3AF,color:#6B7280,stroke-dasharray:6 4 classDef zoom stroke-width:3px class bh source class fac eudi class iss,rdr,stl store class regc,q28,q30,q17 planned class fac zoom style legal fill:#FFFFFF,stroke:#6F94B8,stroke-dasharray:4 3 style tech fill:#F4F5F7,stroke:#EA580C style certs fill:#F4F5F7,stroke:#7C3AED style open fill:#FFFFFF,stroke:#9CA3AF,stroke-dasharray:6 4 click fac "../../../concepts/identity-trust/#eudi-trust-roles" "EUDI trust roles"

Figure 7: Trust roles in PuB-EAA issuance. Dashed: not built or not decided.

ADR-0028 (proposed) divides the roles. The bronhouder is the PuB-EAA provider and stays legally responsible for content, validity and revocation. GBO operates the technical issuance facility and is probably the EEAIssuer in the notification of each bronhouder. The facility is optional: a bronhouder can run its own credential issuer.

Since August 2026, PuB-EAA issuance falls under the same standard as QEAA issuance (F-81). That frame asks for these parts:

  • Designation of the issuer.
  • A registration certificate.
  • A qualified seal in a QSCD.
  • Conformity with ETSI TS 119 471 and TS 119 472-3.

Demo v0.9.0 is not checked against this frame. One development CA fills all roles. Three decisions are open: who designates the issuer (Q-28), on which trust list the providers appear (Q-30), and who holds the keys (Q-17). For the certificate roles, see EUDI trust roles.

Compared with the Globaal Ontwerp

The demo follows flow A of pattern A. It differs in three points:

Point Globaal Ontwerp Demo v0.9.0
Role of GBO A technical, central and optional facility. GBO is not a PuB-EAA provider. The same. The bronhouder is the provider (ADR-0028, proposed).
Authorization An authorization facility (autorisatievoorziening) authorizes the PuB-EAA facility after a PID login. The issuance server checks the PID itself. No separate authorization service exists.
Semantic mapping A shared GBO facility translates the format of the source. The source maps its own fields to claims with gbo-simple-v1. No translation layer exists.

Open points

The main open points in the PuB-EAA provider are these:

  • The callback gap: nothing binds the disclosure to the source query (REQ-45, Q-18).
  • The regulatory frame for PuB-EAA issuance, and who designates the issuer (F-81, Q-28).
  • The trust list for PuB-EAA providers (Q-30).
  • Every attestation is valid for 365 days. GBO gets no change signal from the source, so it cannot revoke on a change (F-82, REQ-77).
  • The source description does not mark the relation behind a related-person attestation (F-47).

Traceability