Skip to content

feat(openid4vci): parse untrusted Credential Offers and Issuer Metadata - #177

Merged
luisgf merged 1 commit into
mainfrom
feat/openid4vci-parsers
Aug 13, 2026
Merged

feat(openid4vci): parse untrusted Credential Offers and Issuer Metadata#177
luisgf merged 1 commit into
mainfrom
feat/openid4vci-parsers

Conversation

@luisgf

@luisgf luisgf commented Aug 13, 2026

Copy link
Copy Markdown
Owner

What

parse_credential_offer (OID4VCI 1.0 §4.1.1) and parse_credential_issuer_metadata (§11.2.3) in openvc.openid4vci — the consume-and-verify half of OID4VCI discovery, per ADR-0007 D7 (parsers in, builders out).

  • Frozen, fully-defaulted dataclasses CredentialOffer / CredentialIssuerMetadata (registered in test_return_contract.py), typed errors CredentialOfferMalformed / IssuerMetadataMalformed, wire constants GRANT_AUTHORIZATION_CODE / GRANT_PRE_AUTHORIZED_CODE.
  • Fail-closed: credential_issuer and every endpoint URL must be absolute https; credential_configuration_ids non-empty/distinct/non-empty strings; batch_size an int ≥ 2; unknown grants members and all extension points preserved (typed fields + raw), never dropped. Nothing is fetched.

Adversarial review (attacker-controlled JSON)

Spawned per the runbook. One merge-blocking finding + three follow-ups, all fixed in this branch with regression tests:

  • F1 (HIGH)urlparse ValueError escaped the typed taxonomy on hostile URLs (https://[::1). Now wrapped, and hostname/port are forced so lazy ValueErrors raise here, not in the caller's fetch code.
  • F2 (MEDIUM)urlparse silently strips \t\r\n; the stored identifier would disagree with the host an HTTP client connects to (and with the signed aud). Control chars/whitespace now rejected.
  • F3 (MEDIUM) — userinfo accepted (https://legit@evil), plus query/fragment on an identifier that compares byte-for-byte against aud. All rejected.
  • F4 (LOW)https://. / bad ports accepted. Subsumed by the forced-accessor fix.

Golden vectors

Happy paths run against material we did not write: the spec's own §4.1.1/§4.1.2/§11.2.3 examples (new spec/ fixtures with _provenance) and the recorded EU-reference-issuer offer/metadata artifacts already vendored by #147. The hostile corpus covers deep nesting, wrong types, empties, duplicates, and every URL bypass above.

Gate

flake8 + mypy + 1758 tests green; test_docs_blocks executes the new wiki snippet. CHANGELOG entry under 1.25.0 — unreleased.

Closes #142

Add parse_credential_offer (OID4VCI 1.0 §4.1.1) and
parse_credential_issuer_metadata (§11.2.3) to openvc.openid4vci — the
fail-closed discovery parsers of ADR-0007 D7, returning the frozen
CredentialOffer / CredentialIssuerMetadata dataclasses. The issuer
identifier and every endpoint URL must be absolute https, hardened by
adversarial review: control characters, userinfo, query/fragment and
lazily-raising ports/brackets are all rejected with typed errors, so the
stored identifier compares byte-for-byte against a key proof's aud and no
raw ValueError escapes the taxonomy.

Pinned against the spec's own §4.1.1/§4.1.2/§11.2.3 examples and the
recorded EU-reference-issuer artifacts from #147.

Closes #142
@luisgf
luisgf merged commit 44c58b3 into main Aug 13, 2026
13 of 14 checks passed
@luisgf
luisgf deleted the feat/openid4vci-parsers branch August 13, 2026 19:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(openid4vci): parse untrusted Credential Offers and Issuer Metadata

1 participant