Change:
Updates a number of syntacitcal and semantic requirements for key proofing mechanisms and makes the language more generic around the use of the mechanisms in the protocol and its extensions. change "htu" claim to "uri" for JOSE-based methods, - align token hash with latest dpop 'ath' claim, - use body hash for detached JWS with body, - update HTTP message signature syntax, - include verification language,
~At this time, cryptographic and encoded content of examples has not been regenerated and recalculated.~
Deploy preview for gnap-core-protocol-editors-draft ready! Built with commit 89cb898b4b5275f44b3daf4edd8a15731895469c https://deploy-preview-250--gnap-core-protocol-editors-URL
Cryptographic examples have all been updated.
The security considerations should discuss the differences between PKI-based (with a CA) and non-PKI keys. This is especially relevant to MTLS signing as it changes how the server-side of the TLS connection is deployed.
See discussion on different deployments at: URL
Addressed by security considerations: URL
Key proof sections do not currently detail how to verify each method, including requirements for verifying fields, this needs to be added.
DPoP defines as not containing path or query components, GNAP's JOSE methods use the full URL. The JOSE methods should move to a different claim name to avoid confusion with DPoP.
See comments at URL
Doesn't make sense to me: JOSE is published as an RFC and is well established, so this sounds like an error in DPoP - which should move away from ... What am I missing?
We don't have control over DPoP's claims, those are defined by the OAuth WG. If we continue to keep DPoP signing in GNAP, we will use what they define as much as we can. JOSE as a signing suite is published as an RFC, GNAP is profiling JOSE (specifically JWS) to have a simple detached HTTP message signature method.
The HTTP Message Signatures draft has changed formats, our examples need to be updated to incorporate these changes.
There have been several questions about the parameter currently used to convey the hash of an access token in requests to the RS. Right now we use the definition from OpenID Connect: URL In speaking with the DPoP editors I've proposed a simplified claim there: URL It's functionally similar but doesn't require the array truncation step, and it also fixes to a single hash algorithm (SHA-256) instead of having to figure out which hash to use based on the external signing envelope. I think this change makes it both simpler to implement and to understand.
Also, for cryptographic agility here we would rely on defining a new claim name and updating the requirements about inclusion. So would always be SHA-256 and once that hash is sufficiently broken and discarded we would define a new claim like for whatever new target there is.
+1 for that, much more intuitive than truncating (and the comment from DPoP editors seem to go in that direction too). In relation to what would happen if SHA-256 gets deprecated: the current method is already based on SHA-256, so it's not a new downside an idea would be to name it for instance, to give a minimal versioning (would currently relate to SHA2) and would simplify changes later on (like avoiding ;-))
I personally think that naming the next version of the field should be left to the next version. Someone might want to specify or even , but the important thing is that we aren't defining plus parameters, we're just defining and anything else comes along as its own unique string. That kind of guidance can be written around the registry for the claim and how it'd get used.
works too
Reopening this issue: we are not providing cryto-agility, because: We do not indicate how the algorithm could change. Renaming the field to would remedy that. An implementation that did not include would formally be non-compliant, because the field is REQUIRED. We should say something about that. See URL
NAME crypto agility would be provided by defining a new field entirely. The algorithm will not change for . This was discussed in the OAuth WG with DPoP's field which has the same semantics as this. I do not recommend we re-open this.
I'm not asking to reopen it. Just to add a note in the text. The IESG reviewers are bound to comment on this.
As I was working on a client implementation, it occurred to me that it would be a lot simpler and less error prone to include a hash of the request body in the JWS rather than doing the current "compute-the-JWS-then-remove-the-body" method. This would remove the and fields from the JWS header, and would add a new sentence describing how to calculate the body hash, dropping the dependency on RFC7797: This significantly cleans up the client code, since previously the client would have to generate the JWS, the pull out the header and signature components and reassemble them with an empty body, creating a string like "XXXXX..XXXXX". Now it can use a JWS library without any additional hacks on top.
Such JWS header wouldn't qualify RFC7797 in this case then.
how so?
RFC 7797 requires you to include "b64" in the "crit" header parameter and set the "b64" header parameter to . Lots of existing JWT parsers obey the "crit" parameter. As you mention, you may remove these parameters. Although, it will result in an inability to parse such JWTs by parsers that already support RFC 7797.
Some of the existing parsers provide a convenient way to get "XXXXX..XXXXX" without any hacks on top. For example, take a look at method in URL
Not relying on the additional rules specified in RFC7797 seems like a benefit, less specs to read and less edge cases to handle. Removing a dependency on another spec is a benefit.
Right, these wouldn't be RFC7797 values anymore, they would just be plain JWS objects with a specially calculated payload. We still will have the "XXXX..XXXX" format for payload-less requests (requests without the body, such as GET and DELETE).
I think I'm in favor of this idea, personally. It still fulfills the requirement of covering the message body with the signature method, but it does so in a way that's perhaps more native to most JOSE developers. It's also more conducive to the kinds of deployment models discussed in
I hear you. I would like to add & emphasize that there are two parts to this story. The first part where we create JWTs and accordingly, there is the second part where we verify them. The beauty is that there are lots of libraries that support RFC 7797. The rest of the decently implemented libraries JWTs because they don't know how to proceed with b64 in the crit header parameter and it's really great IMO. It reduces the chances of errors. Now, let’s imagine we omitted b64 from the crit header parameter and started to put the SHA-256 hash in the payload of the JWS object. In this case, the verification process should fail by all existing libraries, right? If you decide to put something like “gnap-detached” in the crit header parameter then you will also need to change the code of a JWT library so this library will be able to process it. I am guessing that isn't always possible, rational, or just easy to do.
The body of this new method would be a base64 encoded hash value, so we actually don't :want: the header at all and we wouldn't be doing RFC7797 here. One thing that's different from what many libraries may expect is that the body doesn't decode into JSON -- but this was always a JWS, not a JWT. Just about every JWS library I've used, these days, allows for an arbitrary payload. Even URL allows for signing an arbitrary string for the payload, but it will base64 encode it first. I think this new approach benefits from not requiring any parameter, enforcement of which I've found hit-or-miss at best in the JOSE community.
NAME will you require the "typ" header parameter to be presented in such JWSs... with the values like "gnap+jwsd" and "gnap+jws"?
The Proving Possession of a Key with a Request section explicitly states that Unfortunately, I don't see how a client can make use of the jws param (Attached JWS payload) to make calls to "regular" RSs. Normally, they won't be ready to accept the "application/jose" content type and the corresponding payload for the endpoint that accepts say "application/json" and "application/xml". I also believe that this specification shouldn't require the payload (HTTP Body) to be included in the calculation of the JWS signature for RSs in case of Detached JWS payload. The reason is that the client (or any logic that validates access tokens) doesn't always have access to the payload and the bright example for this would be NGinx and . Note, this approach isn't limited in any way to NGinx and is extremely popular among various cloud providers. There are also other minor reasons say just huge payloads (like a movie upload) or even a real-time stream (WebRTC) and so on and so forth. Both these items are totally fine and definitely should be required for any calls from a client to an AS though.
URL says this about the payload and HTTP headers and
Attached JWS is highly problematic and limited for exactly the reason you describe: it requires the API the client is calling to accept a JOSE encoded payload instead of its native payload (see ). Plus, this only works for requests with a message body (POST/PUT) so you need to fall back to the detached method for other verbs, and it's really only mostly applicable for JSON payloads though technically you can serialize anything in a JWS so that's just more it feeling "weird" than being wrong. I'm personally not a fan of the method for all these reasons, but several members of the WG have stated that it fits their use cases better than other methods. Inclusion of the body (when present) in the detached JWS request allows it to be validated along with the signature in the reset of the request. A library outsourcing GNAP with something like an auth_request module would need to include the signature calculation in that module, like here: URL An AS is just as likely to be behind a security layer proxy, either externalized as you describe or internal to the app like in Java's servlet filter system. In any event, to handle body integrity you're going to have to handle this as part of the filtering system, like here: URL ... and to me, it's really not any dissimilar to TLS reverse proxies. As for DPoP, note that we're only using the signature method of DPoP here, not the whole protocol extension in its context. The restrictions and guidance you've quoted come from the fact that DPoP is targeted specifically at SPA's running OAuth 2, and so the advice does not apply here.
The changes to get the whole request body in such externalized components in the Kubernetes (and its friends) world are very slim as that somehow defeats the benefits of their use. Note, these components don't act as a proxy. They receive all or only certain headers of an HTTP request, do their own business, and then finally tells whether access is granted. If granted, the whole initial HTTP request gets a couple of new headers like X-Auth-User-* and then forwarded to the right service to be processed further. If not, a caller gets 401 or an HTTP redirect, etc. URL is another example of a component that does pretty much the same as NGinx's auth_request module. There is a bunch of other similar components. I do like that GNAP requires "body" integrity for all interactions between a client and an AS. It feels for me it should be optional for interactions between a client and an RS.
This is where things like the HTTP Message Signatures could help, since the body hash is separate from the signing. The client can calculate the body Digest header and pass that along to the signing component. It could also be that the security components get redesigned to accommodate the security protocols. We already saw that shift when API libraries started handling access tokens instead of just HTTP Basic username/password combos, which is all they used to do. Or when browsers adopted the fetch() API as opposed to the deeply limited capabilities that were there before. Now, I'm not saying that GNAP should count on the world bending to its will, or that we should ignore deployment patterns; but we also shouldn't limit the protocol based on a design choice in software that makes sense today and might not in the future. These libraries only look at a couple headers because that's all they need to do right now, much like in the past they would have just taken in a username and password.
These external components don't really have a choice to read only certain headers or the whole request. They get only what they get from ingress controllers in a cluster. It's very possible that in the future, these controllers (like NGinx or Traefic) will have to provide the SHA-256 hash of the request's payload in order to accommodate the security protocols as you said. It's hard to predict when and if it will happen at all. As of today, this is a very common and extremely popular deployment model.
The approach proposed in should address these concerns by and large, and this issue will close with
Looks fine, but tbh I must test to be sure

