Conversation
477a6fd to
3f57b1b
Compare
|
To the best of my understanding, refreshing ID Tokens should not alter the ID Token's DPoP-bound JWK. I like the idea of allowing the RP also to refresh the JWK. Do you have any security concerns, or is there a reason it should be prevented from doing that? |
@JonasPrimbs How are you thinking the RP would refresh the JWK? |
@dickhardt By sending another token request with |
|
@JonasPrimbs There seem to be two ways to rotate JWKs during refresh.
The second approach, while stronger than the first approach, is still to my mind weaker than the not allowing key rotation during refresh. Below I will explain my thinking on problems with the second approach. If you are protecting the JWK signing key, key1, as a non-extractable key in a web browser, each time you change the key you introduce the risk that the new JWK signing key, key2, isn't setup to be non-extractable. Consider the following attack:
However if we do not allow the ability to rotate JWKs, Eve can not use the refresh flow to substitute an extractable key2 for Alice's original key1 which is non-extractable. This also applies to the use of HSM/passkeys in other settings. Remote attestation can be used to prove a key was generated inside an HSM, but this attacks complexity and additional security assumptions. Normally we rotate keys to minimize the window of vulnerability after a compromise. I could be mistaken and I'm very interested in hearing disagreeing opinions, but in this case, I don't only see ways in which enabling key rotations in the refresh flow helps an attacker. Appendix H Device Signing Keys and XSS Attacks in the OpenPubkey paper, has a deeper discussion of how to use JWKs with extractable keys to constraint XSS attackers. It isn't necessary for this discussion, but does provide more context. |
|
@EthanHeilman Besides that, I'm not feeling well with not being able to rotate the key of a refresh token. However, specifically for the refresh token, I think this is fine if we use hardware-bound/non-extractable tokens (TPM- or HSM-backed). The tokens themselves can be rotated or revoked in the event of a compromised RP being detected, and I value the security advantage of not being able to move the refresh token to another device. Maybe combining the following requirements can reduce this problem to an acceptable level:
Since the attacker would need the refresh token key to obtain a new ID token, the user must be online, as the refresh token key is non-extractable and can therefore not be transferred to the attacker's device. Using separate key pairs for distinct use cases with different lifetimes (long-term refresh tokens for refreshing access tokens, short-term ID tokens for authentication, and short-term access tokens for authorization) is a nice pattern that should also be applied here, if we want to do everything right. |
Couldn't you force rotation by simply rejecting the refresh token so the user has to log in again and generate a new key pair? I can see RP wanting the ability in some circumstances to bind tokens (ID Tokens, Refresh Tokens) to non-extractables keys, but I don't see a good mechanism to enforce it at the OP or RP Consuming Component (RPCC). The RPs best bet is to simply writing their RP Authenticating Component (RPAC) to use an non-extractable key.
What you propose here works, but I don't see the advantage over just not allowing the key to be rotated. ID Token key can't be rotated (non-extractable keys used) --> XSS attacker locked out immediately on tab close vs. ID Token key rotation via refresh (ID Token 1 min expiration) --> XSS attacker locked out 1 minute after tab close.
Thanks for sharing this. I like their idea of separating key pairs for the access token and refresh token because access tokens key pairs are shared with less trusted components whereas refresh tokens should be better protected. For this to improve security for a use case, I think two things need to be true for that use case:
As noted in the RFC: "large, distributed systems with many worker nodes, however, involving an HSM in every transaction is operationally impractical, motivating this extension." So the Access Token key pair isn't non-extractable and it can be sent around to worker nodes as needed. It starts on the same component as Refresh Token so that the initiate two DPoP headers can be generated and then it gets broadcast to worker nodes. At least the ways I am familiar with using ID Tokens, the refresh token and the ID Token are stored in the same component and you'd make both non-extractable. That said I live very much in the public client side of OpenID Connect and might be missing conventional client use cases. What ID Token use cases are thinking about here? The mechanism of DPoP-RT is clever since it merely extends existing behavior. Our Key-Binding spec as currently written would be forwards compatible with this: "If the DPoP-RT header is omitted, the AS follows the behavior defined in [DPoP] and binds both tokens to the same key from the DPoP proof." Separating DPoP Bindings for Access and Refresh Tokens |
…efresh DPoP header
JonasPrimbs
left a comment
There was a problem hiding this comment.
Looks great! As commented, I only recommend adding a reference to Section 5 of the DPoP RFC to clarify details on the Refresh Token issuance.
|
|
||
| - its `cnf` claim MUST be the same as in the ID Token issued when the original authentication occurred. | ||
|
|
||
| If a new Refresh Token is returned as a result of a Refresh Request, the newly issued Refresh Token MUST continue to be bound to the same public key as the original Refresh Token. |
There was a problem hiding this comment.
We should reference Section 5 of the DPoP RFC 9449 here, which describes detailed information on DPoP Refresh Token issuance: https://datatracker.ietf.org/doc/html/rfc9449#section-5-8
There was a problem hiding this comment.
Added ref for this
a1aa1df to
7179f71
Compare
|
@JonasPrimbs Any additional desired changes or does this pass review? |
This PR adds a short section on Token Refresh
Implementation: openpubkey/openpubkey#354
Comparison with RFC 9449 Refresh Flow
RFC 9449 OAuth 2.0 DPoP Refresh flow defines the refresh flow as follows:
The Refresh Flow must contain a DPoP signed by a public key. What this public key is associated with depends on the if the request is made by a public or confidential client.
For public clients the DPoP must verify for the public key bound to the refresh token used in the refresh request and this public key must be the can public key used to obtain the refresh token originally, i.e. it can not change without requesting a new refresh token.
For confidential clients refresh tokens are not bound to a public key, but a DPoP is sent anyways. It is implied by the fact that a Refresh Request is a type of Access Token request, that the public key in the DPoP supplied becomes the new public key in the refresh Access Token. This would enable rotating the public key in the access token.
For public clients, the refresh flow is the same for OpenID Connect Key Binding and RFC 9449.
For confidential clients there is one difference between the refresh flow for OpenID Connect Key Binding 1.0 and RFC 9449. RFC 9449 appears to allow the Refresh Token to change the Access Token's public key during Refresh. OpenID Connect Key Binding requires the same public key be used. That is, OpenID Connect Key Binding keeps the same behavior across public and confidential clients. In essence RFC 9449 allows the credentials of the Refresh Token, in confidential clients, to override the public key associated with the session, whereas OpenID Connect Key Binding sees the public key as essential to the session.
The reasoning for this difference:
The credentials for client are universal to that client. For instance if there are 1,000 active sessions that need to be refreshed, each session must be able to make refresh requests using those credentials. The public key, on the other hand, is specific to a session. If we allow the client credentials to override the public key than any of these sessions could compromise any other session, by using the refresh flow to inject a attacker controlled public key.
Instead if the public key can not be overridden, then each session could use a different HSM for that key pair. If Alice's session on the backend is compromised, she could not hijack Bob's session, because she could not replace Bob's public key via a token refresh. More importantly, if an attacker were to compromise the client credentials, the attacker would not be able to break the security of any of the existing sessions without first also compromising the key pairs associated with those sessions. This allows implementors in complex applications to significant limit the blast radius of a compromise.
Finally client credentials are often just a bearer secret that is sent over the wire to the OP/AS. This dramatically increases the difficulty of securing them. Having a key pair be the foundation of a sessions security provides a firm foundation. Given that protocol already supports a key pair and treats the key pair in this way for public clients, it is valuable to use this mechanism to upgrade confidential clients to the security offered by public clients.