Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update section on client authentication #216

Merged
merged 13 commits into from
Jan 21, 2025

Conversation

eric-murray
Copy link
Collaborator

What type of PR is this?

  • enhancement/feature

What this PR does / why we need it:

The current specification for client authentication does not limit the lifetimes of the signed JWT. This presents a security risk, and further complicates implementations as there is an additional requirement that the authorisation server should reject duplicate JWTs.

This PR proposes that:

  • the signed JWT must include the iat claim
  • token lifetimes must be limited to 300 seconds
  • tokens with longer lifetimes must be rejected by the authorisation server

Which issue(s) this PR fixes:

Fixes #208

Special notes for reviewers:

None

Changelog input

 release-note
 - Add restriction on maximum JWT lifetime for client authentication

Additional documentation

See proposed changes for documentation links

@eric-murray
Copy link
Collaborator Author

Specifying that they may return an error if the expiration time is longer also ensures interoperability.

No, it doesn't. An API consumer may get used to tokens with a longer lifetime being accepted, and then one day they are rejected because they are dealing with a different authorisation server.

However, mandating the inclusion of a claim that is optional in the standard itself does not

The CAMARA specification is a separate specification to OIDC, albeit one defined by differences to that standard. Any CAMARA client or authorisation server must meet the CAMARA specification, and not just vanilla OIDC. What you are saying is that any API consumer that does not adopt the CAMARA standard but only the OIDC standard will have interoperability issues. Well, yes they will, which is why we don't say "just use OIDC".

There are already changes relative to OIDC in the CAMARA standard that mandate features that are optional in OIDC. This would be another such change - a feature that is optional for OIDC is mandatory for CAMARA.

What would happen to existing authorization server implementations that follow OIDC and the iat claim is considered optional?

iat is already an optional parameter, so no OIDC compliant authorisation server should reject a token that includes the iat claim.

What would happen to applications that are already integrated and do not send the iat claim?

API definitions are linked to a specific release of the CAMARA working group standards. This would be a change to the next release of the ICM standards, and so existing API definitions are not affected. The client would only need to upgrade to support the next release of the API.

According to your proposal, what would be the behavior of an authorization server that receives a request without the iat claim?

The authorisation server should reject the request as not being CAMARA compliant. However, no "authorisation server police" are going to come around and shutdown the server. But an API consumer used to having tokens accepted without the iat claim would then be confused when other authorisation servers start rejecting them.

That is the situation we are trying to avoid by having an "interoperability profile".

@palmerabollo
Copy link

I think we can have the best of both worlds (ensure interoperability + minimize deviation from standards + avoid breaking existing API consumers when possible) if we:

  1. Keep iat as an optional field for API consumers

In addition to the mandated claims, the signed JWT SHOULD also include the iat (issued at) claim

  1. Maintain the restriction on expiration time as mandatory (MUST) , using the authorization server's current time as a default when iat is not present.