Discussion:
BCP 14 RFC2119 RFC8174 when, and only when, they appear in all capitals, as shown here. <ins> This document contains non-normative examples of partial and complete HTTP messages.  Some examples use a single trailing backslash '' to indicate line wrapping for long values, as per RFC8792.  The "" character and leading spaces on wrapped lines are not part of the value. </ins> 1.2. The parties in GNAP perform actions under different roles.  Roles are

All key binding methods used by this specification MUST cover all relevant portions of the request, including anything that would change the nature of the request, to allow for secure validation of <del> the request by the AS.  Relevant aspects include the URI being called, the HTTP method being used, any relevant HTTP headers and values, and the HTTP message body itself.  The recipient of the signed message MUST validate all components of the signed message to ensure that nothing has been tampered with or substituted in a way that would change the nature of the request. When a key proofing mechanism is bound to an access token, the access token MUST be covered by the signature method of the proofing mechanism. </del> <ins> the request.  Relevant aspects include the URI being called, the HTTP method being used, any relevant HTTP headers and values, and the HTTP message body itself.  The verifier of the signed message MUST validate all components of the signed message to ensure that nothing has been tampered with or substituted in a way that would change the nature of the request.  Key binding method definitions SHOULD enumerate how these requirements are fulfilled. When a key proofing mechanism is bound to an access token, the key being presented MUST be the key associated with the access token and the access token MUST be covered by the signature method of the proofing mechanism. The key binding methods in this section MAY be used by other components making calls as part of GNAP, such as the extensions allowing the RS to make calls to the AS defined in {{I-D.ietf-gnap- resource-servers}}. To facilitate this extended use, the sections below are defined in generic terms of the "sender" and "verifier" of the HTTP message.  In the core functions of GNAP, the "sender" is the client instance and the "verifier" is the AS or RS, as appropriate. </ins> When used for delegation in GNAP, these key binding mechanisms allow the AS to ensure that the keys presented by the client instance in

