Code Monkey home page Code Monkey logo

Comments (28)

tlodderstedt avatar tlodderstedt commented on September 15, 2024

Sounds reasonable - I support this proposal.

from draft-dpop.

b---c avatar b---c commented on September 15, 2024

I'm not sure I follow the specifics of the proposal 100% but I think I might support it :)

More generally however, I think we need to take a step back and try to rationalize the whole model a bit. Just by way of example, right now for protected resource access in -01, the full JWK is being sent twice - in both the access token and the proof token. Unless I'm misunderstanding something, which is always possible. And speaking of misunderstanding, maybe I'm just not smart enough to get it but the distinction between the proof and the binding tokens isn't clear to me.

For protected resource access we need:

  1. the JWK (public key)
  2. the proof (to some degree of proof anyway)
  3. the access token bound to the JWK

The three can be combined or separated in various different ways.

For token requests, I think we need to decide how much of what was done for protected resource access can or should be reused. Or if we want to have more OAuth application specific stuff there.

If we lean towards the former (i.e. making token requests similar to protected resource requests), and I feel like the draft is tending that way now in -00/01, I think we can move towards something that has a lot of parallels to token binding where the key and proof of possession are sent with each request in the sec-token-binding header and then binding of token(s) to the key and/or checking the binding in a token is done as needed. In DPoP, the JWK + the proof (could be together or separate) headers could serve a function similar to the sec-token-binding header. Then tokens are bound to the jwk by the AS and the binding in tokens is verified by protected resources with the proof part being the same for both. The JWK could be in the proof as a jwk header. Or a separate HTTP header in both cases with the access token containing a JWK thumbprint as the confirmation (I think this is most similar to OAuth Token Binding and MTLS). Or the access token could contain the whole JWK as the confirmation and the HTTP header JWK could be omitted in protected resource requests. There are probably tradeoffs for every approach.

Of course, there are other ways to skin the cat but I've rambled incoherently for too long already in what is probably the wrong place. So I'm going to stop for now. But I'm wondering if maybe trying to find some time for a synchronous discussion might be a good idea...

from draft-dpop.

dwaite avatar dwaite commented on September 15, 2024

the full JWK is being sent twice - in both the access token and the proof token. Unless I'm misunderstanding something, which is always possible.

My understanding was that only the binding JWT included the JWK. After that, the bound JWK was represented by the access token and you send a proof JWT. Representing the JWK in the proof JWT just means more traffic and that some implementations will verify the proof wrong.

My proposal is to always externalize the key, so it winds up being the same proof JWT format in the code grant, refresh grant, and resource access cases.

from draft-dpop.

danielfett avatar danielfett commented on September 15, 2024

So if I am understanding the proposal correctly, we would have something like this:

Token Request with Code

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
DPoP-Key: <JWK-1> ...
DPoP-Binding: <Proof-1> ...

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

with

JWK-1:

{
          "kty":"EC",
          "crv":"P-256",
          "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
          "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
}

and Proof-1:

{
    "typ": "dpop+jwt",
    "alg": "ES512",
}.{
    "jti": "HK2PmfnHKwXP",
    "http_method": "POST",
    "http_uri": "https://server.example.com/token",
    "exp": 1555555555,
    "cnf":{
        "dpop+kid": "<JWK Thumbprint of JWK-1>"
    }
}

signed using JWK-1.

Resource Access

POST /resource HTTP/1.1
Host: resource-server.example.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
DPoP-Proof: <Proof-1b> ...
Authorization: Bearer <AT>

where AT would contain the same cnf claim as in Proof-1 (referring to JWK-1) and Proof-1b would look like Proof-1 except for the http_uri claim.

Token Request with Refresh Token

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
DPoP-Proof: <Proof-1> ...
DPoP-Key: <JWK-2> ...
DPoP-Binding: <Proof-2> ...

grant_type=authorization_code
&refresh_token=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

Here, Proof-2 would refer to and be signed using JWK-2. The AS would bind the new AT and RT to the key in JWK-2. Proof-1 is needed to prove the binding for the refresh token.

