HAIP Verifier — operator setup
Runbook for IT admins deploying the CodeB HAIP Verifier on a per-tenant IIS site. Covers the three operator-supplied trust-anchor stores, the two related tenant settings, and the post-deploy smoke tests that confirm each store is wired up correctly.
mdoc_verify_not_configured, wallet_attestation_not_configured) rather than silently trusting anything. This page is what an operator does before going live.
1. mDoc issuer anchors
- Directory
App_Data/<tenant>/trust/mdoc-issuers/- File format
*.ceror*.crt— DER- or PEM-encoded X.509 certificates- Purpose
- Trust roots for ISO 18013-5 mDoc credentials. The verifier walks the
x5chainin everyDeviceResponseIssuerAuthCOSE_Sign1and requires termination at one of these anchors. - Missing state
- HTTP 501
mdoc_verify_not_configuredon every mDoc credential-verify call and on any VP whose token starts with anmso_mdocCBOR map header. - How to obtain
- From your PID issuer or the issuing authority of the member state whose mDL / PID you want to accept. Also usable for your own MDocIssuer test root.
New-Item -ItemType Directory -Force ` -Path 'C:\inetpub\wwwroot\<tenant>\App_Data\<tenant>\trust\mdoc-issuers' Copy-Item .\pid-issuer-root.cer ` 'C:\inetpub\wwwroot\<tenant>\App_Data\<tenant>\trust\mdoc-issuers\'
2. Wallet-provider anchors — HAIP §5.11
- Directory
App_Data/<tenant>/trust/wallet-providers/- File format
*.jwks.json(JSON Web Key Set) or*.pem(SPKI or X.509 CERT)- Purpose
- Public keys that signed the Wallet Attestation JWT presented by a wallet on OID4VP requests (draft-ietf-oauth-attestation-based-client-auth).
- Missing state
- HTTP 501
wallet_attestation_not_configuredwhenever a wallet supplieswallet_attestationor the tenant hasOidc:WalletAttestationRequired=true. - How to obtain
- From the wallet vendor's attestation-issuer service (JWKS endpoint export) or the operator you have contracted to issue attestations.
Invoke-WebRequest 'https://wallet-vendor.example/attestation-issuer/jwks.json' ` -OutFile 'C:\inetpub\wwwroot\<tenant>\App_Data\<tenant>\trust\wallet-providers\vendor-a.jwks.json'
3. SD-JWT VC issuer anchors — LOTL overlay
- Directory
App_Data/<tenant>/trust/vc-issuers/(preferred). Legacy fallback:App_Data/<tenant>/trust/mdoc-issuers/.- File format
*.ceror*.crt— DER- or PEM-encoded X.509- Purpose
- Operator-supplied X.509 anchors in a directory modelled on the ETSI TS 119 612 shape (a LOTL-shaped local trust store, not an official EU List-of-Trusted-Lists fetched from a Member State-notified endpoint). Iter-3 / iter-3b of the SD-JWT VC issuer chain gate in
oidc.ashxrequires thex5cchain in the VC's JWS header to terminate at one of these operator-loaded anchors. - Missing state
- If both this dir and the fallback are empty, the built-in LOTL cache is used alone. When the LOTL cache also has no match, behaviour depends on
Oidc:LotlRequireChain(now defaults totruefrom buildhaipclose-cominotto). Strict mode → HTTP 400 unresolved; explicit non-strict (Oidc:LotlRequireChain=false) → iter-2 SAN-only path or lower — unless the tenant is running in production EUDI profile (eudiProductionProfile=trueordcqlTrustedAuthoritiesMode=all), in which case the override is refused and alotl-chain-compliance-overrideevent is logged. - How to obtain
- Extract certificates from the official EU List-of-Trusted-Lists at
ec.europa.eu/tools/lotl/eu-lotl.xmlor from your member state's Trusted List (TSL) and drop them into this directory as .cer / .crt files (an automated ETSI TS 119 612 LoTE fetcher / chain-walker is on the roadmap — today this is a manual operator step), or use any issuer root you have received out-of-band.
Per-tenant settings
| Setting | Default | Effect when enabled |
|---|---|---|
| Oidc:WalletAttestationRequired | false | Verifier advertises wallet_attestation_required=true in the JAR and rejects OID4VP responses without a valid wallet_attestation JWT. |
| Oidc:LotlRequireChain | true (default flipped 2026-07-26 haipclose-cominotto) | Strict-mode SD-JWT VC: iter-3 that cannot resolve the x5c chain fails closed (HTTP 400). Non-strict fallback (SAN-only) is available by setting the value explicitly to false, but the production EUDI profile refuses that downgrade. Per-session override ?lotl_require_chain=1|0 at vp-start (documented in eu-wallet-api); ignored in the production EUDI profile. |
| Oidc:StatusListCheckMode | enforce (default) | OAuth Status List (draft-ietf-oauth-status-list-13) revocation check. enforce rejects revoked/suspended credentials; warn logs and continues; skip disables the fetch. Production EUDI profile forces enforce. LOUD [STATUSLIST-DIAG] traces at every branch. Per-session override ?status_list_check=enforce|warn|skip at vp-start; ignored in the production EUDI profile. |
| Vci:DpopMode | optional | RFC 9449 DPoP proofs at the OpenID4VCI ?token + ?credential endpoints. optional accepts DPoP-bound tokens and legacy Bearer; enforce requires DPoP proofs on both endpoints (HAIP-strict); off disables. LOUD [DPOP-DIAG] traces. |
| Vci:ClientAuthMode | optional | attest_jwt_client_auth at OpenID4VCI ?par + ?token. optional accepts both plain client_id whitelist and attestation-based auth; enforce rejects requests without a valid <WalletAttestation>~<PoP> assertion (HAIP-strict); off disables. LOUD [VCI-CLIENT-AUTH-DIAG]. |
| Vci:KeyAttestationMode | optional | ARF TS3 Key Attestation acceptance on ?credential (both mDoc/JWT and SD-JWT/JWT proof paths). optional verifies when the wallet presents one and ignores otherwise; require rejects wallets that do not present a key attestation (400 key_attestation_required); off skips the gate entirely. Trust anchors live under App_Data/<tenant>/trust/key-attestation-issuers/*.pem (also .cer, .crt). LOUD [KEY-ATTESTATION-DIAG] traces. |
| Vci:KeyAttestationRequireSealed | false | ETSI TS 119 182-1 JAdES Baseline B / ARF July 2026 — when true, the leaf certificate of the Key Attestation x5c chain MUST carry EKU OID 0.4.0.194112.1.4 (id-kp-EIDASSeal). Absence rejects with cert_not_sealed. Default false accepts both signing and sealing certificates. Applies to both key-attestation+jwt and key-attestation+jades typ paths. LOUD [KEY-ATTESTATION-DIAG] sealed-vs-signed trace on every check. |
| eudiProductionProfile | false | When true, forces Oidc:LotlRequireChain=true, Oidc:StatusListCheckMode=enforce, and clamps Oidc:PidRetentionSeconds=-1 back to 30 days. Any tenant attempt to weaken these is logged as a compliance override. |
| Oidc:WalletClaimDefaultAllowAll | false | Backward-compat escape hatch for the per-OIDC-client wallet-claim allowlist. Default false means when no allowlist file exists at App_Data/<tenant>/oidc-clients/<client_id>/wallet-claim-allowlist.json the merged wallet claims are stripped from the userinfo response for that client (safe-by-default per VC default-on). Setting true restores the pre-hardening behaviour and passes every wallet-derived claim through. RASP: allowlist file capped 8 KB, allow array capped 32 entries, each entry regex ^[a-z][a-z0-9_]{0,63}$. LOUD [WALLET-CLAIM-ALLOWLIST-DIAG] at every branch. See eu-wallet-api § wallet-claim-allowlist. |
| Oidc:PidRetentionSeconds | 2592000 (30 days) | Retention TTL for verified Person Identification Data (PID) claims persisted from a European Digital Identity Wallet presentation to the tenant user record. 0 = never persist (session-only, write path is skipped); positive integer = seconds until lazy expiry on the next userinfo read; -1 = indefinite (opt-out) and refused in the production EUDI profile (clamped back to 30 days with a pid-retention-compliance-override audit event). Enforcement is lazy on read; no active sweeper this round. LOUD [PID-RETENTION-DIAG] traces at write, expired-lazy, skip-write, and COMPLIANCE-OVERRIDE branches. See eu-wallet-api § pid-retention. |
| Ts7:DisableDeletionRequests | false | ARF 3.0 TS7 data-deletion request endpoint (POST /oidc.ashx?action=data-deletion-request). Default enabled per VC default-on rule. Set true to make the endpoint return 404 for tenants that route deletion requests through a separate channel. |
| Ts7:DeletionQueueMaxEntries | 10000 | Hard cap on the number of queued deletion request files under App_Data/<tenant>/data-deletion-queue/. When exceeded, the endpoint returns 503 with Retry-After: 600. Operator drains the queue by reviewing + removing processed files. |
| WebPhone:AdminSharedSecret | (unset) | HMAC-SHA256 shared secret for the X-CodeB-Admin-Signature header on the TS7 listing (?action=data-deletion-list) and TS8 DPA-report (?action=dpa-report) endpoints. Bucket string is ts7-ts8-admin. When unset, the two endpoints accept only OIDC Bearer JWTs with role=admin or role=superuser. Meta-audit trails are written to App_Data/<tenant>/audit/deletion-requests-YYYY-MM.log.jsonl and dpa-reports-YYYY-MM.log.jsonl. |
| Csc:CryptoModuleProvider | software | Provider that backs every CSC v2 signing operation (credentials/authorize, signatures/signHash). software (default, shipped) → ECDsa P-256 signing certificate per tenant, private key in Windows CNG LocalMachine key set, PFX password wrapped with DPAPI at LocalMachine scope, three-tier lifecycle (on-disk PFX → wacs-issued cert → auto-generated self-signed subordinate). hsm-azurekv and hsm-pkcs11 are reserved for future HSM connectors and currently return HTTP 501 with crypto_module_not_configured. Unknown values fail-closed with the same error. The CSC v2 credentials/info response advertises the shipped module in key.protection, key.moduleType, and key.moduleCertification. LOUD [CRYPTOMOD-DIAG] traces at every branch. See eu-wallet-api § crypto-module-architecture. |
| Recording:AppDataRoot | (unset) | Overrides the default App_Data/<tenant>/recordings/ output directory — useful for pointing at a dedicated volume. |
Set via App_Data/<tenant>/appsettings.json or through the tenant admin UI (Settings → OIDC / Recording).
Post-deploy smoke tests
Every script targets a running tenant and either exits 0 (green) or prints a diagnostic. Run them from any Windows/Linux host that can reach the tenant HTTPS endpoint.
| Script | What it exercises |
|---|---|
| testscripts/mdoc-cred-verify-smoke.py | Round-trips a self-issued mDoc through the credential-verify endpoint. Green means anchors under trust/mdoc-issuers/ are loaded and chain-walk succeeds. |
| testscripts/wallet-attestation-smoke.py | Sends an OID4VP flow with a Wallet Attestation JWT and PoP. Green means anchors under trust/wallet-providers/ are loaded and JWT verifies. |
| testscripts/lotl-iter3-smoke.py | Presents an SD-JWT VC with an x5c chain and confirms iter-3 walks up to an anchor in the operator-loaded LOTL-shaped local trust store. |
| testscripts/wa-pop-selfcheck.py | Exercises the Wallet Attestation verifier's negative paths (bad alg, missing cnf, missing iat, PoP nonce/aud mismatch) without production credentials. Green means all reject branches surface their expected diagnostic reasons. |
| testscripts/dcql-smoke.py | Drives an OID4VP 1.0 FINAL DCQL query end-to-end — a good final sanity check after all three anchor stores are in place. |
For DPIA + RoPA templates see /dpia.html and /ropa.html — tenant DPO adapts and signs off; not legal advice.
Last updated 2026-07-26 (haipclose-cominotto). Related pages: HAIP Verifier conformance · EU Wallet Verifier reference · EU Wallet integrations · Verifier cookbook · API reference · DPIA template · RoPA template · Deutsch