OpenID4VCI icon indicating copy to clipboard operation
OpenID4VCI copied to clipboard

Protect the nonce endpoint

Open andprian opened this issue 10 months ago • 7 comments

Having this endpoint unprotected is not a good idea:

  • If the Issuer returns the same nonce every time this makes the nonce publicly available to anyone, which makes it completely useless. Moreover, clause 7.2 states that "This value MUST be unpredictable." so I am not sure if it is therefore required to have a new nonce with every call.
  • If the Issuer returns a different nonce with every call this makes it an attack vector because the nonce database would grow continuously and the service could be vulnerable to a DoS. This would also make it difficult to search for a nonce when receiving it in the credential request.

This endpoint should be protected with the access token that the wallet just obtained, just like the credential endpoint. Thus, the nonce would be also linked to a specific wallet which would make it simple for the Issuer to match the nonce when receiving it in the subsequent credential request.

Also, it would make more sense for this endpoint to implement a GET method instead of a POST.

andprian avatar Feb 17 '25 13:02 andprian

There was some discussion on this in the prior issue/PR: #404

c2bo avatar Feb 17 '25 13:02 c2bo

Also some discussion in #381 - in particular this part https://github.com/openid/OpenID4VCI/pull/381#discussion_r1777355851 about GET vs POST and https://github.com/openid/OpenID4VCI/pull/381#issuecomment-2361382383 on protection of the endpoint.

bc-pi avatar Feb 17 '25 13:02 bc-pi

My 2 cents. I checked the comments in #381 and the current OID4VCI draft. If the only way to use a nonce endpoint—where a fresh c_nonce value can be obtained—is for proof of possession of key material in a subsequent request to the Credential Endpoint, and this call is always made after the Token request, then why don’t we secure the nonce endpoint? Are there other use cases where we need a nonce before acquiring the Token?

oriolcanades avatar Feb 19 '25 16:02 oriolcanades

@oriolcanades I do not see the need to ask for a nonce if you don't have a previously obtained access token.

andprian avatar Feb 26 '25 08:02 andprian

