Integrity of the JWK used for Credential Response encryption
Currently, there is nothing that ensures the integrity of the JWK used for Credential Response encryption. When not using credential request encryption, a party in the middle that can mutate the request can easily replace the jwk without the Client or the Issuer being aware. Credential Request encryption helps, but in principle encryption should not be used for integrity.
Conceptually, there are two solutions here:
-
provide a proof over the key (a key_attestation or other proof mechanism as used for other credential keys could work.)
-
provide an optional signature over the request using an already known key (dpop key would be an example)
I think option 2 is a bit more generalised here given the JWK used for response encryption isn't the only aspect of the request that benefits from integrity guarantees. In effect it would mean adding support for signed credential requests, which depending on the shape that #505 takes it could be relatively easy to achieve as we could leverage JWT's to provided either (or both) signed and encrypted requests. .
Beyond the credential response encryption key the public key supplied to issue the credential could also be substituted if one were using the JWT proof type and hence would benefit from being able to be integrity protected in the credential request in certain situations.
WG discussion:
- in most cases, TLS is what guarantees integrity of the JWK used for Credential Response encryption
- option 1: address this in 1.1. but if we do that we want to remove all credential request & response encryptions in 1.0
- option 2: fix it in 1.0. (seems to be preferred also because this is an ISO requirement)
- in VCI, define how to sign the credential request and in HAIP mandate it to be DPoP key (does DPoP key being HW bound matter? key integrity and strength of encryption are related)
- bind DPoP or wallet attestation to the encryption key attestation here
- another option might be mandating encrypting credential request when credential response encryption.
I think my worry about the "wallet attestation" and "dpop" type ideas is that we're just moving the trust around, without being clear what we do and don't trust in the first place.
So for example if we say "a wallet attestation is sufficient for the encryption key", I believe that we're saying (based on wallet attestation as per https://openid.github.io/OpenID4VCI/openid-4-verifiable-credential-issuance-wg-draft.html#appendix-E ) is that we trust the wallet backend to provide a correct attestation for the key, but we don't trust it to not alter the request from the wallet.
Or put another way, the wallet backend would be free to generate whatever attestation it wanted for the key it wants to be used for the encryption, and hence it could still man-in-the-middle the request without the wallet frontend being aware of it.
Or put yet another way, this only adds value when the attester is an entity other than the wallet backend?
We had a bit more discussion on this after the call and I think the cleanest solution would be to include the encryption key in the proof types (jwt proof / key attestation). That is probably the best point to make sure decryption happens at the point you want it to: the same point where you generate PoPs for the credential request.
--> We could add an optional parameter to all proof types to carry the encryption key. If you have more than one (jwt proof), then only one (the first) carries the encryption key.
We had a bit more discussion on this after the call and I think the cleanest solution would be to include the encryption key in the proof types (jwt proof / key attestation). That is probably the best point to make sure decryption happens at the point you want it to: the same point where you generate PoPs for the credential request.
--> We could add an optional parameter to all proof types to carry the encryption key. If you have more than one (jwt proof), then only one (the first) carries the encryption key.
I think this is reasonable, though if we are going to have specialized rules around 'only include it in the first one', I'd rather it was a stand alone field to make processing more obvious. But that is mostly bike-shedding.
WG discussion:
- seems to be agreement with this approach: adding an optional parameter to all proof types to carry the encryption key. If you have more than one (jwt proof), then only one (the first) carries the encryption key.
- requires good security considerations? and be very clear on limitations.
- using key attestation does not guarantee that the key was provided by the front end and was not changed by the backend.
- encryption terminates where the key is created. if backend is not trusted for the encryption key but not for the rest.
seems to be agreement with this approach: adding an optional parameter to all proof types to carry the encryption key. If you have more than one (jwt proof), then only one (the first) carries the encryption key.
I'm not convinced of this approach as when using the JWT proof type one can simply substitute the credential/device key in the request and create a new JWT with a new encryption key. I think if we want better assurances around integrity of the credential request in general we should just consider adding a signed credential request where the key used to sign it could be the DPOP key OR known and trusted by the credential issuer for the wallet.
copying below from https://github.com/openid/OpenID4VCI/pull/540#issuecomment-2967491554
WG discussion: direction after the discussion:
- direction seems to be that integrity protection of the credential response encryption key cannot be achieved in a meaningful way as long as trust relationship between issuer and the wallet frontend is established through the wallet backend. at least without relying on OS specific mechanisms.
- to protect the credential response encryption key from non-backend attackers, credential request encryption is sufficient, if credential request encryption is mandatory with credential response encryption?
rest of the discussion:
- why mobile OS provided key can't authenticate the frontend to the issuer? it's not the key attestation that proves frontend's integrity
- what requirements we have regarding the key that response is encrypted to?
- that key needs to be tied to the wallet that controls the access token. can't be an arbitrary key. all key/wallet attestations sent in the credential request are generated by the wallet backend.
- we are facilitating architecture that involves wallet backend - how do existing implementations cope with the wallet backend being in the middle?
- these implementations use android OS key attestation directly. works because wallet backend is a different entity from the android OS PKI?
- what authenticates the app is wallet attestation, not key attestation
- Lee had a suggestion to sign over the encryption key in the credential response. but that, malicious backend can spoof.
Given time to think:
As it stands, if we mandate request encryption if you want to use response encryption (and require that the client retrieves the encrypted keys in a trusted manner e.g. directly from the issuer or by verifying the signed client_metadata) then there is no gain from integrity protection here.
There are some potential uses for signing then encrypting (such as including the issuer public key that the client is encrypting to, so the issuer can detect if the client has been mitm, rather than simply relying on correct client behavior), however retrieval of keys via TLS is straight forward and hosting by the issuer is required, so probably not worth the complexity.
Given time to think:
As it stands, if we mandate request encryption if you want to use response encryption (and require that the client retrieves the encrypted keys in a trusted manner e.g. directly from the issuer or by verifying the signed client_metadata) then there is no gain from integrity protection here.
I'm not 100% convinced this is true:
I think it would be true if (for example) the DPoP proof were also encrypted, and we had some assurance that the DPoP key was genuinely in a HSM in the intended frontend, but I don't think either of those things are true, and if those things are not true an attacker that can access/replace the request contents can replace the encrypted payload (and hence the response encryption key) with an encrypted payload the attacker creates.
As per WG agreement, for 1.0 we're proceeding with what is in PR #505. PRs #522 and #540 have been closed (see the comments on those PRs for details of the WG discussion).
Hence moving this one out of the 1.0 milestone.