OpenID4VCI
OpenID4VCI copied to clipboard
Wallet Attestations and Nonces
Currently, implementations can protect the PoP for wallet attestations (https://datatracker.ietf.org/doc/draft-looker-oauth-attestation-based-client-auth) by:
- making it short lived
- making it one time use
- binding it to a protocol artifact like the authorization code (as kind of a nonce)
The latter does not work for Pushed Authorization Requests. The proposal was made, to let the wallet request a nonce from the Issuer that can then be used for binding the wallet attestation. This came up in various discussions including the eIDAS expert group's touch point meeting on OID4VCI and discussions with member states.
The basic idea would be to define another endpoint, e.g. nonce endpoint, the wallet could be use to obtain that nonce.
why not reducing complexity by signing the request (request) with the private key where the public one is attested within the WIA (client_assertion)?
I believe we can satisfy all the requirements without adding anything new and preserving the current implementations
POST /as/par HTTP/1.1
Host: pid-provider.example.org
Content-Type: application/x-www-form-urlencoded
response_type=code
&client_id=$thumprint-of-the-jwk-in-the-cnf-wallet-attestation$
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
&request=eyJhbGciOiJSUzI1NiIsImtpZCI6ImsyYmRjIn0.ew0KIC Jpc3MiOiAiczZCaGRSa3F0MyIsDQogImF1ZCI6ICJodHRwczovL3NlcnZlci5leGFtcGxlLmNvbSIsDQo gInJlc3BvbnNlX3R5cGUiOiAiY29kZSBpZF90b2tlbiIsDQogImNsaWVudF9pZCI6ICJzNkJoZFJrcXQz IiwNCiAicmVkaXJlY3RfdXJpIjogImh0dHBzOi8vY2xpZW50LmV4YW1...
&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-client-attestation
&client_assertion=$WalletInstanceAttestation$
@peppelinux You mean the signature over the request is the proof of possession for the wallet attestation? That would mean to send a signed request object to the PAR endpoint, right? Something like this:
`POST /as/par HTTP/1.1 Host: pid-provider.example.org Content-Type: application/x-www-form-urlencoded
response_type=code &client_id=$thumprint-of-the-jwk-in-the-cnf-wallet-attestation$ &request=eyJhbGciOiJSUzI1NiIsImtpZCI6ImsyYmRjIn0.ew0KIC Jpc3MiOiAiczZCaGRSa3F0MyIsDQogImF1ZCI6ICJodHRwczovL3NlcnZlci5leGFtcGxlLmNvbSIsDQo gInJlc3BvbnNlX3R5cGUiOiAiY29kZSBpZF90b2tlbiIsDQogImNsaWVudF9pZCI6ICJzNkJoZFJrcXQz IiwNCiAicmVkaXJlY3RfdXJpIjogImh0dHBzOi8vY2xpZW50LmV4YW1...
That would reduce the attack surface as an attacker would need to replay the request. It does not work (out of the box) for token requests as there is no equivalent of a signed request there.
The token request can be bound to the authorization code. Regarding the client authentication, both private_key_jwt or draft-looker-oauth-attestation-based-client-auth might be used. @tlodderstedt do you see any issues on this?
That would reduce the attack surface as an attacker would need to replay the request. It does not work (out of the box) for token requests as there is no equivalent of a signed request there.
I assume that a jti within the JWT (signed) request avoids the possibility that issA+jti may collide with issB+jti or be used in a replay attack.
following this assumption, may we say that nonce is then not required anymore?
@peppelinux As I wrote in my initial comment, one time use (jti) is a possible option for replay prevention. However, one time use limits scalability and nonces offer more implementation flexibility and are considered best practice.
@fmarino-ipzs As I wrote in my initial comment, an authorization code cannot be used as nonce in a pushed authorization request. So we either stick to one time use or short expiration or we add support for nonces.
adding a new endpoint for satisfying the same requirement satisfied by jti, according to RFC, might sound overkill
My comment was related to your last sentence regarding the token request where there is no equivalent to the signed request. For the PAR, the jti approach provides the same benefit in term of replay attack without introducing a new endpoint.
@peppelinux Can you please describe why you assess use of a nonce as "overkill"?
As I already stated, jti requires one time use on the issuer side, which means shared, current state across cluster nodes. This is know to cause issues with scalability of systems. Besides this, nonces provided by the receiving party are best security practice.
@fmarino-ipzs
My comment was related to your last sentence regarding the token request where there is no equivalent to the signed request.
@peppelinux suggested to use a request signature as proof of possesion instead of the proof of possession in the wallet attestation pop. That would work for PAR but not for the token request, as there is no signed token request. So the question is whether the proof of possession is implemented as defined in draft-looker-oauth-attestation-based-client-auth (as JWT appended to the assertion) or as another private_key_jwt parameter. Not sure of the benefits of the later.
For the PAR, the jti approach provides the same benefit in term of replay attack without introducing a new endpoint.
I disagree. Please read my comment above.
From a security point of view (mitigation against replay attacks), I see no benefit in using a nonce over jti. On the contrary, using a nonce requires a new endpoint that does not come for free. But maybe I am missing something. Regarding the scalability issue, which has nothing to do with security or even PoP, could you give us some references?
My intent is to reuse what we already have without require any additional endpoints made for the issuance like the nonce endpoint might represent.
I look forward to the satisfaction of the security requirements with their implementation considerations, putting aside every personal taste, as an implementer, and above all the ideas to which we try to become attached.
Here I share the flow that a group of developers and analysts have implemented and tested on stage, which uses both PAR and token endpoint with DPoP. We continue working on that, then our approach is still open. Since this beautiful working group is enriched by mutual experiences it will produce for sure a solution that objectively move, replace or improves, in our minds, the flow that is shared below:
https://italia.github.io/eudi-wallet-it-docs/versione-corrente/en/pid-eaa-issuance.html#token-endpoint
I believe that for the value of decision making, it is important to highlight why some choices are preferable to others, then we will implement them exactly like Buddhist monks destroy their sand mandalas for creating new ones (without getting attached!).
In the previous flow the private_key_jwt is involved, the signed JWT contains aud, exp and jti. Then sub, aud and jti prevents the replay.
Probably you're trying to tell us that using the nonce endpoint it would give the benefit to reduce the cross checks above multiple JWS, HTTP Headers and HTTP parameters? If yes let's try to have a clear schema about the number of checks and the attested level of security given by these, then we'll definitively feel comfortable to adopting a solution instead of another
@peppelinux Can you please describe why you assess use of a nonce as "overkill"?
As I already stated,
jtirequires one time use on the issuer side, which means shared, current state across cluster nodes. This is know to cause issues with scalability of systems. Besides this, nonces provided by the receiving party are best security practice.
I think that this really needs to be confirmed with objective elements (since this chat of nonce vs. jti is long from more than a year, you know :sweat_smile:)
with the cap of implementer I assume to have a cluster of services, be they whatever entity that deals with JWTs and serves the token endpoint.
I require that a centralized or distributed storage is then used, where any transactions life time happen: creation, enrichment/update and pruning.
If I have a nonce endpoint, when I issue a nonce this would be stored in the storage shared between the nodes. This would be the same for any transaction or couple of iss-jwt (unique-together) -> stored in the storage, be it a nonce or a couple of issuer-jti.
There are other cases where the nonce is encrypted with a key that is know within the cluster, this doesn't require a storage, because any receiving party would validate by decrypting it (as some strategy does with the web cookies, for instance).
anyway, due to the important number of mixed endpoints we used to have in the OAuth2 framework, we know that anyway we need a layer of persistence where the transaction is tracked and enriched/updated. Then I don't see these scalability improvements or concerns of a solution vs another.
@tlodderstedt or @jogu, since you worked for decades in IETF and with success with all-about-nonce, could you please enlights why the nonce is a game changer in this context and the jti must then not be used?
The key differences with jti vs nonce:
nonce is generated by the authorization server and it doesn't actually necessarily need to store anything (sometimes it generates a hash or jwt in some fashion that means it can verify a value without needing to have stored it, or values can be communicated in advance to all servers).
jti is generated by the client. Many authorization servers don't check jti uniqueness (it's not mandatory to implement in most specs). Checking jti properly definitely requires a store that is shared across all regions the token can be presented in. The size of the store varies depends on the validity time of the token, which is also set by the client (iat/exp) in many cases, and the size is potentially unbounded depending on how fast clients might present valid tokens. Because the jti is client selected, if a client is compromised in some way the attacker can generate valid jwts for use (potentially days) later (nonces prevent that).
In the NL wallet, we would prefer to use nonces over jti primarily because we think there are security advantages over jwt:
- A JWT with a jti intended for issuer A might be replayed to issuer B. Unless one takes mitigating measures against that, but then that shows that securely implementing a jti-based approach is more difficult.
- The failure mode is different. As @tlodderstedt mentioned, using jwt requires remembering seen jti's across nodes. If there is an issue in synchronizing those seen jti's between nodes then that opens the possibility of replay attacks again. Nonces may also be implemented using shared state, but if that fails then issuance just breaks down without opening replay attack vectors.
Since a nonce is a value that didn't exist before the issuer generated it, a nonce-based approach just gives more guarantee that the pop that signs the nonce is truly "fresh".
We plan to use nonces or similar mechanisms everywhere else whenever something is disclosed or issued, as it is indeed considered best practice. Not using it here would be the one exception to that. Since the wallet attestation (at least in our setup) serves as a sort of anchor on which the security of a large part of the rest of the setup depends, we feel that this is not a place where we can or should ignore security advantages such as the ones above.
There are also advantages in the implementation, as previously mentioned. It can be implemented stateless if the issuer would e.g. sign the nonce into a JWT as @jogu suggests. And even if you do implement it using state, then it will be less state, that is easier to deal with.
A JWT with a jti intended for issuer A might be replayed to issuer B
Unless I have forgotten a situation, I don't think that is possible in the VCI spec as all JWTs should have aud values. (I think there's one situation in VP where it might be an issue [using signed request objects by value to shared custom url schemes where you're not sure which wallet will open] but I'm not sure nonces are usable in that situation either.)
@jogu I agree on the different nature between jti and nonce, their directions and audiences, even if a jti is issued by any kind of jwt issuer, be it AS, RP, Client, OP and so on, but I fully got your point and I agree that handling a single value (nonce, even if encrypted with many bytes within it) is less that handling an entire JWT in term of computational and storage costs.
Regarding the issue that some or many implementations doesn't properly handle the "uniqueness" of the jti would be better taken out, since we would say the same for every implementation that, for instance, supports X.509 without verifying properly a certificate chain; these consideration are too much relative to the technical debits of the implementations, even if, the value in your and also @tlodderstedt words may be found in two important aspects: 1) simplicity: a single value, issued by the actor that validates the flow where it is the main auditor; 2) implementation robustness: avoids the implementation weakness of mixing many properties taken from multiple sources, while a single value, when mandatory in the implementation (where jti is not) makes the difference in a completely arbitrary way.
@sietseringers thank you for join in this discussion, here my though:
JWT might mean many things depending on its payload, to avoid a replay the recipients are the traditional aud, iss, sub and jti. I agree with you that mixing multiple properties and then checking them may open the door to bug and implementation weakness. @jogu good catch for the aud, even if it is not the case for the private_key_jwt.
regarding the shared state I'm not convinced that nonce is different in this context, since it should be shared in that storage anyway. It is thin, it may be verified with encryption with internal logic of validation (then it may be reversible and not opaque to the issuer).
Well, now that I've forced you to share a walk along this road, and had at least for me a brilliant chat on pros and cons, the still opened points on this thread of jti vs nonce I think are enough to move to the actionable stuffs within this thread.
My scope is having a robust and secure interop with other parties, then it seems that we really want to go for the nonce endpoint. @tlodderstedt and @fmarino-ipzs if you agree I believe that this chat that started more than a year ago costs more than the implementation of a simple endpoint for the issuance of the nonce :sweat_smile: implementing it would costs less than 8 hours, not a problem, then ...
I would like to focus more on the scopes and then on the big picture of this issue. Is this intended to say that the WIA must be ephemeral and one-time-use?
If yes, I would say: it depends, maybe not. I assume that a WIA must be reused until its expiration and there might be cases where a WIA, bringin the public key attestation within it, should be reused along the same pseudonym (siopv2 id_token) that was issued along with it. Before raising too much ideas on that, I would say that probably it would time to say that it may be better to have multiple types of WIA for different scopes.
- operational state WIA with nonce, for the issuance
- reusable WIA for the pseudonyms trust
- reusable WIA for the presentation (consider to be in offline flow, where a wallet cannot reach the provider nonce endpoint but MUST present a digital credential to a verifier ... more than a single time and in short period in addition!)
before deciding that WIA should be one-time-use, with a nonce, I would analyze with your help the impacts on the entire ecosystem for that. Then let's develop the nonce endpoint in few hours!
Hi.
If my understanding of the discussion is correct, there are essentially two strategies (which are, IMO, compatible)
a) Passive sharing of a Wallet Attestation (jti-protection) b) Active sharing of a Wallet Attestation (nonce protection)
a) In this case:
- Authorisation or token requests (in pre-authZ flow) SHOULD be signed, and the wallet attestation is part of the request. Yes, the token request should be signed in this case.
- AS needs to store the request info and with it the jti (I guests) to prevent reply attacks. To not overload the server the request lifetimes should be short so that one can set the TTL and not care about the deletion of the old entries
b) In this case, the AuthZ./Token request is followed by a "VP or ID Token" (OID4VP or SIOP) request to the wallet asking for a Wallet attestation VC. Maybe there are other "request" protocols I don't know of
In EBSI, we implemented b). To me, a) is straightforward if JAdES is used as the wallet attestation can be transported as signer attributes.
I believe we'll see both cases.
Just to be clear on one point:
Regarding the issue that some or many implementations doesn't properly handle the "uniqueness" of the jti would be better taken out, since we would say the same for every implementation that, for instance, supports X.509 without verifying properly a certificate chain
These are slightly different cases:
In the case of TLS it is expected (and stated in many specs) that you must verify the certificate chain.
In the case of jti there is no required behaviour in any spec I can immediately recall that anyone must reject already seen jtis.
regarding the shared state I'm not convinced that nonce is different in this context, since it should be shared in that storage anyway. It is thin, it may be verified with encryption with internal logic of validation (then it may be reversible and not opaque to the issuer).
Generally nonces are treated as shared state like the jti, indeed. In that sense I agree they are the same, but there are still differences. I expect that the nonce-based approach will result in less state, since generally you only need to remember the nonce until the client sends its response (a signature over the nonce), which will be (almost) immediately after fetching the nonce. By contrast, a jti will need to be remembered until the end of the expiration window, which is much longer. So there will be more state that needs to be remembed, also increasing the difficulty of obtaining performance and scalability as mentioned by @tlodderstedt.
Additionally, I think the failure mode that I mentioned can really be a concern, in that it opens its own attack vector for a sufficiently powerful adversary:
- Break the state synchronization mechanism between two clusters of issuer nodes (e.g. by breaking connectivity);
- Somehow obtain a jwt with a jti in it, that may or may not have been sent already to the issuer;
- Use it against a node of the issuer that has not already seen it.
That would be difficult so it might be considered hypothetical, but the nonce-based approach has no such potential attack vector at all.
Regarding the issue that some or many implementations doesn't properly handle the "uniqueness" of the jti would be better taken out, since we would say the same for every implementation that, for instance, supports X.509 without verifying properly a certificate chain
Well, there are sufficient examples of vulnerabilities originating in improperly validating an X.509 chain. I believe a standard should strive to make it as easy as possible to write implementations that are secure, and I believe the nonce-based approach has an advantage there.
If the nonce is not computed from the information, it must be shared with all AS in the cluster. If the nonce is computed from the information, it is susceptible to similar attacks as jti. (If someone gets a jwt and jti it can get the nonce.)
However (as mentioned above)
- nonce validity will be much shorter than of the jti (request/response vs. request lifetime (if signed, can be defined)), only if consumed. Someone could just call the nonce endpoint and not send a response.
Questions:
- Where is holder/wallet authentication in the flow? (You cannot continue the flow until authenticated.) Even if the attacker replies AuthZ request, it will fail to authenticate.
- What's the rationale behind binding a Wallet Attestation to a session token? (Wallet attestation is already bound to a private/public key)
What's not mentioned in the flow is the credential offer. Could it contain a nonce?
If the Wallet attestation should be bound to a session (IMO, that's not necessary), one could go with the flow:
- issuer creates nonce in the credential offer; nonce_co
- wallet takes nonce_co and creates another nonce: nonce_w; it sends the hash(hash(nonce_co)), hash(nonce_w) to the attestation issuer
- attestation issuer includes hash(hash(hash(nonce_co)), hash(nonce_w)) in the attestation
- wallet presents to the AS 3 items: H(H(H(nonce_co)), H(nonce_w)), H(nonce_co), nonce_w
this way, nonces or their hashes are not transported more than 1x. The attacker must get to the wallet to get all the info. If that happens, it's game over anyway.
I don't see any issue with the flow presented by @peppelinux since the holder needs to authenticate anyway.
Is this question about reducing the burden of the AS?
@alenhorvat the point that you have raised Is very interesting, by adding a new unprotected endpoint then we should protect It in some way against the abuse.
both nonce and JWT.jti are then stored with the sole difference that a signed JWT.jti protects the write with an authentication, while nonce not, then the implementers must use some way to protect the nonce endpoint against the abuses
If the nonce is not computed from the information, it must be shared with all AS in the cluster.
- Nonces can be created and managed stateless (e.g., by issuing the hmac over the current time as the nonce).
- If there is a load balancer connecting the same client to the same node for a while, "global" nonces don't need to exist, they can be node-local.
Both is not true for jti.
Also, there is no need for a new nonce endpoint to issue nonces; DPoP issues nonces in headers in error messages.
I wonder if the whole approach should not be structured more like DPoP, defining headers that can be used in requests of many types. Or even a DPoP extension, reusing the DPoP nonces and/or the DPoP PoP.
If there is a load balancer connecting the same client to the same node for a while, "global" nonces don't need to exist, they can be node-local.
it depends by the balancing type, the way you indicate is a sticky balancing that binds clients to endpoints (session persistence). This solution is awesome but works just for balancing and not for fault tolerance of the nodes, for this a shared storage is then needed.
Both is not true for jti.
indeed, a signed JWT.jti is authenticated.
I wonder if the whole approach should not be structured more like DPoP
I fully agree with you on this, here we have used DPoP in both the issuance and the presentation phase (for providing the WIA)
https://italia.github.io/eudi-wallet-it-docs/versione-corrente/en/pid-eaa-issuance.html#detailed-flow https://italia.github.io/eudi-wallet-it-docs/versione-corrente/en/relying-party-solution.html#remote-protocol-flow
To prevent a reply attack, the server needs to keep track of "consumed tickets", whether it is jti, nonce, or DPOP JWT. Difference between client-generated "nonce" (jti, dpop jwt) and server-generated nonce: how long the information needs to be stored.
client-generated nonce: for the lifetime of the request server-generated nonce: until the nonce is consumed or lifetime of the nonce, at worst
from a security point of view, I don't see a big difference. From a capability point of view, it is similar. the new nonce endpoint would need some protection, and a server-generated nonce approach would be less memory intensive since most nonces are usually consumed quickly and can be removed (whether LB+sticky is used or distributed DB, ... it's a design decision)
The storage costs depends also to the retention policy for the signed requests and responses
Where identities are involved, with personal data, the retention duration Is established by gdpr and member states specific regulation
Then the persistence of both jti and nonce (this latter contained in the signed requests) would be the same
I'll second the point that nonces can be self-authenticating and self-contained and therefore need not be stored. A common way to achieve this is for the nonce to contain content encrypted to the server that creates it.
Based on the discussion in the WG session today, I suggest the following design: a nonce is provided on demand with an error message from the PAR or token endpoint (as in DPoP). This way the wallet can send the wallet attestation (with a jti) and the issuer can decide whether it requires a nonce or not. And we don't need a separate endpoint.
What do people think?
It seems a good achievement to be further analyzed, below some notes in the wild.
We now for sure have realized the requirement of providing the Wallet Instance Attestation during both the issuance and the presentation phase, even if the presentation is out of scope here I would put on the table that we're also working on this issue: https://github.com/openid/OpenID4VP/issues/45#issuecomment-1711954514
following the elements taken in my understanding we're trying to put in place a discovery process, which purpose is to obtain the wallet instance attestation "before-any" other operations made by its audience (CI or RP).
If I'm on the right page, the discovery process ends with the acquisition of the wallet instance attestation, the verification of the PoP and the evaluation of the capabilities of the wallet instance.
We have this requirement both in the issuance and in the presentation phases.
Might we think to create a specialized endpoint to achieve this feature of wallet instance discovery, to be then the same implementation in both the issuance and the presentation phase, to be then reused as it is, like a kind of wallet instance registration endpoint? (even if that cannot be compared with a registration phase, but just a discovery where WIA is submitted to a trusted audience)
So after reading this long issue, I want to share my views:
- I agree that nonce is preferred over jti variation, which matches my initial drafts that used the pre-auth code/code and I share the argumentation that both variations need to be held in state by the issuer but nonce is issuer chosen and requires less effort and seems more secure
- however I would not automatically exclude the jti variation from the oauth-client-attestation draft, instead I propose to add both variations to the draft and formulate the tradeoff in the security and implementation considerations section and even say that nonces can be shared implicitly by other means oob
- I agree that add the nonce request as dpop-style header request and this should as well be added to oauth-client-attestation
- based off this, the OpenID4VCI spec should define how to use client attestation
For Authcode-Flow , especially PAR, the proposed nonce request is probably good. For Pre-Authorizedcode-Flow, we have the option to put the nonce into the credential offer or reuse the preauth code, that would then be slightly different to the Authcode Flow, but safe us one roundtrip. I'm unsure whether OpenID4VCI should strip off the option of jti, but I think that especially HAIP should do that
One thought just popped up that I would like to share with you. The proposal to use a nonce provided as part of an error response assume the wallet would produce a proof of possession without nonce, send a request, if required obtains a nonce from an error response, creates a new proof of possession, repeats the call. I think the underlying assumption is creating another signature for the proof of possession is cheap. What if that is not the case, for example if the respective key is managed in a remote HSM? In this case, the proposal causes another network roundtrip to the remote HSM on top. Would that be a problem? @sietseringers please have a look.