The difference between the exp (expires at) and iat (or the authorization server's current time, if absent) claims MUST be no more than 300 seconds [...]

JWTs should be used shortly after they are generated; 300 seconds is a generous interval, so I don't think defaulting to the current time is an issue.

@eric-murray What do you think?

Some telcos don't have a dedicated Authorization Server exclusively for issuing tokens for CAMARA APIs. This is one of the reasons why making standard JWTs not valid is problematic.

@eric-murray
Copy link
Collaborator Author

Hi @palmerabollo

So I'm fine to keep iat optional - this just slightly complicates the authorisation server logic for rejecting tokens, but isn't a big deal. Also fine to make 300 second token lifetime mandatory as that was the original proposal.

I'd prefer to keep the statement "JWTs with a longer lifetime SHALL be rejected by the authorisation server" because this addresses @MarkCornall's concern about specifying consistent behaviour across implementations.

Now I accept that there will be authorisation server implementations that will anyway accept tokens with a longer lifetime. And that's fine - as I said, no "authorisation server police" are going to come around and shut it down. Specifications such as this one are written primarily for clients so that they understand how to construct their requests, and what responses they can expect. API providers know the game and know what they can get away with at the edges of a standard without breaking it.

So the statement "JWTs with a longer lifetime SHALL be rejected by the authorisation server" would be telling the client what will happen if their token's lifetime is too long. Whether that actually happens in all cases is not so important, though we do want to avoid the situation where most authorisation servers accept longer lived tokens and clients get used to that behaviour.

@palmerabollo
Copy link

Agree, thanks Eric.

jpengar
jpengar previously approved these changes Oct 21, 2024
Copy link
Collaborator

@jpengar jpengar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM considering #216 (comment)

@AxelNennker
Copy link
Collaborator

What is the benefit of having iat?

Why not just use exp and define that exp-now<300 seconds?

Also why mention all the different endpoints? Why not just state that the lifetime of the jwt used in client_credentials flow must be shorter than 300 seconds.

The current text seems unnecessarily complicated.

@eric-murray
Copy link
Collaborator Author

@AxelNennker

What is the benefit of having iat

Why not just use exp and define that exp-now<300 seconds?

CIBA Core made the mistake of making iat optional rather than mandatory for private_key_jwt, and we need to live with that because of the "existing implementations" argument. If iat was mandatory, key lifetime would be straightforward for both API consumer and the authorisation server to calculate for any JWT.

JWTs without an iat claim could have been created at any time. This increases the risk that the API consumer "stockpiled" them some time ago, and they have since leaked. By specifying that the API consumer should limit the token lifetime to 300 seconds when they create them, we can reduce (but not eliminate) this risk.

I see that OIDC CIBA avoided this mistake for signed authentication requests, so well done there.

Also why mention all the different endpoints?

From ICM Meeting Minutes:

Outcome:

Eric will draft more detailed text to specify exact requirements and error responses for various token lifetime scenarios. The group will review this in the next meeting to finalize.

So the request was to specify error responses for the different endpoints as the relevant standards do not include "token lifetime too long" as an explicit error example. But I agree it may now be better to document this in #220.

Why not just state that the lifetime of the jwt used in client_credentials flow must be shorter than 300 seconds.

If the standard is using the term "lifetime", we need to define what is meant by "lifetime", and I am defining it as the difference between the exp and iat claims of the token. Doesn't get much simpler than that. The complexity arises solely from the iat claim not being mandatory in the JWT itself, but the concept remains valid even for API consumers who do not explicitly include the iat claim in their token.

"client_credentials flow" is not the correct term here

@eric-murray
Copy link
Collaborator Author

Removed wording on error responses, as that is now covered by #220

@AxelNennker
Copy link
Collaborator

JWTs without an iat claim could have been created at any time. This increases the risk that the API consumer "stockpiled" them some time ago, and they have since leaked. By specifying that the API consumer should limit the token lifetime to 300 seconds when they create them, we can reduce (but not eliminate) this risk.

How does iat help against creating stockpiled jwts or stockpiled jwts being misused after they were stolen?

The API consumer is controlling JWT creation. So, they could create 1000 JWT for future use.

some pseudo-code that creates JWTs with lifetime 300 that can run at the API consumer's site at anytime:

start = now
for iat=start do
  create JWT with iat and exp=iat+300
  sign JWT
  store JWT (somewhere where it can be stolen)
  iat=iat+300
  if iat>start+3000000 break

Now the API consumer has many JWTs with iat which still can be stolen and misused.

I feel I am missing something. Sorry, if I am thick. Which threat scenario is iat protecting against?

@eric-murray
Copy link
Collaborator Author

Now the API consumer has many JWTs with iat which still can be stolen and misused.

I feel I am missing something. Sorry, if I am thick. Which threat scenario is iat protecting against?

Well, it clearly doesn't eliminate the risk of tokens being stolen and misused, but may reduce the number that are available to be stolen.

Let's say an API consumer does not want to create signed JWTs "on the fly", but rather generate them overnight using a batch process, and then use them throughout the next day by pulling one off the stack when required. Even with the new 300 second maximum token lifetime rule, they could still create multiple batches overnight, with exp times incremented by 300 seconds for each batch. They would just need to remember to discard any unused tokens as each batch expired.

But if they included the iat in the tokens, then they could only create a batch for the next 300 seconds. Any created with a later exp time would be invalid and known to be invalid when they were created (as exp - iat would be > 300). So the maximum "stockpile" size would be 300 seconds worth of tokens, and the consequences of their theft would only last for the next 300 seconds.

And maybe the need to generate a new batch every 300 seconds just might persuade the API consumer to generate them on demand, as they should.

It's a small point, but a point nonetheless.

@eric-murray
Copy link
Collaborator Author

eric-murray commented Dec 6, 2024

@camaraproject/identity-and-consent-management_maintainers
I'd like to try and progress this PR. Outstanding issue is how the API provider should treat the iat claim if included in the JWT.

The two options are:

  • Option 1: Ignore it, and base the remaining token lifetime on the arrival time at the authorisation server; or
  • Option 2: If present, use it to compute the true token lifetime (i.e. exp - iat), otherwise use the arrival time at the authorisation server

The pros and cons for each solution are:

  • Option 2 is slightly more complex, as establishing the reference time for the token lifetime requires a bit of logic (if iat is present, use that, otherwise use current time in unix time format)
  • Option 2 allows the true token lifetime and hence validity to be established from the token itself, and will become the "de facto" solution as increasing numbers of API consumers include the iat claim in their JWT. If iat was mandatory, there would be no objection to computing token lifetime this way.

It's a relatively small point compared to all the other issues being discussed in ICM, so I propose a simple "vote" to resolve it. I favour Option 2. Axel favours Option 1. Others should express their view in a comment.

@jpengar
Copy link
Collaborator

jpengar commented Dec 10, 2024

Agree with Option 2 as per #216 (comment)

@jpengar
Copy link
Collaborator

jpengar commented Dec 17, 2024

As there are no further responses, can we close this PR considering option 2? Do you agree with this? If so, can we merge the PR as it stands? I would suggest setting this week as the deadline.
CC @eric-murray @AxelNennker

@jpengar
Copy link
Collaborator

jpengar commented Dec 19, 2024

@AxelNennker Please review this PR to unblock and potentially merge it. The latest feedback from WG is available at https://lf-camaraproject.atlassian.net/wiki/spaces/CAM/pages/56754234/2024-12-18+ICM+Minutes

@AxelNennker
Copy link
Collaborator

@eric-murray @jpengar I am waiting on comments on my suggestions. Please commit or write a comment why not.

@eric-murray
Copy link
Collaborator Author

I am waiting on comments on my suggestions. Please commit or write a comment why not.

There is only one open suggestion, which already has a comment against it, but I will add another. The current text proposal is the one that the last meeting agreed to proceed with.

@AxelNennker
Copy link
Collaborator

I marked the two uncommented suggestions with "I see no reaction to this suggestion".

Why do you think that I am in favor of option 1)?
One of the suggestions I did not see a reaction too is this:

