โ† all docs

OID4VC High Assurance Interoperability Profile (HAIP) โ€” Conformance Audit

Spec: openid.net/specs/openid4vc-high-assurance-interoperability-profile-sd-jwt-vc-1_0.html
Audited: 2026-05-16 (original audit, against walt.id 0.18.2)
Gap 1 re-audited: 2026-08-24 (see note below) โ€” the other findings below have
not been re-audited and may also be stale; only Gap 1 was checked.
Scope: verifiably-go as orchestrator (not issuer/verifier DPG); walt.id
0.23.1 is the version actually deployed today (deploy/compose/stack/docker-compose.yml).


Legend

Symbol Meaning
โœ… Conformant
โš ๏ธ Partial / conditional
โŒ Gap โ€” action required
N/A Not applicable to this component

1. Credential Format

Requirement Status Notes
SD-JWT VC (vc+sd-jwt) MUST be supported โœ… wired via sd_jwt_vc (IETF) in schema catalog
mdoc (mso_mdoc) SHOULD be supported โš ๏ธ format recognized, no revocation support
W3C VCDM 2.0 (ldp_vc) is out of scope for HAIP N/A still supported for non-HAIP flows

2. Issuance (OID4VCI)

Requirement Status Notes
Authorization Code Flow โœ… delegated to walt.id
Pre-authorized Code Flow โœ… used in single / bulk issuance
credential_offer URI via QR โœ… offerURI returned and surfaced to operator
issuer_state binding โœ… handled by walt.id
Proof-of-possession (DPoP / key-binding) โš ๏ธ enforced by walt.id; verifiably-go doesn't inspect proof
Issuer metadata at /.well-known/openid-credential-issuer โš ๏ธ served by walt.id; verifiably-go proxies the offer, not the metadata
credential_identifier in offer โœ… schema-id mapped to credential config
Batch issuance (/batch_credential) โš ๏ธ bulk.go issues N single credentials; no true batch endpoint
Display metadata for credentials โš ๏ธ SchemaName propagated; display array not surfaced

3. Presentation (OID4VP)

Requirement Status Notes
vp_token returned via response_mode=direct_post โœ… verified in internal/adapters/waltid/verifier.go
response_mode=direct_post.jwt (HAIP REQUIRED) โŒ Gap moved, not closed (re-verified 2026-08-24): walt.id verifier-api:0.23.1 genuinely implements JARM โ€” a live POST /openid4vc/verify with responseMode: direct_post.jwt returns a real encrypted request (client_metadata.jwks.keys[0] EC P-256 use:enc/alg:ECDH-ES, authorization_encrypted_response_enc:"A256GCM"), confirmed via a real container, not source reading. But internal/adapters/waltid/verifier.go's verifyBody struct has no response_mode/responseMode field at all (grepped โ€” zero matches) and never sends one, so verifiably-go doesn't request it even though the backend can now serve it. The gap is in this repo's adapter code, not in walt.id.
client_id_scheme=x509_san_dns (HAIP REQUIRED) โŒ Gap: walt.id uses redirect_uri as client_id; x509 cert not provisioned
client_id_scheme=did โœ… current default in walt.id
Presentation exchange (DIF PE) โœ… presentation_definition wired via OID4VP template
nonce freshness โœ… session state enforces one-time use
SD-JWT selective disclosure โœ… disclosed fields extracted and stored in DisclosedFields
vp_token as SD-JWT (not LD-Proof) โœ… verified by regression tests in vp_token_regression_test.go

4. Key Binding & Wallet Attestation

Requirement Status Notes
wallet_attestation JWT in token request โŒ Gap: HAIP ยง6 requires wallets to present a wallet attestation signed by the wallet provider; not enforced in verifiably-go (would require verifier-side attestation validation)
Holder key binding proof (cnf claim) โš ๏ธ enforced by walt.id wallet-api; verifiably-go doesn't verify cnf independently
Key binding JWT (kb-jwt) over vp_token โš ๏ธ present in SD-JWT VC wire format; verifiably-go extracts claims without re-verifying kb-jwt signature

5. Security

Requirement Status Notes
TLS for all endpoints (HAIP ยง4.1) โœ… enforced by Caddy in subdomain mode; localhost mode exempt per spec
Authorization Code PKCE โœ… PendingPKCE stored in session, passed through OIDC flow
DPoP token binding (HAIP RECOMMENDED) โš ๏ธ not enforced by verifiably-go; delegated to walt.id
iss claim validation in responses โš ๏ธ verified by walt.id; verifiably-go trusts the adapter's success/error
Status list revocation (HAIP ยง7.2) โœ… W3C BSL 2023 + IETF Token Status List both implemented
Credential expiry (exp claim) โš ๏ธ set by walt.id; not enforced by verifiably-go on fetch