Delivering the keys would be optional if the receiver knows them already (e.g., because some other mechanism was used to transport them).

Does that look like what you intended, @dwaite ?

from draft-dpop.

panva avatar panva commented on September 15, 2024

I think using kid as the thumbprint can lead to wrong implementations since a kid can be chosen, not just calculated. Using it would mean a kid must either be forbidden or ignored when present in the DPoP-Key header.

from draft-dpop.

danielfett avatar danielfett commented on September 15, 2024

I think using kid as the thumbprint can lead to wrong implementations since a kid can be chosen, not just calculated. Using it would mean a kid must either be forbidden or ignored when present in the DPoP-Key header.

I took that idea from RFC7638 (JWK Thumbprint), which says that

The
resulting hash value can be used for identifying or selecting the key
represented by the JWK that is the subject of the thumbprint, for
instance, by using the base64url-encoded JWK Thumbprint value as a
"kid" (key ID) value.

Yes, kids can be fabricated, but then again, there is no specified namespace for kids; the implementation must have some knowledge about its contents anyway.

from draft-dpop.

panva avatar panva commented on September 15, 2024

kid defined in 7517 is an arbitrary string. We can use the thumbprint calculation for binding for sure but reusing kid just welcomes someone using a client supplied value and the RS blindly trusting it, not recalculating it.

from draft-dpop.

danielfett avatar danielfett commented on September 15, 2024

Is there any other canonical claim name for this purpose?

from draft-dpop.

panva avatar panva commented on September 15, 2024

IIRC no, we’d have to make up one.

from draft-dpop.

b---c avatar b---c commented on September 15, 2024

My understanding was that only the binding JWT included the JWK. After that, the bound JWK was represented by the access token and you send a proof JWT.

You are right, my mistake. I'd overlooked the "(REQUIRED for DPoP Binding JWTs, OPTIONAL for DPoP Proof JWTs)" qualification in the Confirmation claim bullet in section 4. (noting again that the cnf claim isn't the right place for the verification key but I digress).

from draft-dpop.

b---c avatar b---c commented on September 15, 2024

Representing the JWK in the proof JWT just means more traffic

Well, the JWK needs to be presented to the RS in some way. It could be in the proof (or next to it in a different header) and the access token bound to it with hash (cnf with a jwk thumbprint). Or the JWK could be fully in the access token (binding the AT to the key via a cnf with jwk) and the proof implicitly relies on it. So the JWK with/in the proof is a little more traffic but not much more.

and that some implementations will verify the proof wrong.

I'm sure some implementations will do things wrong regardless. But curious why you seem to suggest this is more error prone?

from draft-dpop.

b---c avatar b---c commented on September 15, 2024

Is there any other canonical claim name for this purpose?

https://tools.ietf.org/html/rfc7800#section-3.4 defines the cnf method kid and the end of that section has:

The content of the "kid" value is application specific. For instance, some applications may choose to use a JWK Thumbprint [JWK.Thumbprint] value as the "kid" value.

Our application of it in the DPoP context could specify that the value of the kid method cnf be the SHA256 JWK Thumbprint. That would probably be fine in context but if there's concern about using 'kid' or want to be more explicit then this doc could define and register a new cnf method like jkt#S256or similar that is explicitly the SHA256 JWK Thumbprint. Note this is very similar to MTLS with x5t#S256 https://tools.ietf.org/html/draft-ietf-oauth-mtls-13#section-3.2 and token binding with tbh https://openid.net/specs/openid-connect-token-bound-authentication-1_0-04.html#Representation / https://tools.ietf.org/html/draft-ietf-oauth-token-binding-08#section-3.4

Having said all that, the cnf claim should only be used to bind the access token to a key. It's not really appropriate in the proof. (#23 is related to this and seems to be where the idea to use cnf to indicate the key that was used to sign to given token/proof).

from draft-dpop.

b---c avatar b---c commented on September 15, 2024

