openid / openid/OpenID4VCI

v1.1 - Interactive Authorization Endpoint (Presentation-During-Issuance): Cross-Session Holder-Binding Confusion

Open
#742 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
No language data
Stars
125
Forks
41
PR merge metrics
No merged PRs in 30d

Description

Interactive Authorization Endpoint (Presentation-During-Issuance): Cross-Session Holder-Binding Confusion — Tamarin-Verified Attack and Fix

In the Interactive Authorization Endpoint (IAE) "presentation-during-issuance" flow, the Credential Issuer acts as an OpenID4VP Verifier: to obtain a derived credential the Wallet must first present an existing one. The new credential's subject claims are taken from the presented credential, while its holder-binding key is taken from the issuance key proof — and §6 mandates no cryptographic link between the two. We show that, under the spec as written (v1.1 editor's draft), this lets an adversary obtain a credential carrying an honest victim's subject claims but bound to a holder key the adversary controls, i.e. a usable impersonation credential. Crucially, the attack requires no compromise of any long-term key — not the Issuer's, not the Authorization Server's and not the victim's holder key — which distinguishes it from token-leak attack classes.

We have formally verified the attack in Tamarin against the protocol as written, evaluated a natural fix that fails for a non-obvious reason that formal analysis surfaces, and verified two configurations that close it: a holder-key-continuity check, and a more flexible presenter-signed commitment that closes the attack while preserving per-credential key unlinkability. The fixes anchor the missing binding at material the Issuer already trusts (the accepted presentation's holder key), not at the Wallet's freely re-mintable inputs.

This corresponds to the "generalized holder-binding / subject gap" instantiated in the new IAE machinery: §6 mandates no linkage between the key/subject of the presented credential and the key/subject of the credential being issued, so the protocol permits a trace in which the entity that completes the presentation step is not the entity whose key is bound into the new credential.

Affected Spec

OpenID for Verifiable Credential Issuance 1.1 (editor's draft, openid-4-verifiable-credential-issuance-1_1-01). The Interactive Authorization Endpoint is new in v1.1; the synchronous and pre-authorized-code flows in 1.0 are not affected by this issue. Section numbers are per the editor's draft and may shift before v1.1 Final.

  • §6 (Interactive Authorization Endpoint) — the flow under which the attack manifests. For the urn:openid:dcp:iae:openid4vp_presentation interaction type, the OpenID4VP request is embedded in the Issuer's require_interaction response and the VP is returned to the same endpoint via response_mode = iae_post / iae_post.jwt. §6 says nothing requiring the holder/subject of the presented VP to equal the holder/subject of the credential being issued, and mandates no proof of same-end-user control across the two steps; what the Issuer (acting "as a Verifier") does with the presented claims is treated as out of scope.
  • §6.2.1.4 (Preventing Session Fixation Attacks) — requires the AS to associate the OpenID4VP nonce with the auth_session and to verify the presentation uses the same nonce. This is a per-flow server-state association (nonce ↔ session), not a term-level cryptographic binding of holder keys. It does not close this gap (and the model confirms it).
  • §6.2.1.1 — explicitly allows the presented VP's Credential Format to differ from the format used for issuance, reinforcing that the two stages are only loosely coupled.
  • Appendix A.2.5 / A.3.5 (Holder-Binding audience) — the VP's holder-binding audience must be iar:<IAE-URL>, binding the presentation to the Issuer endpoint URL and nothing else (in particular, not to any issuance key).
  • §9 / key proof (openid4vci-proof+jwt) — the issuance key proof is bound to the Credential Issuer Identifier and to nonce-endpoint freshness, but not to the authenticated/presented subject. Whoever completes the issuance exchange chooses the binding key.
  • §13.4 (Split-Architecture Wallets) — already acknowledges the principal real-world adversary (a malicious/compromised provider-operated Wallet backend) but provides no mitigation for this cross-stage binding gap.

The corresponding holder-binding requirements I-40 and P-50 of the companion document Security and Trust in the OpenID4VC Ecosystem (§4.3) exist as requirements but are not protocol-enforced for this path.

Threat Model

The adversary is a Dolev–Yao adversary (Web Attacker + Network Attacker per §6.1 of the companion document). The distinguishing feature of this issue is what the adversary does not need:

  • It does not compromise the Credential Issuer's signing key.
  • It does not compromise the Authorization Server's signing key.
  • It does not compromise the victim's holder private key, response-encryption key, or any other long-term secret.
  • It does not rely on any leaked access token, DPoP key, or transaction ID.

The only adversary capabilities required are:

  1. The adversary runs its own issuance session with the same Credential Issuer CI (it is, or can act as, a Wallet talking to CI), generating its own holder keypair (skH_Adv, pkH_Adv).
  2. The adversary can cause an honest holder's genuine presentation to be produced against CI's (Issuer-signed) presentation request for the adversary's session — i.e. it interposes on presentation-request delivery / VP return for the same CI.

Capability (2) has two faithful instantiations, both already recognized by the spec/companion document:

  • Malicious or compromised split-architecture Wallet backend (§13.4). The provider-operated backend mediates the user's interaction with CI. When the user authorizes a presentation-during-issuance, the backend feeds the device the attacker session's nonce, the device (holding skH in its secure element) signs a genuine KB-JWT, and the backend redeems the resulting Access Token in the attacker session and requests the credential bound to a backend-held key. No key is ever leaked; the backend abuses its architectural position. (The audience binding makes this same-Issuer: the VP is bound to iar:CI, so a different issuer cannot harvest it — the attack is specifically a cross-session confusion at one Issuer, not cross-issuer relay.)
  • Dolev–Yao interposition / concurrent sessions. Any positioning from which the adversary can deliver CI's own (signed) presentation request to the honest holder and relay the holder's VP back to CI in the adversary's session.

In both cases the honest holder performs a presentation it intended to perform; the protocol simply fails to tie the credential subsequently issued to that holder's key. We do not assert how common split-architecture deployments are — only that §13.4 establishes the trust boundary as recognized, which is sufficient.

Composability note. §6.1 of the companion document protects a non-compromised Wallet against compromised Issuers and Verifiers, but does not extend that protection to a compromised peer Wallet / backend on the same footing. This issue is an instance worth correcting under that principle.

Attack: Cross-Session Holder-Binding Confusion (A1)

On the "A1" label. "A1" is the identifier this attack vector carries in our internal threat catalogue. We retain it in this issue only because it is the prefix used throughout the accompanying Tamarin artifacts — the theory is OID4VCI_A1 and the lemmas are named a1_no_impersonation, a1_attack_witness, a1_holder_continuity_*, etc. — so the prose here maps one-to-one onto the proof files. It denotes the cross-session holder-binding confusion described below; it is not a severity rating and not a reference to any external (e.g. OWASP/CWE) catalogue.

The adversary opens its own auth_session with CI, relays CI's Issuer-signed presentation request to an honest victim, consumes the victim's genuine VP in the adversary's session, and then completes issuance with a key proof under the adversary's own key. CI, finding no requirement that the issuance key match the presented holder key, issues a credential carrying the victim's subject claims bound to the adversary's key.

Attack steps
  1. The honest victim Wid_V holds a prerequisite credential issued by an honest Prerequisite Issuer PI, bound to the victim's holder key pkH_pres = pk(skH_pres) (held in the device-side trust domain; never leaked).
  2. The adversary generates its own holder keypair (skH_Adv, pkH_Adv) and starts an IAE session with CI: it sends iae-init and receives CI's require_interaction response containing an Issuer-signed presentation request presReq(nonce_A, aud = iar:CI) for the adversary's auth_session_A.
  3. The adversary causes presReq(nonce_A, aud = iar:CI) to be presented to by the victim (via the §13.4 backend position, or DY interposition). The victim returns a genuine VP: the prerequisite VC plus a KB-JWT signed under skH_pres over (iar:CI, nonce_A).
  4. The adversary submits (auth_session_A, VP) to CI's IAE endpoint. CI verifies the VP: the prerequisite VC verifies under PI's key; the KB-JWT verifies under pkH_pres; nonce_A matches auth_session_A's nonce, so §6.2.1.4 is satisfied. CI mints an Access Token bound to auth_session_A, storing the victim's pkH_pres and subject claims.
  5. The adversary sends a Credential Request carrying the Access Token, a key proof signed under skH_Adv, and holder key pkH_Adv, plus an attacker-chosen response-encryption key.
  6. CI accepts: the key proof is self-consistent (it verifies under pkH_Adv), and nothing requires pkH_Adv = pkH_pres. CI issues a credential carrying the victim's subject claims bound to pkH_Adv, encrypted to the attacker's key.
  7. The adversary decrypts and obtains a credential about the victim's subject, cryptographically bound to a key the adversary controls — and therefore presentable by the adversary as if they were the victim.

What the attacker gains. A credential asserting the victim's identity/attributes, bound to an attacker-held key — a violation of holder-binding requirements I-40 / P-50 and a full impersonation primitive. (A confidentiality-only variant, in which the credential is instead bound to the victim's key but encrypted to the attacker, is also reachable; the holder-key substitution above is the stronger outcome and the one we headline.)

Why the spec does not currently prevent this

The presentation is bound to three things — the opaque auth_session handle, the iar:CI audience, and the OpenID4VP nonce — but none of these relates the presented holder key k_pres to the issuance key-proof key k_proof:

  • §6.2.1.4 binds nonce ↔ auth_session. In the attack, nonce_A genuinely belongs to auth_session_A, so the check passes. Session-fixation prevention does not imply holder-key continuity.
  • Appendix A.2.5 / A.3.5 binds the VP to iar:CI (the Issuer endpoint), not to any issuance key.
  • The key proof (§9) proves possession of some holder key and binds the Issuer identifier and a fresh nonce, but the Issuer never checks that this key is the presenter's key.

So at the term level k_pres and k_proof are independent objects with no relating equation. Standard channel/signature abstractions do not rule the attack out: modelling TLS as a confidential channel does nothing (there is no interception), and signatures are unforgeable (no signature is forged — the victim's KB-JWT is genuine and the adversary signs its own key proof with its own key). It is a binding failure, not an interception or a forgery.

Tamarin Formal Verification

We formalized the IAE presentation-during-issuance flow in Tamarin (1.12.0 / Maude 3.5.1) from a single source with four #ifdef configurations. The model uses standard Dolev–Yao primitives (signing, public-key encryption, hashing); folds the OAuth/PAR + Token Endpoint into one opaque, session-bound Access Token gating the Credential Endpoint; and includes explicit Reveal_* rules for the Issuer key and victim holder key — neither of which the attack uses. The adversary's only key comes from an Adversary_Wallet_Key rule (its own freshly generated key). The Prerequisite Issuer is modelled as honest (it issues prerequisite credentials only to honest holders), so every issuance in the model is genuinely gated on a real honest-holder presentation.

We evaluated four configurations, labelled by their -D flag and mechanism:

Configuration (-D flag) Mechanism at the issuance step Holder-binding confusion (A1) Headline impersonation lemma Honest-flow executability
VULNERABLE (spec as written) none — no link between k_pres and k_proof breaks falsified verified
FIXED_NONCE (nonce-bound key proof) key proof must carry the auth_session nonce still breaks falsified verified
FIXED (holder-key continuity) Issuer requires k_proof = k_pres (issued key ≡ accepted presentation's holder key) closed verified verified
FIXED_TXDATA (presenter commitment) presenter commits h(k_iss) inside the KB-JWT; Issuer checks the key proof's key against the commitment closed (permits key separation) verified verified

Per-configuration lemma verdicts (Tamarin 1.12.0; all four pass well-formedness):

  • All configurations: ltkCI_secrecy, skH_secrecy, holderkey_unique, and exec_honest_iae_flow (executability) — verified.
  • VULNERABLE: a1_attack_witness (concrete, zero-compromise PoC) — verified; a1_holder_continuity_vuln and a1_no_impersonationfalsified.
  • FIXED_NONCE: a1_no_impersonation and a1_holder_continuity_noncefalsified.
  • FIXED: a1_no_impersonation and a1_holder_continuity_fixedverified.
  • FIXED_TXDATA: a1_no_impersonation and a1_holder_authorization_txdataverified.

The headline property, a1_no_impersonation (stated identically across all configs), is: no credential is ever issued bound to an adversary-controlled key while gated on an honest, un-revealed victim's presentation.

Key findings from formal verification
  1. The gap is structural, not a textual ambiguity. Under VULNERABLE the confusion constructs with no creative reasoning — and it does so even without any adversary key: a1_holder_continuity_vuln is falsified by a trace in which the issued credential is bound to a second honest holder's key while the presentation was the victim's, with no compromise anywhere. The portability of the key proof (it binds only (CI, pkH), never the session or the presenter) is the root cause.

  2. a1_attack_witness is a clean, zero-compromise proof-of-concept. Tamarin returns a trace in which the credential is issued bound to the adversary's key, carries the victim's subject, and is read by the adversary (K(cred)), with no Reveal_CI and no Reveal_HolderKey anywhere in the trace — the adversary uses only its own Adversary_Wallet_Key. This confirms the attack is a property of the baseline protocol, not of any key leak.

  3. §6.2.1.4's nonce binding does not help — and the obvious patch (FIXED_NONCE) fails for a non-obvious reason. Binding the auth_session nonce into the issuance key proof and checking equality is a natural first fix. Formal analysis falsifies it: the nonce ships in the clear in the require_interaction response, so any party freshly mints a key proof over its own key carrying that nonce. The underlying argument is general and deductive — a freshness value the Issuer hands out in the clear carries no requester identity, so wrapping it into a proof cannot constrain who chose the key. The binding must come from material the Issuer already knows belongs to the presenter.

  4. FIXED (continuity) and FIXED_TXDATA (commitment) both close the attack, at the right layer. FIXED makes the Issuer enforce k_proof = k_pres: in the impersonation trace this forces pk(skH_Adv) = pk(skH_pres), impossible for distinct fresh keys (a1_no_impersonation closes immediately). FIXED_TXDATA instead has the presenter commit to the intended issuance key h(k_iss) inside the KB-JWT (signed by the presenter's holder key); the proof discharges the cuckoo branch via a KB-JWT-origin argument — the only producer of that commitment is the genuine presenter committing to its own fresh key, and forging it would require the victim's holder key. FIXED_TXDATA thereby closes the attack while still allowing the issued key to differ from the presented key (k_iss ≠ k_pres), preserving per-credential key unlinkability.

Model, configurations, and proof artifacts: https://github.com/congenial-max/OpenID4VCI_v1-1_IAE_cross-session-holder-binding-confusion_FormalSecurityAnalysis/tree/8971275293bc75d3a5c7e441c7f73e4e5835a5b3

Recommended Fix: Bind the Issued Holder Key to the Accepted Presentation

The Credential Issuer must, at the issuance step, cryptographically tie the holder key of the issued credential to the holder that produced the accepted presentation. We verified two interoperable options; either closes the attack, and the choice is a privacy-vs-simplicity tradeoff.

Option A — holder-key continuity (FIXED). The Issuer requires the issuance key proof to verify under, and the credential to be bound to, the same holder key that signed the accepted VP's KB-JWT, and rejects otherwise. Simplest to specify and implement; the cost is that the issued credential is bound to the same key as the presented one.

Option B — presenter-signed key commitment (FIXED_TXDATA), recommended where key separation matters. The presenting holder commits, inside the KB-JWT it already signs, to a thumbprint h(k_iss) of the (possibly fresh) key the new credential should be bound to — naturally carried via the OpenID4VP transaction_data mechanism. The Issuer verifies that the key in the issuance key proof matches that commitment. This closes the attack and permits k_iss ≠ k_pres, so a Wallet can bind each issued credential to a fresh key for unlinkability without weakening the binding.

Why neither nonce-binding nor a key-proof extension suffices
  • Nonce-binding the key proof (FIXED_NONCE) does not work: the nonce is public, so it is re-mintable over an attacker-chosen key (falsified; see Key Finding 3).
  • More generally, no extension of the key proof's own payload can fix this, because the key proof is signed under the issuance key, which the adversary chooses and controls. Any payload an adversary-held key can sign, an adversary-held key can re-sign. The binding must reference the presenter's key (Option A) or a presenter-signed commitment (Option B) — material outside the issuance key's control.
Why this is the right fix
  • It moves the binding to where the threat model places trust. The gap is precisely that the Issuer trusts the presentation (presenter's holder key, in the device-side trust domain) but binds the credential to an unrelated, freely-chosen issuance key. Both options re-anchor the binding on the presenter's holder key, which the §13.4 backend compromise does not reach.
  • It is standards-aligned, not bespoke. Option B reuses OpenID4VP transaction_data and the KB-JWT the holder already produces; both options use only JWS over JSON and add no new primitives or endpoints. It makes the existing companion-document requirements I-40 / P-50 protocol-enforced for this path.
  • It composes cleanly. The Issuer's added check is a single JWK comparison (Option A) or one hash/commitment comparison (Option B); no new token types and no change to the synchronous flow.
Spec changes required
  1. §6 / §6.2.1 (issuance after presentation): Add normative text requiring the Credential Issuer to bind the holder key of the issued credential to the holder that produced the accepted presentation, via either:

    • (Option A) requiring the issuance key proof to verify under the same holder key as the accepted VP's KB-JWT, and rejecting the Credential Request otherwise; or
    • (Option B) requiring the accepted KB-JWT to carry a commitment (e.g. transaction_data containing h(k_iss)) to the intended issuance key, and requiring the Issuer to verify the key proof's key against that commitment.

    On mismatch the Issuer MUST reject with HTTP 400 and an appropriate error (e.g. invalid_proof, or a new holder_binding_mismatch value).

  2. §6.2.1.4 (Preventing Session Fixation Attacks): Add a clarifying note that the nonce ↔ auth_session association prevents session fixation but does not establish holder-key continuity across the presentation→issuance boundary; the binding in (1) is additionally required.

  3. Appendix A.2.5 / A.3.5 (Holder-Binding audience): Note that the iar: audience binds the VP to the Issuer endpoint but not to any issuance key; cross-reference (1).

  4. §13 (Security Considerations): Document this cross-session holder-binding confusion and its §13.4 split-architecture instantiation, and state explicitly that TLS confidentiality and signature unforgeability do not prevent it (it is a binding failure). Note that the binding in (1) closes it without any assumption of key secrecy beyond the presenter's holder key.

  5. §13.1 (Formal Security Analysis): Add a reference to this analysis under the existing list of formal-security-analysis citations.

  6. Companion document mapping: Make holder-binding requirements I-40 / P-50 protocol-enforced for the IAE presentation-during-issuance path via (1).

Limitations of the fix and of the model
  • Option A vs. unlinkability. Continuity (Option A) forces the issued credential's holder key to equal the presented one, which conflicts with fresh-pkH-per-credential privacy patterns. Option B avoids this; deployments with a per-credential-key privacy model should prefer Option B.
  • Modelling abstractions. TLS is abstracted as Dolev–Yao channels; the OAuth/PAR + Token Endpoint is folded into one opaque, session-bound Access Token; DPoP is omitted (it sender-constrains the Access Token to the transport key, not the holder/issuance key, so it gives no uplift against this attack); credentials use carry-and-verify signing; a single derived-credential dataset is modelled.
  • Honest Prerequisite Issuer. We model PI as honest (issuing only to honest holders); a malicious PI that mints prerequisite credentials to attackers is a distinct, stronger compromise not covered here (and not required for this attack).
  • Batch issuance (the proofs array, multiple holder keys per request) is not modelled; the binding in (1) would need to apply per proof. We flag this as future work.
  • Full backend compromise. If the holder/commitment private keys themselves reside in (and are taken from) the compromised backend rather than the device-side trust domain, the binding in (1) reduces to the full-Wallet-compromise case, which no protocol can distinguish from the legitimate holder. The value of the fix derives precisely from the §13.4 boundary, where the holder key is not in the compromised backend.

Suggested Action

  1. Acknowledge the structural cross-stage holder-binding gap in §6 (presented holder key vs. issuance key proof) under the §13.4-acknowledged adversary, and that §6.2.1.4 does not close it.
  2. Adopt a binding at the issuance step — Option A (continuity) or Option B (presenter-signed commitment) — with Option B recommended where per-credential key separation/unlinkability is a goal.
  3. Update §6/§6.2.1, §6.2.1.4, Appendix A.2.5/A.3.5, §13, and §13.1 as described above.
  4. (Optional) Reference the Tamarin model and proofs from §13.1.

We recommend this be processed as part of the v1.1 work, since the IAE is a v1.1 addition. The change adds no new endpoints or token types: the new request members (an iar:-bound key commitment, or the continuity check) and the Issuer-side verification are additive.

We are happy to provide:

  • A pull request against the spec text containing the §6 / §6.2.1.4 / Appendix A / §13 / §13.1 changes.
  • The full Tamarin model and proof artifacts for all four configurations (VULNERABLE, FIXED_NONCE, FIXED, FIXED_TXDATA), with a reproducibility README.
  • A walkthrough with working-group security reviewers if useful.

References

  • OpenID for Verifiable Credential Issuance 1.1, editor's draft openid-4-verifiable-credential-issuance-1_1-01 (Lodderstedt, Yasuda, Looker, Bastian).
  • OpenID for Verifiable Presentations (for transaction_data and the KB-JWT holder-binding mechanism).
  • Fett, D., Lodderstedt, T. — Security and Trust in the OpenID4VC Ecosystem (companion document), holder-binding requirements I-40 and P-50.
  • RFC 7800 — Proof-of-Possession Key Semantics for JWTs (related cnf binding).
  • RFC 7638 — JSON Web Key (JWK) Thumbprint.
  • Basin, D., Cremers, C., Dreier, J., Sasse, R. — The Tamarin Prover for the Symbolic Analysis of Security Protocols.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with §6 and §9 of the OpenID4VCI 1.1 editor's draft, then inspect the Tamarin theory OID4VCI_A1 and its a1_* lemmas. Compare the holder-key continuity and presenter-signed commitment configurations described in the issue. Done means adopting a specified binding that prevents the cross-session attack and preserves the claimed security properties in the formal model.

Written by the indexing model from the issue text.

Assessment

Domain
authentication, security
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.