The JWT age, determined by means of the iat field, MUST be no more than 300 seconds.
The value of the exp field MUST not be more than 300 seconds bigger than the iat value.
If the JWT does not include the iat claim, the value of exp MUST NOT be more than 300 seconds in the future.

I want to avoid using different words than the spec used.

Hi @eric-murray I re-used the term "age" from https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6 and void introducing "lifetime". Does the suggestion above capture the intent of this PR?

I think "required" is better then "mandate", and "age" is better than "lifetime".

Which is not ignoring iat, right?

@eric-murray you write you see only one suggestion by me. Are we looking at the same page? I see two suggestions here on this page and two on this page https://github.com/camaraproject/IdentityAndConsentManagement/pull/216/files

@eric-murray
Copy link
Collaborator Author

Why do you think that I am in favor of option 1)? One of the suggestions I did not see a reaction too is this:

The JWT age, determined by means of the iat field, MUST be no more than 300 seconds.
The value of the exp field MUST not be more than 300 seconds bigger than the iat value.
If the JWT does not include the iat claim, the value of exp MUST NOT be more than 300 seconds in the future.

I don't see that suggestion. What I see is the (now outdated) text suggestion:

The JWT lifetime as indicated by the exp field MUST be no more than 300 seconds. Token lifetime SHALL be measured relative to time of receipt.