[[ See issue #105 [48] ]] <ins> In the following sections, unless otherwise noted, the "RS256" JOSE Signature Algorithm is applied using the following RSA key (presented here in JWK format): </ins> 7.3.1. This method is indicated by "jwsd" in the "proof" field.  A JWS RFC7515 object is created as follows: <del> The JOSE (JSON Object Signing and Encryption) header MUST contain the "kid" parameter of the key bound to this client instance for this request.  The "alg" parameter MUST be set to a value appropriate for the key identified by kid and MUST NOT be "none".  The "b64" parameter MUST be set to "false" and the "crit" parameter MUST contain at least "b64" as specified in RFC7797. </del> <ins> To protect the request, the JOSE header of the signature contains the following parameters: </ins> <del> To protect the request, the JOSE header MUST contain the following additional parameters. </del> <ins> The key identifier.  RECOMMENDED.  If the key is presented in JWK format, this MUST be the value of the "kid" field of the key. </ins> <del> The type header, value "gnap-binding+jwsd". </del> <ins> The algorithm used to sign the request.  REQUIRED.  MUST be appropriate to the key presented.  If the key is presented as a JWK, this MUST be equal to the "alg" parameter of the key.  MUST NOT be "none". The type header, value "gnap-binding+jwsd".  REQUIRED </ins> The HTTP Method used to make this request, as an uppercase ASCII <del> string. </del> <ins> string.  REQUIRED </ins> The HTTP URI used for this request, including all path and query <del> components. </del> <ins> components and no fragment component.  REQUIRED </ins> <del> A timestamp of the request in integer seconds </del> <ins> A timestamp of when the signature was created, in integer seconds since UNIX Epoch </ins> When a request is bound to an access token, the access token hash <del> value.  Its value is the base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the "access_token" value, where the hash algorithm used is the hash algorithm used in the "alg" header parameter of the JWS's JOSE Header.  For instance, if the "alg" is "RS256", hash the "access_token" value with SHA-256, then take the left-most 128 bits and base64url encode them. </del> <ins> value.  The value MUST be the result of Base64url encoding (with no padding) the SHA-256 digest of the ASCII encoding of the associated access token's value.  REQUIRED if the request protects an access token. </ins> <del> The payload of the JWS object is the serialized body of the request, and the object is signed according to detached JWS RFC7797. </del> <ins> If the HTTP request has a message body, such as an HTTP POST or PUT method, the payload of the JWS object is the Base64url encoding (without padding) of the SHA256 digest of the bytes of the body.  If the request being made does not have a message body, such as an HTTP GET, OPTIONS, or DELETE method, the JWS signature is calculated over an empty payload. </ins> <del> The client instance presents the signature in the Detached-JWS HTTP Header field. </del> <ins> The client instance presents the signed object in compact form RFC7515 in the Detached-JWS HTTP Header field. </ins> <del> If the request being made does not have a message body, such as an HTTP GET, OPTIONS, or DELETE method, the JWS signature is calculated over an empty payload. </del> <ins> In this example, the JOSE Header contains the following parameters: The request body is the following JSON object: </ins> <del> When the server (AS or RS) receives the Detached-JWS header, it MUST parse its contents as a detached JWS object.  The HTTP Body is used as the payload for purposes of validating the JWS, with no transformations. </del> <ins> This is hashed to the following Base64 encoded value: This leads to the following full HTTP request message: When the verifier receives the Detached-JWS header, it MUST parse and validate the JWS object.  The signature MUST be validated against the expected key of the signer.  All required fields MUST be present and their values MUST be valid.  If the HTTP message request contains a body, the verifier MUST calculate the hash of body just as the signer does, with no normalization or transformation of the request. </ins> 7.3.2.

string. The HTTP URI used for this request, including all path and query <del> components. </del> <ins> components and no fragment component. </ins> <del> A timestamp of the request in integer seconds </del> <ins> A timestamp of when the signature was created, in integer seconds since UNIX Epoch </ins> When a request is bound to an access token, the access token hash <del> value.  Its value is the base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the "access_token" value, where the hash algorithm used is the hash algorithm used in the "alg" header parameter of the JWS's JOSE Header.  For instance, if the "alg" is "RS256", hash the "access_token" value with SHA-256, then take the left-most 128 bits and base64url encode them. The payload of the JWS object is the JSON serialized body of the request, and the object is signed according to JWS and serialized into compact form RFC7515. The client instance presents the JWS as the body of the request along with a content type of "application/jose".  The AS MUST extract the payload of the JWS and treat it as the request body for further processing. This example's JWS header decodes to: And the JWS body decodes to: </del> <ins> value.  The value MUST be the result of Base64url encoding (with no padding) the SHA-256 digest of the ASCII encoding of the associated access token's value. If the HTTP request has a message body, such as an HTTP POST or PUT method, the payload of the JWS object is the JSON serialized body of the request, and the object is signed according to JWS and serialized into compact form RFC7515.  The client instance presents the JWS as the body of the request along with a content type of "application/ jose".  The AS MUST extract the payload of the JWS and treat it as the request body for further processing. </ins> If the request being made does not have a message body, such as an HTTP GET, OPTIONS, or DELETE method, the JWS signature is calculated over an empty payload and passed in the "Detached-JWS" header as described in detached-jws. <ins> In this example, the JOSE header contains the following parameters: The request body, used as the JWS Payload, is the following JSON object: This leads to the following full HTTP request message: </ins> [[ See issue #109 [49] ]] <ins> When the verifier receives an attached JWS request, it MUST parse and validate the JWS object.  The signature MUST be validated against the expected key of the signer.  All required fields MUST be present and their values MUST be valid.  If the HTTP message request contains a body, the verifier MUST decode the payload of the JWS object and treat this as the HTTP message body. </ins> 7.3.3. <del> This method is indicated by "mtls" in the "proof" field.  The client instance presents its TLS client certificate during TLS negotiation with the server (either AS or RS).  The AS or RS takes the thumbprint of the TLS client certificate presented during mutual TLS negotiation and compares that thumbprint to the thumbprint presented by the client instance application as described in RFC8705 section 3. </del> <ins> This method is indicated by "mtls" in the "proof" field.  The signer presents its TLS client certificate during TLS negotiation with the verifier. In this example, the certificate is communicated to the application through the "Client-Cert" header from a TLS reverse proxy, leading to the following full HTTP request message: The verifier compares the TLS client certificate presented during mutual TLS negotiation to the expected key of the signer.  Since the TLS connection covers the entire message, there are no additional requirements to check. Note that in many instances, the verifier will not do a full certificate chain validation of the presented TLS client certificate, as the means of trust for this certificate could be in something other than a PKI system, such as a static registration or trust-on- first-use. </ins> [[ See issue #110 [50] ]] 7.3.4. <del> This method is indicated by "dpop" in the "proof" field.  The client instance creates a Demonstration of Proof-of-Possession signature header as described in I-D.ietf-oauth-dpop section 2.  In addition, this specification defines the following fields to be added to the DPoP payload: </del> <ins> This method is indicated by "dpop" in the "proof" field.  The signer creates a Demonstration of Proof-of-Possession signature header as described in I-D.ietf-oauth-dpop section 2.  In addition, this specification defines the following fields to be added to the DPoP payload: </ins> Digest of the request body as the value of the Digest header defined in RFC3230.  When a request contains a message body, such as a POST or PUT request, this field is REQUIRED. <del> When a request is bound to an access token, the access token hash value.  Its value is the base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the "access_token" value, where the hash algorithm used is the hash algorithm used in the "alg" header parameter of the JWS's JOSE Header.  For instance, if the "alg" is "RS256", hash the "access_token" value with SHA-256, then take the left-most 128 bits and base64url encode them. </del> <ins> In this example, the request body is the following JSON object: ~~~ { "access_token": { "access": [ "dolphin-metadata" ] }, "interact": { "start": ["redirect"], "finish": { "method": "redirect", "uri": "https://client.foo/callback", "nonce": "VJLO6A4CAYLBXHTR0KRO" } }, "client": { "proof": "dpop", "key": { "jwk": { "kid": "gnap-rsa", "kty": "RSA", "e": "AQAB", "alg": "RS256", "n": "hYOJ- XOKISdMMShn_G4W9m20mT0VWtQBsmBBkI2cmRt4Ai8Bf YdHsFzAtYKOjpBR1RpKpJmVKxIGNy0g6Z3ad2XYsh8KowlyVy8IkZ8NMwSrcUIBZG YXjHpwjzvfGvXH_5KJlnR3_uRUp4Z4Ujk2bCaKegDn11V2vxE41hqaPUnhRZxe0jR ETddzsE3mu1SK8dTCROjwUl14mUNo8iTrTm4n0qDadz8BkPo-uv4BC0bunS0K3bA_ 3UgVp7zBlQFoFnLTO2uWp_muLEWGl67gBq9MO3brKXfGhi3kOzywzwPTuq-cVQDyE N7aL0SxCb3Hc4IdqDaMg8qHUyObpPitDQ" } } "display": { "name": "My Client Display Name", "uri": "https://client.foo/" }, } } ~~~ The JOSE header contains the following parameters, including the public key: The JWS Payload contains the following JWT claims, including a hash of the body: This results in the following full HTTP message request: The verifier MUST parse and validate the DPoP proof header as defined in I-D.ietf-oauth-dpop.  If the HTTP message request includes a message body, the verifier MUST calculate the digest of the body and compare it to the "htd" value.  The verifier MUST ensure the key presented in the DPoP proof header is the same as the expected key of the signer. </ins> 7.3.5. This method is indicated by "httpsig" in the "proof" field.  The <del> client instance creates an HTTP Signature header as described in I- D.ietf-httpbis-message-signatures section 4.  The client instance MUST calculate and present the Digest header as defined in RFC3230 and include this header in the signature. </del> <ins> sender creates an HTTP Message Signature as described in I-D.ietf- httpbis-message-signatures. The covered content of the signature MUST include the following: the target of the HTTP request The Digest header as defined in RFC3230.  When the request message has a body, the signer MUST calculate this header value and the verifier MUST validate this header. When the request is bound to an access token, the covered content MUST also include: The Authorization header used to present the access token as discussed in use-access-token. Other covered content MAY also be included. </ins> <del> When used to present an access token as in use-access-token, the Authorization header MUST be included in the signature. </del> <ins> If the signer's key presented is a JWK, the "keyid" parameter of the signature MUST be set to the "kid" value of the JWK, the signing algorithm used MUST be the JWS algorithm denoted by the key's "alg" field, and the explicit "alg" signature parameter MUST NOT be included. In this example, the message body is the following JSON object: This body is hashed for the Digest header using SHA-256 into the following encoded value: The HTTP message signature input string is calculated to be the following: This leads to the following full HTTP message request: If the HTTP Message includes a message body, the verifier MUST calculate and verify the value of the "Digest" header.  The verifier MUST ensure that the signature includes all required covered content. The verifier MUST validate the signature against the expected key of the signer. </ins> 7.3.6. This method is indicated by "oauthpop" in the "proof" field.  The <del> client instance creates an HTTP Authorization PoP header as described in I-D.ietf-oauth-signed-http-request section 4, with the following </del> <ins> signer creates an HTTP Authorization PoP header as described in I- D.ietf-oauth-signed-http-request section 4, with the following </ins> additional requirements: The "at" (access token) field MUST be omitted unless this method is being used in conjunction with an access token as in use- access-token.  [[ See issue #112 [51] ]] <del> The "b" (body hash) field MUST be calculated and supplied, unless there is no entity body (such as a GET, OPTIONS, or DELETE request). </del> <ins> The "b" (body hash) field MUST be calculated and included, if the message includes an entity body (such as a PUT or PATCH request). </ins> <del> All components of the URL MUST be calculated and supplied </del> <ins> All components of the URL MUST be calculated and included, including query parameters "q", path "p", and host "u". </ins> <del> The m (method) field MUST be supplied </del> <ins> The "m" (method) field MUST be included In this example, the request message body is the following JSON object The JOSE header for the PoP token is the following: After calculating the hashes for the body, headers, and URL components, the JWS Payload is the following: This leads to the following HTTP message request: </ins> [[ See issue #113 [52] ]] <ins> The verifier MUST parse the JWS object of the PoP header.  The verifier MUST validate the signature of the header against the expected key of the signer.  The verifier MUST ensure that all required parts of the message are covered by the signature.  The verifier MUST ensure that all components of the signature contain correct values. </ins> 8. GNAP provides a rich structure for describing the protected resources