My proposal is to always externalize the key, so it winds up being the same proof JWT format in the code grant, refresh grant, and resource access cases.

+1 here. And also other grants.

from draft-dpop.

b---c avatar b---c commented on September 15, 2024

As I understanding the proposal and/or inject my own thoughts onto it, we would have something like this:

Token Request with Code

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
DPoP-Key: <JWK> ...
DPoP-Proof: <Proof-a> ...

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIAWTF
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
&client_id=someclientidhere

with

JWK:

{
          "kty":"EC",
          "crv":"P-256",
          "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
          "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
}

and Proof:

{
    "typ": "dpop+jwt",
    "alg": "ES256",
}.{
    "jti": "HK2PmfnHKwXP",
    "http_method": "POST",
    "http_uri": "https://server.example.com/token",
    "exp": 1555555555,
}

signed using JWK (that link is implied but maybe could also be indicated by a kid header with something "DPoP-Key" or a thumbprint).

Resource Access

POST /resource HTTP/1.1
Host: resource-server.example.com
Content-Type: application/json
DPoP-Proof: <Proof-b> ...
Authorization: Bearer <AT>

{ ... }

where the AT would contain (or reference via introspection) a cnf claim with a jwk member with the JWK from the previous step. Something like:

   {"typ":"at+JWT","alg":"RS256","kid":"some AS key"}.
   {
     "sub": "danielfett",
     "exp": 1555555655,
     "client_id": "someclientidhere",
     ... etc ...,
     "cnf" : { 
       "jwk" : {
          "kty":"EC",
          "crv":"P-256",
          "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
          "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
       }
     } 
   }

Proof-b is just a regular proof for the given request:

{
    "typ": "dpop+jwt",
    "alg": "ES256",
}.{
    "jti": "_0accESWLTCbwc",
    "http_method": "POST",
    "http_uri": "https://resource-server.example.com/resource",
    "exp": 1555555559,
}

Token Request with Refresh Token

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
DPoP-Proof: <Proof-c> ...
DPoP-Key: <JWK> ...

grant_type=authorization_code
&refresh_token=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
&client_id=someclientidhere

Proof-c is again a regular proof for the given request:

{
    "typ": "dpop+jwt",
    "alg": "ES256",
}.{
    "jti": "cc1GmfnYwi16",
    "http_method": "POST",
    "http_uri": "https://server.example.com/token",
    "exp": 1555555660,
}

And JWK is the same key the client has been using:

{
          "kty":"EC",
          "crv":"P-256",
          "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
          "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
}

One could assume the full key is stored with and bound to the refresh token so DPoP-Key could be omitted here. However I think more uniformity for calls to the AS is probably preferable and the AS could store just a hash of the key with the RT.

Delivering the key could be optional if the receiver knows them already (e.g., because some other mechanism was used to transport them). In effect that's what is happening with protected resource access.

For now, I've put aside binding ATs to a new key on refresh. That's a separate issue that should be discussed independently.

from draft-dpop.

dwaite avatar dwaite commented on September 15, 2024

I'm sure some implementations will do things wrong regardless. But curious why you seem to suggest this is more error prone?

If you include a copy of the key in the JWT, it makes it more likely that an implementor will take the key from the JWT and evaluate the signature in isolation, ignore evaluating the trust relationship pf the key (if persistent such as part of the client registration) or binding (if ephemeral).

from draft-dpop.

b---c avatar b---c commented on September 15, 2024

more likely that an implementor will take the key from the JWT and evaluate the signature in isolation

fair speculation

from draft-dpop.

danielfett avatar danielfett commented on September 15, 2024

I like the idea of identifying the key in the proof/binding JWTs using a new jkt#S256 header. The JWK can then be sent in a header or via other mechanisms.

In the HTTP headers, we should distinguish between DPoP-Proof and DPoP-Binding to enable rotation.

Therefore, we would have something like this:

Token Request with Code

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
DPoP-Key: <JWK-1> ...
DPoP-Binding: <Proof-1> ...

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIAWTF
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
&client_id=someclientidhere