... and this is Option 1 - determine lifetime relative to the arrival time, ignoring iat if present. If that text suggestion is no longer valid, I'll mark it resolved.

I want to avoid using different words than the spec used.

Hi @eric-murray I re-used the term "age" from https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6 and void introducing "lifetime". Does the suggestion above capture the intent of this PR?

I think "required" is better then "mandate", and "age" is better than "lifetime".

Happy to use "required" instead of "mandated", though they are not exact synonyms.

I think "lifetime" is (exp - iat), whereas "age" is (now - iat). We want to limit the token lifetime, so that the authorisation server can know when to checking that a specific token is not being re-used. I think using "age" obscures the key point that a token will be rejected if its lifetime more than 300 seconds, even if its age is less.

So I would prefer to use "lifetime" to emphasise that a token for which exp - iat > 300 will be rejected no matter how old it is when received by the authorisation server. exp - iat must have a name, and that cannot be "age".

@eric-murray you write you see only one suggestion by me. Are we looking at the same page? I see two suggestions here on this page and two on this page https://github.com/camaraproject/IdentityAndConsentManagement/pull/216/files

I only see one outdated suggestion not resolved, and do not see the text you quote above. Are the new changes shown as "pending" in your view? Github lets you prepare multiple changes in private before publishing them for others to see.

jpengar
jpengar previously approved these changes Jan 14, 2025
Copy link
Collaborator

@jpengar jpengar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@AxelNennker
Copy link
Collaborator

I never mend that iat is ignored and don't know where this notion comes from.

The JWT lifetime MUST be no more than 300 seconds, measured as the difference between the exp (expires at) claim and the token creation time (the value of the iat claim, whether present in the token or not). JWTs with a longer lifetime SHALL be rejected by the authorisation server. If the JWT does not include the iat claim, token lifetime SHALL be measured relative to time of receipt.

I think the sentence "measured as the difference between the exp (expires at) claim and the token creation time (the value of the iat claim, whether present in the token or not)" makes no sense, because you cannot compute the difference of two values if one is not present.

That is why I proposed this text.

The JWT age, determined by means of the iat field, MUST be no more than 300 seconds.
The value of the exp field MUST not be more than 300 seconds bigger than the iat value.
If the JWT does not include the iat claim, the value of exp MUST NOT be more than 300 seconds in the future.

Actually that text has the same flaw.

How about this:

The JWT lifetime of the MUST not be more than 300 seconds. If the iat field is present, then the lifetime is computed as the difference of exp and iat. If the iat is not present, then the lifetime is computed as the difference of exp and the current time.

@eric-murray
Copy link
Collaborator Author

I think the sentence "measured as the difference between the exp (expires at) claim and the token creation time (the value of the iat claim, whether present in the token or not)" makes no sense, because you cannot compute the difference of two values if one is not present.

It does make sense, because the API consumer can always ask themselves "if I did include the iat claim, what would it be?". That will then tell them what value of iat to use for lifetime calculation.

@eric-murray
Copy link
Collaborator Author

Following the above comments, I tightened up the language to clarify:

  • Token lifetime is set when the API consumer creates the token
  • Lifetime is measured relative to the token creation time, so the API consumer cannot create long lived tokens just because they don't include the iat claim

The JWT lifetime MUST be no more than 300 seconds when created, calculated as the difference between the exp (expires at) claim and the token creation time (which SHALL also be the value of the iat claim if present). JWTs with a longer lifetime SHALL be rejected by the authorisation server. If the JWT does not include the iat claim, token lifetime SHALL be measured relative to time of receipt.

jpengar
jpengar previously approved these changes Jan 15, 2025
Copy link
Collaborator

@jpengar jpengar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@AxelNennker
Copy link
Collaborator

AxelNennker commented Jan 19, 2025

@eric-murray the main point in the WG meeting seemed to be we want to prevent the client assertion producer to do stupid things.

Ff client assertions are created in a batch then iat would be a lie if a API consumer created assertions with a lifetime of 300 seconds.

start = now
for iat=start do
create JWT with iat and exp=iat+300
sign JWT
store JWT
iat=iat+300
if iat>start+3000000 break