6. Identified Gaps โ€” Priority Order

โŒ Gap 1: response_mode=direct_post.jwt (JARM)

Requirement: HAIP ยง5.5 โ€” response MUST be JWT-encrypted to the verifier's public key.
Current state (re-verified 2026-08-24 against a real waltid/verifier-api:0.23.1
container):
the walt.id upstream support this gap used to be blocked on now
exists
โ€” POST /openid4vc/verify with responseMode: direct_post.jwt returns
a genuinely JARM-encrypted authorization request (real jwks.keys, ECDH-ES /
A256GCM). But verifiably-go itself never asks for it:
internal/adapters/waltid/verifier.go's verifyBody struct has no
response_mode field, so every request this repo sends still gets plain
direct_post. The gap moved from "walt.id doesn't support this" to "this
repo's adapter doesn't request it."
Risk: Credential claims in transit are visible to redirect intermediaries.
Fix path:

  1. Add a ResponseMode string \json:"responseMode,omitempty"`(or the exact casing walt.id'sVerifierApi.ktexpects โ€” confirm against a live call before shipping, don't assume the casing matches the OID4VP spec's snake_case) field toverifyBodyininternal/adapters/waltid/verifier.go, set to "direct_post.jwt"`.
  2. Provision the verifier's encryption key and confirm it's the one walt.id
    echoes back in client_metadata.jwks (it was auto-generated in the
    container tested here โ€” confirm whether that's stable across restarts or
    needs explicit provisioning for a real deployment).
  3. Update the OID4VP consumer side (any wallet this ecosystem talks to) to
    decrypt a JARM response โ€” this is no longer walt.id-blocked, so it's the
    next real blocker once the adapter change above ships.

Effort: Sโ€“M โ€” no longer blocked on walt.id; is now a real, scoped adapter
change plus wallet-side decrypt support.

โŒ Gap 2: client_id_scheme=x509_san_dns

Requirement: HAIP ยง4.3 โ€” verifiers in high-assurance flows MUST authenticate with an X.509 certificate whose SAN DNS matches the client_id.
Current state: client_id is the verifier's redirect_uri (scheme redirect_uri).
Risk: Wallet cannot distinguish a legitimate verifier from a phishing site without a certificate.
Fix path:

  1. Issue a TLS certificate for the verifier subdomain (already done via Caddy/Let's Encrypt in subdomain mode).
  2. Configure walt.id verifier-api client_id_scheme = "x509_san_dns" and point to the cert.
  3. verifiably-go passes client_id through the OID4VPTemplate; add ClientIDScheme field to the template type.

Effort: M โ€” config change in walt.id + schema extension in verifiably-go.

โŒ Gap 3: wallet_attestation

Requirement: HAIP ยง6 โ€” issuer MUST validate wallet attestation JWT before issuing.
Current state: attestation not required or validated.
Risk: Any OAuth client that has the authorization code can impersonate a conformant wallet.
Fix path:

  1. Add wallet_attestation validation to the issuance flow in APIIssue / walt.id adapter.
  2. Trust anchor (wallet provider public key) must be fetched from a trusted registry or pinned.

Effort: Lโ€“XL (depends on whether a public wallet attestation registry exists for the target wallets).


7. What's Already HAIP-Compliant

  • SD-JWT VC credential format end-to-end (issue โ†’ present โ†’ verify disclosed fields)
  • OID4VCI Pre-auth + Authorization Code flows via walt.id
  • OID4VP with DIF PE presentation_definition
  • W3C BSL 2023 + IETF Token Status List revocation
  • PKCE in OIDC authorization flows
  • TLS termination via Caddy in production mode
  • Nonce freshness (one-time use enforced via session state)
  • Per-issuer credential scoping (OwnerKey)

8. Recommended Closure Plan

Priority Item Version target
P1 Add response_mode: direct_post.jwt to verifyBody in the walt.id adapter โ€” walt.id 0.23.1 already supports JARM, verifiably-go just isn't asking for it (re-verified 2026-08-24) verifiably-go sprint, no walt.id upgrade needed
P1 Configure client_id_scheme=x509_san_dns in verifier-api โ€” not re-audited against 0.23.1, may also have changed Same release cycle
P2 Add ClientIDScheme field to OID4VPTemplate and thread it through RequestPresentation verifiably-go sprint
P3 Research wallet attestation registry for target wallets (EUDI, inji) Architecture spike
P3 Add kb-jwt signature verification in verifier adapter After trust anchor decision
Source: docs/haip-conformance.md