Attesto.VpToken (Attesto v1.10.0)

Copy Markdown View Source

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

mdoc_safe_result()

@type mdoc_safe_result() :: %{
  doc_type: String.t(),
  namespaces: map(),
  device_namespaces: map(),
  validity: map()
}

safe_result()

@type safe_result() :: %{
  vct: String.t(),
  iss: String.t(),
  claims: map(),
  cnf: map() | nil
}

verify_result()

@type verify_result() :: {:ok, map()} | {:error, term()}

Functions

verify(vp_token, opts \\ [])

@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.