Afterward they would have a ton of assertions which are all a lie because iat is a lie.
We cannot prevent the API consumer from lying or from being stupid.

What we write in CAMARA specs cannot prevent servers to create assertions with any values and sign them.

The API consumer could also create a ton of assertions in a batch without iat and just increase exp by 300.
Whether they do that or not can't be controlled by the API producer or any other entity.

The OAuth2 security best practices do not mention iat once.

I think it is a good idea to restrict the lifetime of the assertion to 300 seconds.

documentation/CAMARA-Security-Interoperability.md Outdated Show resolved Hide resolved
documentation/CAMARA-Security-Interoperability.md Outdated Show resolved Hide resolved
documentation/CAMARA-Security-Interoperability.md Outdated Show resolved Hide resolved
@AxelNennker
Copy link
Collaborator

@eric-murray addresses clients and servers in one text. I think we should only address the server and the client requirements logically follow from that.

The JWT lifetime MUST be no more than 300 seconds, measured as the difference between the exp (expires at) claim and the token creation time (the value of the iat claim, whether present in the token or not). JWTs with a longer lifetime SHALL be rejected by the authorisation server. If the JWT does not include the iat claim, token lifetime SHALL be measured relative to time of receipt.

The server cannot determine the token creation time if iat is missing

How about this:

The request MUST be rejected if the exp value is more than 300 seconds in the future.
The request MUST be rejected if the difference between the exp value and the iat value, if it exists in the JWT, is more than 300 seconds.

@eric-murray
Copy link
Collaborator Author

@AxelNennker

I did update the text compared to that quoted in your last comment, but the point that the authorisation server is implementing two rules, dependent on whether iat is in the token or not, remains. I don't mind making this even more crystal clear, but I'd prefer "in the future" to be replaced with "later than the time of receipt". So how about:

The JWT lifetime MUST be no more than 300 seconds when created, calculated as the difference between the exp (expires at) claim and the token creation time (which SHALL also be the value of the iat claim if present).

The request SHALL be rejected by the authorisation server if the exp claim is more than 300 seconds later than the time of receipt. Additionally, if the iat claim is present, the request SHALL be rejected if the difference between the exp claim and iat claim is more than 300 seconds.

I think it is still useful to include some "guidance" for clients not to create long lived tokens and think that is OK just because they don't include the iat claim. But I agree that API providers cannot really police that.

@AxelNennker
Copy link
Collaborator

The JWT lifetime MUST be no more than 300 seconds when created, calculated as the difference between the exp (expires at) claim and the token creation time (which SHALL also be the value of the iat claim if present).

The request SHALL be rejected by the authorisation server if the exp claim is more than 300 seconds later than the time of receipt. Additionally, if the iat claim is present, the request SHALL be rejected if the difference between the exp claim and iat claim is more than 300 seconds.

Good

@eric-murray
Copy link
Collaborator Author

OK, updated

@AxelNennker AxelNennker dismissed their stale review January 20, 2025 21:27

Dismissing my review change request that would make iat mandatory

@AxelNennker
Copy link
Collaborator

I removed my review change request and if won't create a new one.

Still, how about this:
Instead of

The JWT lifetime MUST be no more than 300 seconds when created, calculated as the difference between the exp (expires at) claim and the token creation time (which SHALL also be the value of the iat claim if present).

write:

The API consumer MUST not create client assertions with a lifetime of more than 300 seconds, calculated as the difference between the exp (expires at) claim and the token creation time (which SHALL also be the value of the iat claim if present).

thus making it clear that this sentence is guidance for the API consumer who creates client assertions.

@eric-murray
Copy link
Collaborator Author

@AxelNennker
I'm fine with that. Text updated.

Copy link
Collaborator

@jpengar jpengar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@AxelNennker
Copy link
Collaborator

Formally we can merge now. To make it easier to see that all comments are addressed, I closed all "conversions" which were "outdated" anyway. Also there a no change-requests from reviews.
I'll merge this now.

@AxelNennker AxelNennker merged commit b5eaff5 into camaraproject:main Jan 21, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Proposal for CAMARA mandated minimum acceptable JWT token lifetime
6 participants