with

JWK-1:

{
          "kty":"EC",
          "crv":"P-256",
          "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
          "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
}

and Proof:

{
    "typ": "dpop+jwt",
    "alg": "ES256",
    "jkt#S256": <thumbprint of JWK-1>
}.{
    "jti": "HK2PmfnHKwXP",
    "http_method": "POST",
    "http_uri": "https://server.example.com/token",
    "exp": 1555555555,
}

signed using JWK-1.

  • If JWK-1 is known to the server through an external mechanism, the DPoP-Key header can be omitted.
  • Tokens issued at the token endpoint are always bound to the key for which the client has proven ownership in the DPoP-Binding header.

Resource Access

POST /resource HTTP/1.1
Host: resource-server.example.com
Content-Type: application/json
DPoP-Proof: <Proof-2> ...
Authorization: Bearer <AT>

{ ... }

where the AT would contain (or reference via introspection) a cnf claim with a jwk member with the JWK from the previous step. Something like:

   {"typ":"at+JWT","alg":"RS256","kid":"some AS key"}.
   {
     "sub": "danielfett",
     "exp": 1555555655,
     "client_id": "someclientidhere",
     ... etc ...,
     "cnf" : { 
       "jwk" : {
          "kty":"EC",
          "crv":"P-256",
          "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
          "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
       }
     } 
   }

Proof-2 is just a regular proof for the given request:

{
    "typ": "dpop+jwt",
    "alg": "ES256",
    "jkt#S256": <thumbprint of JWK-1>
}.{
    "jti": "_0accESWLTCbwc",
    "http_method": "POST",
    "http_uri": "https://resource-server.example.com/resource",
    "exp": 1555555559,
}

Token Request with Refresh Token

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
DPoP-Proof: <Proof-3> ...
DPoP-Binding: <Proof-4> ...
DPoP-Key: <JWK-2> ...

grant_type=authorization_code
&refresh_token=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
&client_id=someclientidhere

Proof-3 is again a regular proof for the given request:

{
    "typ": "dpop+jwt",
    "alg": "ES256",
    "jkt#S256": <thumbprint of JWK-1>
}.{
    "jti": "cc1GmfnYwi16",
    "http_method": "POST",
    "http_uri": "https://server.example.com/token",
    "exp": 1555555660,
}

JWK-2 is a new JWK (not known to the server yet).

Proof-4 is a proof for that JWK:

{
    "typ": "dpop+jwt",
    "alg": "ES256",
    "jkt#S256": <thumbprint of JWK-2>
}.{
    "jti": "2bwsGmfnYwi6",
    "http_method": "POST",
    "http_uri": "https://server.example.com/token",
    "exp": 1555555660,
}
  • DPoP-Binding and DPoP-Key can be omitted when refreshing tokens; the previous binding is then re-used, i.e., tokens are bound to JWK-1.

✓ Same format for binding and proof
✓ Support for key rotation
✓ Key is always externalized
✓ Compatible with externally exchanged keys
✓ Not using cnf in the proof/binding JWTs any longer

from draft-dpop.

b---c avatar b---c commented on September 15, 2024

Again, I think we should eliminate the distinction between proof and binding. They are the same thing. Eventually, if this work gets accepted and progresses in the IETF, it will have to register the http headers and I suspect it will be difficult to argue that two different headers are needed that are basically the same thing.

It could even just be called DPoP.

I question the need for supporting for key rotation on refresh. Or at least would prefer that we settle on the basic working model before trying to tackle that.

from draft-dpop.

panva avatar panva commented on September 15, 2024

I think we should eliminate the distinction between proof and binding.

But can we combine the two while still allowing for DPoP key rotation?

from draft-dpop.

b---c avatar b---c commented on September 15, 2024

can we combine the two while still allowing for DPoP key rotation?

Sure, the could be multiple occurrences of the DPoP/JWK headers to facilitate the case of DPoP key rotation on refresh.

from draft-dpop.

b---c avatar b---c commented on September 15, 2024

