HAIP 1.0 — implementation notes
What CodeB’s EU Wallet Verifier implements against the OpenID for Verifiable Credentials — High Assurance Interoperability Profile published by the OpenID Foundation. Section-by-section, with honest gaps flagged.
Summary
| Area | Status | What we implement |
|---|---|---|
| Credential format | Implemented | SD-JWT VC with KB-JWT holder binding |
| Signing algorithm | Implemented | ES256 (P-256 ECDSA), IEEE P1363 signature encoding |
| Client Identifier Prefix | Implemented | Both x509_san_dns and x509_hash; wallet chooses via ?client_id_prefix= |
| Signed Authorization Request (JAR) | Implemented | typ: oauth-authz-req+jwt, x5c in JOSE header, per-tenant verifier key |
| Query language | Implemented | DCQL only (Presentation Exchange deliberately not shipped) |
| Response mode | Implemented | direct_post.jwt with JWE-encrypted response |
| Response encryption | Implemented | ECDH-ES + A128GCM, ephemeral P-256 keypair per session |
| Key Binding JWT verification | Implemented | KB-JWT signature + nonce + aud checked |
| x5c chain handling | Implemented | Every VP x5c chain parsed, stored per tenant, exposed to downstream RPs via the verified-claims file |
| Transaction data (per-session) | Implemented | Signed request payload accepts arbitrary transaction_data that the integrator validates per use case |
Section notes
§3 Credential format — SD-JWT VC
The verifier parses IETF SD-JWT VC compact form, walks the disclosure array, verifies each disclosure hash against the signed payload’s _sd array (SHA-256 by default; SHA-384 / SHA-512 accepted per registry). The KB-JWT is required — a VP without one fails the verify step. Verified claims land at App_Data/<tenant>/vc-claims/<user>.json and are relayed through the standard OIDC userinfo endpoint under a vc claim group.
§4 Cryptographic suites
Signing: ES256 exclusively for verifier-side JWS. .NET Framework 4.7.2+ ECDsa.SignData(byte[], HashAlgorithmName) emits IEEE P1363 (raw r||s) natively — no DER-to-P1363 conversion needed. Wallet-side signatures accepted: ES256, ES384, ES512, EdDSA (Ed25519 via BouncyCastle).
Response JWE: alg = ECDH-ES, enc = A128GCM, ephemeral P-256 keypair minted per session and destroyed after use. NIST SP 800‑56A Concat KDF, RFC 7518 §4.6 / §5.1 shape.
§5 Client Identifier — both prefixes shipped
The default is x509_hash (OID4VP 1.0 §5.9.3, wallet-preferred: no DNS resolution needed at wallet-side, works on isolated networks). x509_san_dns is available for callers that request it via ?client_id_prefix=x509_san_dns. Both derive from the same per-tenant verifier X.509 leaf, so switching prefixes doesn’t force re-issuance.
§6 Signed Authorization Request (JAR)
Request body: JWT with typ: oauth-authz-req+jwt, x5c in JOSE header (single self-signed cert in dev, operator-supplied chain in production), ES256 signature. Payload fields per HAIP: client_id with the prefix scheme applied, response_type = vp_token, response_mode = direct_post.jwt, response_uri pinned to our ?vp-response=<session>, nonce, state, client_metadata including the JWK for response encryption, dcql_query object.
§7 DCQL query language
Only DCQL is shipped. Presentation Exchange 2.0 is not implemented — DCQL is the OID4VP 1.0 Final mandatory query language and the EUDI reference wallets implement DCQL first. Adding PE would double the query surface for a use case the tender does not require.
§8 Response processing
JWE decrypted with the ephemeral P-256 private key held server-side for the session lifetime. The wallet’s ephemeral public key is read from the JWE header (epk). Inner JWT is the VP with the SD-JWT VC + disclosures + KB-JWT. Verifier walks: (1) JWS signature over the SD-JWT, (2) disclosure hashes match _sd, (3) KB-JWT signature over the SHA-256 of the JWT+disclosures, (4) KB-JWT nonce matches our request nonce, (5) KB-JWT aud matches our client_id, (6) status of each disclosure recorded on the tenant’s VC claims file.
§10 Trust framework
Trust anchor is a per-tenant explicit X.509 leaf, suitable for pilots and controlled deployments. Every verified presentation carries an __iter field (visible in the tenant’s VC claims file and in the audit log) so operators and downstream RPs can see exactly which trust regime backed a given assertion. The current runtime writes iter=1 (per-tenant explicit anchor) into App_Data/<tenant>/vc-claims/<user>.json.
What we do not claim
- We are not a notified national EUDI Wallet under Article 5a of the eIDAS 2 Regulation. That designation is reserved for Member State schemes.
- We are not a Qualified Trust Service Provider under Article 3(19) — QSCD, QES issuance and remote signing per ETSI TS 119 431 are out of scope.
- We do not hold OIDF HAIP conformance certification. When we do, the badge and the certificate link will land on this page.
- We are not an issuer of Person Identification Data (PID). The verifier accepts PIDs signed by other issuers via the LOTL trust chain.
What we do claim
- A working OID4VP 1.0 verifier that implements the HAIP client_id and JAR profiles.
- SD-JWT VC parsing with disclosure verification and KB-JWT holder binding.
- Both
x509_hashandx509_san_dnsClient Identifier Prefixes, per-request switchable. - DCQL query support (Presentation Exchange deliberately excluded).
- ECDH-ES + A128GCM JWE-encrypted response with per-session ephemeral keys.
- Per-tenant multi-domain isolation; no shared cross-tenant trust store.
- Verifier interaction audit trail via the standard
vp-*events (see api-docs).
Evidence you can inspect
- Live verifier at /logineu.html — every page load mints a fresh signed JAR.
- The current signed authorization request is exposed on the same page in the diagnostic panel; open your browser dev tools and copy the JWT to jwt.io to inspect.
- The public JWKS with the verifier’s current signing key is served at
/.well-known/openid-configurationand/oidc.ashx?jwks_uri=1. - The API documentation lists every
vp-*event we emit for verifier interactions.
Iteration — what shipped since these notes were first written
- Sign-in widget — drop-in
<script src="signin-widget.min.js">renders a branded button + QR modal on any host page. Firescodeb:wallet-signed-in. See the live demo. - Per-tenant RP verifier certificate CA — every OIDC client on
oidc-clients.htmlcan now download an X.509 leaf, signed by a root that lives atApp_Data/<tenant>/rp-ca/. Wallets that walk the RP’sx5cchain during OID4VP pin identity to this tenant CA. Note: private per-tenant CA (a “WRPAC-lite” for deployments that manage their own trust surface), NOT a national Relying-Party Access CA. Seerp-cert.ashx. - Verifier audit UI — every
vp-*event that hits the tenant is browsable at /vc-audit.html with CSV export. - Presentation-request builder — five templates on /oidc-clients.html generate DCQL + Presentation Exchange 2.0 JSON side-by-side.
- OpenID4VCI issuer (SD-JWT VC) — per-tenant issuer at
/vci.ashxmints SD-JWT VCs for non-regulated attestations (membership, employee ID, event tickets). Admin console at /vci-admin.html. Uses an auto-generated ECDsa P-256 signing key stored atApp_Data/<tenant>/vci/issuer.pfx. Not a PID Provider; not tied to any national eID. - Wallet interoperability self-test — public page at /verifier-test.html where any wallet vendor can point their wallet at our verifier and get a JSON conformance report (JAR signature, x509_hash client_id, DCQL, JWE-encrypted response, KB-JWT, SD disclosures, aud match).
Spec reference: openid4vc-high-assurance-interoperability-profile-1_0 · last reviewed 2026-07-12