VC Issuance is vulnerable to Unknown Key Share attacks

Issue #1777 resolved
Richard Barnes created an issue

A credential is a statement that certain identity attributes correspond to the holder of the private key of a key pair. (Where the key pair is indicated by the credentialSubject URI.) If the subject of the attributes from the issuer’s perspective is not the same as the holder of the private key of the key pair, then the credential is false and should not be issued.

There are two types of false credential an attacker might obtain:

  1. A credential with the attacker’s key pair and the victim’s attributes
  2. A credential with the victim’s key pair and the attacker’s attributes

The former type is the usual case for maliciously issued credentials, e.g., the fraudulent X.509 certificates issued in the 2011 attack on DigiNotar. But the latter type can also be damaging, leading to attacks known as Unknown Key Share or Identity Misbinding attacks. Failure to prevent the issuance of such credentials leads to attacks on WebRTC and DANE. The ACME protocol for certificate issuance requires that the client requesting a certificate sign over the identifiers to be put in the certificate (“The CSR MUST indicate the exact same set of requested identifiers as the initial newOrder request“) precisely to prevent these attacks.

The current VC issuance specification is vulnerable to unknown key share attacks, i.e., to issuing false credentials of the second type above. The Proof of Possession JWT (the only PoP defined in the specification) does not have any binding to the OAuth client (the party whose information will go in the credential), so if an attacker can obtain a PoP JWT with a nonce that is valid for the Issuer, then the attacker can request a credential using that PoP JWT. The resulting credential will falsely associate the attacker’s attributes with the victim’s key pair.

Note that while this is in a sense a replay issue, it is not addressed by single-use nonces. From the perspective of the victim Issuer, the nonce in the PoP JWT may still be fresh when the attacker uses it (depending on how the attacker obtained the PoP JWT). Anti-replay defenses intended to ensure each signature is used only once do not address this attack.

Instead, what is needed is for the signature on the PoP JWT to bind it to the OAuth client making the credential request. This effectively creates a two-way consent – The OAuth client agrees that it is the same as the key pair holder by submitting the credential request containing the PoP JWT, and the key pair holder agrees that it is the same as the OAuth client by signing some identifier for the OAuth client.

A simple way to implement this solution would be to include a hash of the OAuth client’s access token in the PoP JWT, as with the “ath” claim in a DPoP JWT. The access token hash is a unique identifier for specific OAuth client session, and thus provides the desired binding. I have implemented this solution in https://bitbucket.org/openid/connect/pull-requests/360.

Comments (13)

  1. Jeremie Miller Account Deactivated

    Since the PoP is required to include the c_nonce provided by the Issuer, this attack vector could also be addressed by requiring the Issuer validate that it is the same access token from the nonce generation to the PoP validation.

    That said, I much prefer making this easier to implement by including the access token hash in the PoP.

  2. David W Chadwick

    This attack assumes the attacker gets hold of the victims PoP JWT. Therefore cannot we assume that the attacker can just as easily get hold of the victim’s access token? If so then attacks of type 1 are just as probable as those of type 2 and similarly need protecting against

  3. gffletch

    If the OAuth client supports client authentication, wouldn’t it be simpler to bind the PoP JWT to the OAuth client? If the “client” is a Wallet, there are mechanisms like Dynamic Client Registration that provide the client with an instance unique client_id and associated authentication method (pki, client_secret, …). The client then just needs to use a client authentication mechanism with the PoP JWT in order to protect against this attack. This seems more stable than binding to an access token which tend to be short lived. It also provided additional assurances for the Issue regarding the context of the request. Did I miss something?

  4. Torsten Lodderstedt

    The proposal is to bind a PoP to a certain access token.

    I think implementers can achieve this by binding the nonce to the access token it was issued for and check that binding when processing the credential request. This can be done opaque to the client so I don’t think this extension is needed.

  5. Kristina Yasuda
    • changed status to open

    discussed in the Atlantic Connect call. It was agreed that the diagram would be helpful to understand the attacker model.

    So far The group's direction has been that it's not a new mechanism that is needed to address this, but to clarify why Attacker cannot steal PoP JWT, because of TLS, mutual authentication, etc.

  6. John Bradley

    To David’s question, the attacker could get the access token, but the attack is to switch that access token with the attackers. If the PoP JWT is bound to the legitimate access token then the resulting claims will be correctly bound.

    The attacker will have them but won’t be able to use the VC as they don’t have the proof key.

    Yes the two tokens need to be bound and checked by the RS.

    Signing over a hash of the AT is probably the simplest for the RS and requires less RS state.

    On the other hand using c_hash requires no work on the client but more work on the RS.

    If we think the RS can implement that check securely then we should opt for reducing the complexity of the client/holder.

  7. David W Chadwick

    One point we might have missed is that W3C VCs do not need to have a proof key (typically a DID). These are so-called bearer VCs. An example might be a ticket to a specific seat in a concert. So the proof JWT is optional in the Credential Request.

  8. Torsten Lodderstedt

    The binding between proof and access token can be established by the Authorization Server/ the Credential Issuer, respectively, by binding the c_nonce to the access token in which context it is created. I think that addresses that solves this issue.

  9. Richard Barnes reporter

    Sorry for letting this sit for a while. @Torsten Lodderstedt , you are correct that having the AS/Issuer bind the c_nonce to the access token on the back end is sufficient. But that’s not required either! So it seems like either we need an explicit ath / at_hash-like binding, or we need to require the issuer to maintain the implicit binding. My usual preference is for explicit vs. implicit, but in this case, the issuer is going to need to maintain state for each nonce (for expiry), so adding the access token to that state doesn’t seem too onerous. And of course you can also do the usual state-offloading trick of making the nonce something like Encrypt(k_ap; expiry || at_hash).

  10. Richard Barnes reporter

    To be clear though, I think a change is still needed. To add ath or to add a requirement on c_nonce, but either way we need a new requirement.

  11. Kristina Yasuda

    The following text is being added in PR #535. would this be good enough? On SIOP call, there seemed to be the agreement that it would be.

    nonce parameter is the primary countermeasure against key proof replay. To further narrow down the attack vector, the Credential Issuer SHOULD bind a unique nonce parameter to the respective Access Token.

  12. Log in to comment