OID4VP vp_token verification for SD-JWT VC (dc+sd-jwt) and ISO mdoc
(mso_mdoc) presentations (OID4VP §7).
The response is a DCQL-shaped map from credential-query IDs to one or more
presentations. Verification is conn-free. SD-JWT VC entries delegate issuer
signature, disclosure, VC claim, and holder Key Binding JWT checks to
Attesto.SdJwtVc and Attesto.SdJwt; mso_mdoc entries delegate Device-
Response and device-signature checks to Attesto.Mdoc.verify_device_response/4.
Holder key binding is mandatory for SD-JWT VC: a credential without a
cnf.jwk or a valid Key Binding JWT cannot satisfy an OID4VP request. mdoc
presentations carry their own device-signature binding instead.
Format dispatch
Each query ID's format is resolved via the optional :formats option — a
map of query ID to "dc+sd-jwt" or "mso_mdoc" — falling back to shape
detection when a query ID is absent from :formats (or the option itself is
omitted): a ~-delimited string is SD-JWT VC, a plain base64url string is
mso_mdoc. Prefer :formats when the DCQL query is known ahead of time;
detection exists for callers that don't thread it through.
mdoc context
An mso_mdoc entry additionally needs :response_uri — the OID4VP
response_uri the wallet's DeviceResponse was bound to via its
OpenID4VPHandover SessionTranscript. :audience doubles as the
handover's client_id and the shared :nonce as its nonce. Only
unencrypted direct_post is supported (the handover's JWK thumbprint is
always nil). SD-JWT-VC-only callers never need :response_uri; it is
required only when the vp_token actually contains an mso_mdoc entry.
Summary
Functions
Verify an OID4VP vp_token carrying SD-JWT VC and/or mso_mdoc presentations.
Types
Functions
@spec verify( term(), keyword() ) :: verify_result()
Verify an OID4VP vp_token carrying SD-JWT VC and/or mso_mdoc presentations.
Required options are :nonce, :audience, and exactly one issuer trust
source: :issuer_jwks for static issuer keys or :resolve_issuer for a
callback receiving the presentation's (unverified) issuer identity. The
optional :now value is passed to both the VC/mdoc and holder-binding
verifiers. See the moduledoc for :formats and :response_uri.
:resolve_issuer receives UNVERIFIED issuer material
For SD-JWT VC, the callback is handed the iss peeked from the still-
unverified issuer JWT (verifying the signature requires the key, which
requires iss — so the lookup is unavoidably ahead of verification). For
mso_mdoc, it is handed the docType peeked from the still-unverified
DeviceResponse in the same way. The signature is then checked against
whatever keys the callback returns, so forged issuer material cannot forge
a credential — it only misdirects the key lookup. But the callback MUST
NOT make a network request derived from this value without an allow-list:
the presenter controls it, so a naive fetch is an SSRF sink. Resolve from
a trusted issuer registry, not by dereferencing the peeked value.
A string presentation produces one safe result for its query ID. A list of
presentations produces a list of safe results. Raw JWTs and raw
DeviceResponse bytes are never returned.