I also agree that the nonce endpoint must be protected. What was this WG (https://github.com/openid/OpenID4VCI/pull/381#issuecomment-2361382383) discussion about?

Fethbita avatar Mar 05 '25 12:03 Fethbita

WG discussion:

  • nonce endpoint does not mean an exponentially growing nonce data bases (mechanisms can be self-contained nonces or fully fledged database or etc.)
  • nonce is used only for the freshness according to the requirement to the servers (which can be very strict) as opposed to being unique to a session. better name would have been "challenge" (probably ship has sailed.. )
  • can't protect nonce endpoint with a sender-constrained access token with DPoP since that would require a nonce
  • for the above reasons, rough consensus is still that there is no need to protect this endpoint closing in a week unless objections

Sakurann avatar May 05 '25 13:05 Sakurann

I am not sure I understand the reasons described.

nonce endpoint does not mean an exponentially growing nonce data bases (mechanisms can be self-contained nonces or fully fledged database or etc.)

How would your nonce database keep a reasonable size with self-contained nonces?

nonce is used only for the freshness according to the requirement to the servers (which can be very strict) as opposed to being unique to a session. better name would have been "challenge" (probably ship has sailed.. )

Do you mean the purpose of the nonce is not to prevent reply attacks?

can't protect nonce endpoint with a sender-constrained access token with DPoP since that would require a nonce

Sender-constrained access tokens are not mandatory in all cases, are they? Besides, the nonce is marked as optional in a DPoP in RFC 9449

Overall, do you agree we should return a different nonce for each call to the endpoint?

andprian avatar May 06 '25 13:05 andprian

I am not sure I understand the reasons described.

nonce endpoint does not mean an exponentially growing nonce data bases (mechanisms can be self-contained nonces or fully fledged database or etc.)

How would your nonce database keep a reasonable size with self-contained nonces?

Why would you need a nonce database if you have self-contained nonces? The point of self-contained nonces is that you don't need to record them in a database :)

nonce is used only for the freshness according to the requirement to the servers (which can be very strict) as opposed to being unique to a session. better name would have been "challenge" (probably ship has sailed.. )

Do you mean the purpose of the nonce is not to prevent reply attacks?

This depends on your definition of a replay attack. The nonce at least limits the time window for replay attacks. The requirement for freshness means that, e.g., an attacker cannot pre-generate key proofs.

can't protect nonce endpoint with a sender-constrained access token with DPoP since that would require a nonce

Sender-constrained access tokens are not mandatory in all cases, are they? Besides, the nonce is marked as optional in a DPoP in RFC 9449

Overall, do you agree we should return a different nonce for each call to the endpoint?

This is not agreed. You can return a different nonce if you want, but it is fine to return the same nonce, and there is already text in the spec ( https://openid.github.io/OpenID4VCI/openid-4-verifiable-credential-issuance-wg-draft.html#section-12.5 ) that I hope makes clear that nonces are not single use:

A Wallet can continue using a given nonce until it either expires or is rejected by the Credential Issuer. The Credential Issuer determines how frequently a particular nonce can be used. Servers MUST establish a clear policy on whether the same key proof can be reused and for how long, or if each Credential Request requires a new key proof.

I agree that the "unpredictable" point you raise in your initial comment is unclear, I opened a PR to add various clarity about nonces, including trying to clarify that they're a challenge rather than a nonce as was discussed in https://github.com/openid/OpenID4VCI/issues/496 : https://github.com/openid/OpenID4VCI/pull/525

jogu avatar May 28 '25 08:05 jogu

I have to admit that I do not understand the strategy here. The spec does not mandate any constraint for the nonce. Currently it is allowed to be a fixed value, publicly available to everyone calling the endpoint and possibly for an unlimited period of time. Then why have a nonce in the first place?

andprian avatar Jun 04 '25 08:06 andprian

I have to admit that I do not understand the strategy here. The spec does not mandate any constraint for the nonce. Currently it is allowed to be a fixed value, publicly available to everyone calling the endpoint and possibly for an unlimited period of time. Then why have a nonce in the first place?

It's down to the issuer's policy. The issuer can use the nonce to guarantee freshness of the proofs if it's security posture requires that.

jogu avatar Jun 06 '25 17:06 jogu

Based on my implementation experience, having nonce endpoint issue nonces/challenges that can be used by any session is a big red flag. It is not a big issue on a small scale. It is also not an issue if your workflow does not require unique challenges. But if your scale is high and nonces/challenges must be fresh/unique, this design creates completely unnecessary implementation difficulties.

All highly-scalable servers rely on some kind of sharding. Any global dynamic resource (that cannot be localized to a single shard) is a non-trivial problem to solve. Sure, there are ways to do it, but it is easy to miss something (with anything that an implementor missed becoming a denial-of-service attack vector). In this particular case I do not see any technical need to create a global resource (correct me if I am wrong).

The problem is that nonce is not bound to any session. If the nonce endpoint is protected or not is less of an issue. However, in the current design adding protection also makes the nonce session-bound, so protection is the most natural way to solve this problem IMHO. And since high level of protection is not important, we do not have to require DPoP nonce when DPoP is used (but I think we should allow or even require it to issue a DPoP nonce as well).

sorotokin avatar Jun 06 '25 17:06 sorotokin

It's down to the issuer's policy. The issuer can use the nonce to guarantee freshness of the proofs if it's security posture requires that.

The fact that it’s a matter of issuer policy is exactly the problem. How can one build a secure wallet that protects the end user if there is no guarantee on what the issuer is implementing?

andprian avatar Jun 09 '25 07:06 andprian

It's down to the issuer's policy. The issuer can use the nonce to guarantee freshness of the proofs if it's security posture requires that.

The fact that it’s a matter of issuer policy is exactly the problem. How can one build a secure wallet that protects the end user if there is no guarantee on what the issuer is implementing?

As far as I can see a secure wallet is 100% reliant on a secure issuer. You can't detach these two concepts, both have to behave sensibly for the user to be protected. Tightening up on how long a challenge can be valid for would probably be better placed in HAIP, I'd suggest raising an issue there with suggestions.

jogu avatar Jun 09 '25 17:06 jogu

Based on my implementation experience, having nonce endpoint issue nonces/challenges that can be used by any session is a big red flag. It is not a big issue on a small scale. It is also not an issue if your workflow does not require unique challenges. But if your scale is high and nonces/challenges must be fresh/unique, this design creates completely unnecessary implementation difficulties.

All highly-scalable servers rely on some kind of sharding. Any global dynamic resource (that cannot be localized to a single shard) is a non-trivial problem to solve. Sure, there are ways to do it, but it is easy to miss something (with anything that an implementor missed becoming a denial-of-service attack vector). In this particular case I do not see any technical need to create a global resource (correct me if I am wrong).

The problem is that nonce is not bound to any session. If the nonce endpoint is protected or not is less of an issue. However, in the current design adding protection also makes the nonce session-bound, so protection is the most natural way to solve this problem IMHO. And since high level of protection is not important, we do not have to require DPoP nonce when DPoP is used (but I think we should allow or even require it to issue a DPoP nonce as well).

Can you please explain how an access token issued by the AS would facilitate sharding of nonces with the credential issuer?

tlodderstedt avatar Jun 09 '25 18:06 tlodderstedt

Based on my implementation experience, having nonce endpoint issue nonces/challenges that can be used by any session is a big red flag. It is not a big issue on a small scale. It is also not an issue if your workflow does not require unique challenges. But if your scale is high and nonces/challenges must be fresh/unique, this design creates completely unnecessary implementation difficulties.

All highly-scalable servers rely on some kind of sharding. Any global dynamic resource (that cannot be localized to a single shard) is a non-trivial problem to solve. Sure, there are ways to do it, but it is easy to miss something (with anything that an implementor missed becoming a denial-of-service attack vector). In this particular case I do not see any technical need to create a global resource (correct me if I am wrong).

The problem is that nonce is not bound to any session. If the nonce endpoint is protected or not is less of an issue. However, in the current design adding protection also makes the nonce session-bound, so protection is the most natural way to solve this problem IMHO. And since high level of protection is not important, we do not have to require DPoP nonce when DPoP is used (but I think we should allow or even require it to issue a DPoP nonce as well).

Out of curiosity: have you implemented your current version with self-contained nonces? I think I understand your point, but from my experience what people would do for such larger-scale deployments is to use self-contained nonces (e.g., a HMAC-protected JWT with the server timestamp and something like a uuid) and a sliding window cache (this can easily be sharded). The self-contained nonce guarantees freshness (since it contains the server time when created) and the cache guarantees one-time use and the cache can be sharded pretty easily. I am wondering if that would solve your problem, or if we really need the session/user binding for the nonce request?

c2bo avatar Jun 09 '25 18:06 c2bo

HMAC-protected JWT with the server timestamp and something like a uuid

an AEAD'd timestamp works as well

bc-pi avatar Jun 09 '25 18:06 bc-pi

Can you please explain how an access token issued by the AS would facilitate sharding of nonces with the credential issuer?

Nonces can be scoped to the access token. If my server issued a zillion nonces that are not yet expired or used up, I do not need to worry about them. I only need to worry about nonces that were issued in the context of the particular access token.

sorotokin avatar Jun 09 '25 19:06 sorotokin

Out of curiosity: have you implemented your current version with self-contained nonces? I think I understand your point, but from my experience what people would do for such larger-scale deployments is to use self-contained nonces (e.g., a HMAC-protected JWT with the server timestamp and something like a uuid) and a sliding window cache (this can easily be sharded). The self-contained nonce guarantees freshness (since it contains the server time when created) and the cache guarantees one-time use and the cache can be sharded pretty easily. I am wondering if that would solve your problem, or if we really need the session/user binding for the nonce request?

I did not implement it, our current code assumes that nonce is a protected endpoint. But multipaz server implementation is not aiming to be highly-scalable anyway (our focus is the client), so, of course, I can easily change the implementation! As I said, I agree that there are clever ways to solve this problem (but I do not have time for these in my implementation, I'll probably just do self-contained nonce without check for reuse).

My point is not that the spec is unimplementable. The point is that making this endpoint unprotected creates unnecessary implementation challenges. Naive code will be written (or copied from code meant to be basically sample code, like ours), deployed, and people will get burned by it.

It is very low burden for the client to send authorization to access this endpoint. We do not even have to mandate server check it - the only important thing is that the client uses nonce in the context of the access token which was presented to create the nonce.

sorotokin avatar Jun 09 '25 19:06 sorotokin

Can you please explain how an access token issued by the AS would facilitate sharding of nonces with the credential issuer?

@sorotokin Re: the WG discussion on June 10th, and to add to @tlodderstedt's question above — if one makes a design choice to implement session stickiness and wants to apply a similar "stickiness" approach to the nonce, independently of the session, is there anything in the spec that would prevent that?

lj-raidiam avatar Jun 10 '25 20:06 lj-raidiam

@sorotokin Re: the WG discussion on June 10th, and to add to @tlodderstedt's question above — if one makes a design choice to implement session stickiness and wants to apply a similar "stickiness" approach to the nonce, independently of the session, is there anything in the spec that would prevent that?

If I understand what you mean by "stickiness" correctly and if I understand "current design" correctly, it is not possible without playing tricks. (The trick one could play of course, is to create a virtual issuance server per session). Server does not have firm knowledge (i.e. short of relying on heuristics) which nonce was requested by a client with a particular access token. Server would have to accept any nonce that it recently issued for any access token. So server would have to issue a lot of nonces, and either rely on secret knowledge to ensure that the nonce that is presented is genuine (+ perhaps a list of used-up nonces), or keep a list of outstanding nonces. In my current implementation (that treats nonce endpoint as protected) a single nonce is issued for a particular access token and the client has to use that exact single nonce, so it is naturally "sticky".

The more I think about this, the less comfortable I am with the lack of this binding (between the nonce and the access token). Suppose I have a proprietary issuance API/protocol that I want to bridge to openid4vci. It might very well be designed with the expectation that a nonce (aka key challenge) is issued to a particular client/session. But I cannot express that: in openid4vci as it stands I have to accept any of the outstanding nonces. In contrast if my issuer is happy to honor any valid nonce and does not need it to be bound to the client/session, there is no problem if openid4vci binds nonces to sessions: a valid nonce issued in a specific session is still a valid nonce.

sorotokin avatar Jun 12 '25 05:06 sorotokin

But I cannot express that: in openid4vci as it stands I have to accept any of the outstanding nonces. In contrast if my issuer is happy to honor any valid nonce and does not need it to be bound to the client/session, there is no problem if openid4vci binds nonces to sessions: a valid nonce issued in a specific session is still a valid nonce.

I don't think we should force all implementations to protect the nonce endpoint - there are good reasons not to do so depending on the deployment, overall architecture, etc. If we want the optional ability to protect it, then it should be signaled via an additional flag in metadata - would imho be by far the cleanest way.

c2bo avatar Jun 13 '25 08:06 c2bo

But I cannot express that: in openid4vci as it stands I have to accept any of the outstanding nonces. In contrast if my issuer is happy to honor any valid nonce and does not need it to be bound to the client/session, there is no problem if openid4vci binds nonces to sessions: a valid nonce issued in a specific session is still a valid nonce.

I don't think we should force all implementations to protect the nonce endpoint - there are good reasons not to do so depending on the deployment, overall architecture, etc. If we want the optional ability to protect it, then it should be signaled via an additional flag in metadata - would imho be by far the cleanest way.

@c2bo having such a flag would be great since it would allow issuers that want to protect the endpoint to be conformant to VCI

andprian avatar Jun 13 '25 09:06 andprian

I don't think we should force all implementations to protect the nonce endpoint - there are good reasons not to do so depending on the deployment, overall architecture, etc. If we want the optional ability to protect it, then it should be signaled via an additional flag in metadata - would imho be by far the cleanest way.

I think it might be an option to require the wallet to pass an access token, but leave it upto the issuer whether it actually requires & checks the access token or not? That's actually simpler for wallets I think.

jogu avatar Jun 13 '25 09:06 jogu

I think it might be an option to require the wallet to pass an access token, but leave it upto the issuer whether it actually requires & checks the access token or not? That's actually simpler for wallets I think.

This certainly seems like a simpler solution than metadata (less forks in the code). I would add the language that the client must not attempt to use the nonce obtained using one access token with the different access token, but the server is under no obligation to reject such nonce.

sorotokin avatar Jun 13 '25 15:06 sorotokin

I think it might be an option to require the wallet to pass an access token, but leave it upto the issuer whether it actually requires & checks the access token or not? That's actually simpler for wallets I think.

This certainly seems like a simpler solution than metadata (less forks in the code). I would add the language that the client must not attempt to use the nonce obtained using one access token with the different access token, but the server is under no obligation to reject such nonce.

The problem I see with always using an access token is that this would require DPoP (if DPoP is used by the AS) as well which might add significant cost for deployments that might not need the session binding (if you use DPoP, then the call to the nonce endpoint would also require DPoP and a DPoP nonce, effectively adding more calls) -> I'd prefer to make this discoverable via metadata if we choose to add it.

c2bo avatar Jun 13 '25 16:06 c2bo

The problem I see with always using an access token is that this would require DPoP (if DPoP is used by the AS) as well which might add significant cost for deployments that might not need the session binding (if you use DPoP, then the call to the nonce endpoint would also require DPoP and a DPoP nonce, effectively adding more calls) -> I'd prefer to make this discoverable via metadata if we choose to add it.

Your milage may vary of course, but IMHO, if DPoP is used, requiring DPoP for nonce and mandating that this endpoint will give you a fresh DPoP nonce would be the simplest solution. But I am open to do it either way.

sorotokin avatar Jun 13 '25 18:06 sorotokin

The problem I see with always using an access token is that this would require DPoP (if DPoP is used by the AS) as well which might add significant cost for deployments that might not need the session binding (if you use DPoP, then the call to the nonce endpoint would also require DPoP and a DPoP nonce, effectively adding more calls) -> I'd prefer to make this discoverable via metadata if we choose to add it.

There's no need to require a DPoP nonce (/a fresh DPoP nonce), that's a per-endpoint policy decision for the issuer I think.

The alternative to meta data (nonce_endpoint_requires_access_token? which honestly sounds a bit odd but I'm not sure why) would be to respond with a 401 & www-authenticate header as Brian mentioned here: https://github.com/openid/OpenID4VCI/issues/541#issuecomment-2956563636 - not sure I have a strong preference between those two solutions.

jogu avatar Jun 13 '25 19:06 jogu

Brian's thoughts here: https://github.com/openid/OpenID4VCI/issues/541#issuecomment-2961007864 are relevant too, if not useful.

bc-pi avatar Jun 13 '25 20:06 bc-pi

WG Call 19.6

  • It seems the access token would primarily be used to manage/shard nonces.
  • for c_nonces, self contained nonces are sufficient
  • there might be value in the issuer could signal its requirement for an access token
  • What about DPoP nonces? they must be fetched unprotected as they are required for the token request (issuing access tokens). Also, if the access token is DPoP bound, the nonce endpoint request itself would need to be DPoP protected.
  • General comment: Many security protocols provide a nonce in the first step of the process in an unprotected manner, doesn't seem to be a problem

tlodderstedt avatar Jun 19 '25 08:06 tlodderstedt

I think adding the option to authenticate/authorize the call to the nonce endpoint significantly adds complexity due to the side effects for DPoP nonces and DPoP bound access tokens for unclear benefits. I suggest to close this issue.

tlodderstedt avatar Jun 19 '25 08:06 tlodderstedt