In the context of pointing to the key from the proof token, using kid and describing how it's used is probably sufficient. If it's even needed.

from draft-dpop.

danielfett avatar danielfett commented on September 15, 2024

In the context of pointing to the key from the proof token, using kid and describing how it's used is probably sufficient. If it's even needed.

"describing how it's used" as in "proof" vs. "binding"?

from draft-dpop.

b---c avatar b---c commented on September 15, 2024

"describing how it's used" as in "proof" vs. "binding"?

No, sorry if I'm not being very clear. In the previous examples there was something like "jkt#S256": <thumbprint of JWK> in the proof JWT header. Which is a pointer to the key that was used to sign it (or to the public key that can verify). What I was saying is that in that context you probably don't need a new header (which would need to be defined and registered) but could rather just use the existing kid header and say that in the given application its value is a hash (or truncated hash even) of the JWK thumbprint of the JWK that signed the token.

Such a pointer arguably isn't even needed as the key to use is implied most/all of the time. But something like it might be useful when the key is referenceable in a client's registeredjwks or jwks_uri rather than an ephemeral(ish) key sent with the request. Or to differentiate when there are multiple potential keys.

from draft-dpop.

panva avatar panva commented on September 15, 2024

Sure, the could be multiple occurrences of the DPoP/JWK headers to facilitate the case of DPoP key rotation on refresh.

This works but might be confusing for implementers. I don't think order can be guaranteed when duped headers get foo|, |foo concatenated by most web frameworks i've used in the past.

from draft-dpop.

danielfett avatar danielfett commented on September 15, 2024

No, sorry if I'm not being very clear. In the previous examples there was something like "jkt#S256": <thumbprint of JWK> in the proof JWT header. Which is a pointer to the key that was used to sign it (or to the public key that can verify). What I was saying is that in that context you probably don't need a new header (which would need to be defined and registered) but could rather just use the existing kid header and say that in the given application its value is a hash (or truncated hash even) of the JWK thumbprint of the JWK that signed the token.

If we want to use the thumbprint to refer to the key in the proof, I would still go for jkt#S256 to make very clear what the contents of that header field are supposed to be. We further avoid confusion between kids that are hashes of the keys and kids as provided in a JWKs (that are not necessarily hashes of the respective keys). Making this distinction clear avoids implementation errors.

But, we might just settle for kid to refer to the key in the proof and reference whatever key IDs are provided by the client.

Thoughts?
(Keep in mind, we are only talking about the proof now, not the cnf in the token.)

from draft-dpop.

danielfett avatar danielfett commented on September 15, 2024

I question the need for supporting for key rotation on refresh.

Let's discuss this in Issue #40

from draft-dpop.

b---c avatar b---c commented on September 15, 2024

Thoughts?

Agreed that making the distinction clear would help avoid implementation errors.

However, I also think that simplification and consistency (to the extent possible) will do more to avoid implementation errors than most anything else. As such I'd revisit the model with that in mind and suggest something like this:

All requests (token endpoint and protected resource) from the client would have a DPoP HTTP header with a value that is a JWT (with the claims we've more or less already established pending the outcome of questions like exp vs. iat). The JWT header would have a jwk JWS Header Parameter (https://tools.ietf.org/html/rfc7515#section-4.1.3) that carries the public potion of the key that was used to sign the JWT and about which proof is being presented regarding the possession of the associated private key. Thus the DPoP HTTP header delivers the public key and proves possession of the corresponding private key (with the caveats of what proof means in this context).

Access tokens would be bound to the dpop key with (or reference via introspection) a cnf claim with a jkt#S256 member that has the no-padded-base64url-encoded SHA256 hash of the RFC7638 JWK thumbprint. Protected resources would verify that binding.

Refresh tokens issued to public clients would also be bound to the dpop key.

from draft-dpop.

danielfett avatar danielfett commented on September 15, 2024

I like the proposal by @b---c and agree that simplification will prevent errors. Version -02 of the draft will contain what Brian suggested.

from draft-dpop.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.