Network Working Group | A. Rundgren |
Internet-Draft | Independent |
Intended status: Informational | September 9, 2019 |
Expires: March 12, 2020 |
Signed HTTP Requests (SHREQ)
draft-rundgren-signed-http-requests-01
The SHREQ specification describes how the JSON Web Signature (JWS) specification combined with the JSON Canonicalization Scheme (JCS), can be utilized to support HTTP based applications needing digitally signed requests. SHREQ is specifically tailored for Web applications using JSON as data interchange format. There is also a SHREQ scheme for HTTP requests that do not have a body ("payload") like GET. SHREQ was designed to be agnostic with respect to REST concepts versus traditional GET/POST schemes.
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."
This Internet-Draft will expire on March 12, 2020.
Copyright (c) 2019 IETF Trust and the persons identified as the document authors. All rights reserved.
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Simplified BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Simplified BSD License.
Currently there is no standard for digitally signing HTTP [RFC7230] [RFC7231] requests. This has led to the development of a multitude of more or less proprietary solutions (see Appendix B), typically building on using HTTP header data for holding security constructs, while JSON request data is provided in clear in the HTTP body.
SHREQ is intended to provide a standardized alternative, including supporting the REST [REST] concept.
SHREQ builds on a common security model where all elements of an HTTP request are signed:
In addition there is a mandatory time stamp.
One of the design goals was turning signed requests into self-contained objects. To achieve this for HTTP requests having a JSON [RFC8259] body (see Section 4), the request data also carries the signature. This arrangement has certain implications:
For HTTP requests that do not have a JSON body (see Section 5), the signature and additional request data is added to the original URI [RFC3986], making signed URI-only requests self-contained and serializable as well. For simplicity such requests are (in this specification NB), referred to as URI based requests.
Both variants utilize JWS [RFC7515] for holding the signature data.
For supporting signed HTTP responses any solution may be used. For maximum "symmetry" and code reuse, the [JWSJCS] scheme should be a suitable candidate since it builds on the same building blocks as SHREQ.
The intended audiences of this document are Web tool vendors, as well as designers of secure Web applications.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.
The following subsections describe HTTP specifics associated with this specification.
In this specification the distinction between HTTP requests having a JSON body or not is based on the presence of a "Content-Length" header. Requests without a body object are in this specification referred to as URI based requests.
This also implies that not all header combinations permitted by HTTP can be used with this specification:
This specification utilizes a single HTTP return code 400 (Bad request) for indicating syntax or security errors. Since the number of possible error conditions is significant, it is RECOMMENDED to accompany the error code with a short explanation in "text/plain" format in the HTTP Body like:
- Missing ".secinf" element - Invalid "alg": es256 - Unknown "kid": example.com:rsa:2018.1 - com.example.jose.Core.validate(2653): Signature validation error - Missing header variable "x-testing"
Communities using this specification MAY customize error codes if needed. However, in practice, it usually turns out to be of little value compared to a text message and a generic "hard error" code since neither users nor machines can do very much on their own to fix errors that are outside of normal processing.
Application level errors are dealt with in an application specific manner. As an example a bank application which finds out that the customer do not have enough funds to perform a transaction would presumably not return an HTTP error code but rather a specifically crafted error message to be displayed to the user.
Return codes for successful operation are application specific but are typically 200 (OK) or 201 (Created).
Assume there is an unsigned HTTP request like the following:
POST /foo HTTP/1.1 Host: example.com Content-Type: application/json Content-Length: 1234 { "something": "data", Additional application specific properties }
Adding a signature to the request above would require the following enhancements to the JSON payload:
{ "something": "data", Additional application specific properties ".secinf": { "uri": "https://example.com/foo", "mtd": "POST", "iat": 1551709923, "jws": "eyJhbGciOiJIUzI1NiJ9..VHVItCBCb849imarDtjw4" } }
Notes:
The following subsections detail the operation for requests having an HTTP body.
Precondition: the application data to be submitted with the request already exists in a format serializable as a JSON Object, from now on referred to as "message".
In order to create a valid signed JSON request, the following steps (and ordering) MUST be adhered to:
In order to validate a request the following steps MUST be performed:
Note: validation of application specific data can be performed anytime after step 6. The action(s) to perform after a possible failure is out of scope for this specification (see Section 3.2).
Assume there is an unsigned HTTP request like the following:
GET /users?id=435 HTTP/1.1 Host: example.com
The full URI would be as follows:
https://example.com/users?id=435
Adding a signature to this request according to this specification would return the following URI:
https://example.com/users?id=435&.jws=eyJhhiJ.eyJ7fgw.VHVIt
Notes:
The middle component of the JWS string ("JWS Payload"), contains Base64Url encoded signed data related to the request. It should (after Base64Url decoding) yield a JSON Object like the following:
{ "htu": "WUjqfXPztLzzXRCs6EcWCw-GC9hSL7hwCR1nG2FSvQ8", "mtd": "GET", "iat": 1551863696 }
Notes:
The following subsections detail the operation for requests using an HTTP query string component for holding a signature.
In order to create a valid signed URI request, the following steps (and ordering) MUST be adhered to:
In addition to normal validation of received data (which may be carried out before or after the steps outlined here), the following steps MUST be performed in order to validate a URI based HTTP request:
Note: validation of application specific data can be performed anytime. The action(s) to perform after a possible failure is out of scope for this specification (see Section 3.2).
This specification builds on a modular scheme using common procedures described in the following subsections.
Unsigned request data is now supposed to reside in "message". To facilitate resilience against (legitimate) variances in JSON processing between different platforms and systems, "message" needs to be canonicalized and serialized into a UTF-8 [UNICODE] encoded byte array. If the used JSON tools offer intrinsic support for JCS [JCS], this is typically a single operation, else the followings steps are performed:
The output from JCS represents a "JWS Payload" [RFC7515].
For URI based requests, the steps to create signable URI data are as follows:
The test vectors in Appendix A provide a few examples showing authentic values of the "htu" (Hashed Target URI) property.
To create a digest of headers to be included in a signed request, perform the following operations:
Below is an example of header input data:
x-debug: full Cache-Control: max-age=60, must-revalidate
Applying the process described in this subsection and using the SHA-256 [SHS] hash algorithm should generate the following ".secinf" data:
"hdr": ["Ljzuq8C9PScbvLpBxG8GNOs-WQUd7gl7R64izahhe-0", "x-debug,cache-control"]
Create a "JWS Protected Header" [RFC7515] JSON Object with algorithm and key data adapted for the application. Below is a minimal example:
{ "alg": "ES256" }
To create a compact JWS object (a string), perform the following steps:
The following processing steps presume that there is an input string holding a JWS compact object, here called "jwsString":
If any of the steps above fail, the service MUST reject the request (see Section 3.2).
To facilitate comparison between actual (received) URIs and signed URIs, URIs MUST be normalized according to the following:
The following URI shows a non-normalized URI:
https://EXAMPLE.COM:443/%63EURO%2f
The same URI after normalization:
https://example.com/c%E2%82%AC%2F
[[ This section is still incomplete ]]
Headers to be included in signed requests MUST be normalized. This subsection shows a common procedure for senders and receivers based on Section 3.2.4 of [RFC7230].
Collect received headers or headers to be submitted in a list of header field name and header field value pairs according to the following:
This list is referred to as "headerCollection" in other places in this specification.
Below is an example of header input data:
x-debug: full Cache-control: max-age=60 Cache-Control: must-revalidate
Applying the process described in this subsection should generate the following collection:
|======================================================| | Header Field Name | Header Field Value | |======================================================| | x-debug | full | |------------------------------------------------------| | cache-control | max-age=60, must-revalidate | |------------------------------------------------------|
For interoperability reasons it is RECOMMENDED to not use duplicate header names for headers that are to be signed. Apparently proxy servers do not always honor original header ordering.
To validate a digest of headers in a signed request, perform the following operations:
If any of the steps above fail, the service MUST reject the request (see Section 3.2).
Note that this specification does not enforce any particular ordering of signed header elements.
Validation of the JWS [RFC7515] object, using the previously extracted and decoded objects requires the following steps:
If any of the steps above fail, the service MUST reject the request (see Section 3.2).
Time stamps have the same name ("iat"), format and function as described in JWT [RFC7519], Section 4.1.6. However, in this specification time stamps are REQUIRED, and stored in the ".secinf" JSON Object.
Although JWT permits non-integer values, implementers of this specification SHOULD limit generated time stamp granularity to seconds and use integer representation.
The policy with respect to the difference between the current time and received time stamps is out of scope for this specification. However, for security reasons it is generally a good idea limiting deviations to a few minutes as well as using network based clock synchronization in both ends.
Inclusion of HTTP header elements as well as the "htu" property of URI based requests depends on digests produced by a hash algorithm. The default is using the hash algorithm associated with the JWS signature algorithm ("alg") featured in the "JWS Protected Header". That is, the JWA [RFC7518] algorithms "ES256" and "HS384" imply the hash algorithms SHA-256 and SHA-384 respectively.
In case this is not desired, this specification permits overriding the default by including a "hao" (Hash Algorithm Override) property in the ".secinf" JSON Object. The currently recognized arguments to "hao" are:
Although using the ".secinf" JSON property name and ".jws" query component name is RECOMMENDED, this specification permits (=being considered as compatible), the use of local naming conventions as long as the specified procedures and formats are adhered to.
Local naming conventions MUST be properly communicated in the community using them.
[[
]]
This document currently has no IANA actions but the reserved names below could be candidates for IANA registration:
The hash algorithms defined in Section 6.12 could also benefit from IANA registration.
The purpose of this specification is adding an integrity and authorization layer to HTTP requests. This part is subject to the same security considerations as the underpinning JCS and JWS schemes.
For most applications HTTPS [RFC7231] would be the logical choice, not only for protecting application data from snooping, but also to not unnecessary reveal data about signature keys.
In a cloud scenario with Web servers open for access by any party new security challenges are introduced. Cryptographic solutions protect data but may also add vulnerabilities to denial-of-service attacks since they often need substantial processing.
Protecting against replay attacks is important because replay may actually be a legitimate facility for systems repeating a request due to a communication failure. This cannot be entirely solved by time stamps and cryptography; you usually need unique transactions IDs and data base support as well. For reliable operation there must be common rules within a community using such features. The REST [REST] paradigm also requires such measures due to the idempotent operation specified for "PUT", "GET" and "DELETE.
Parts of this specification were derived from the HTTP signature [HTTPSIG] draft.
[AWS] | Amazon.com, "Signing AWS API Requests" |
[FAPI] | Open ID, "Financial-grade API" |
[HTTPSIG] | M. Cavage, M. Sporny, "Signing HTTP Messages" |
[OBIE] | Open Banking UK, "Open Banking API" |
[REST] | Roy Fielding, "Architectural Styles and the Design of Network-based Software Architectures" |
[STET] | STET, "PSD2 API V1.4.1" |
The following test vectors "activate" all parts of the specification. After removing the line breaks needed for publishing, the test vectors are supposed to be fully validatable.
Target URI:
https://example.com/users/456
Signed URI:
https://example.com/users/456?.jws=eyJhbGciOiJIUzI1NiJ9.eyJodHUi OiJmaVZpNGpZaER0N1ZDdVFJS1VJZFdJTkVXZm9oX05YSGZMVFpORWVTYXZZIiwi aWF0IjoxNTUxOTUxOTAwfQ.Wll5cFEE9sidHs01sADus8kbHNHAC5DCzyytYoAtT 2g
Decoded JWS Payload:
{ "htu": "fiVi4jYhDt7VCuQIKUIdWINEWfoh_NXHfLTZNEeSavY", "iat": 1551951900 }
Symmetric signature validation key, here in hexadecimal notation:
7fdd851a3b9d2dafc5f0d00030e22b9343900cd42ede4948568a4a2ee655291a
Target URI:
https://example.com/users
JSON Body:
{ "name": "John Doe", "profession": "Unknown", ".secinf": { "uri": "https://example.com/users", "iat": 1551951900, "jws": "eyJhbGciOiJFUzI1NiJ9..-N7yuF1TEASo5Ub5q2T1_EkLWrWHs2 nyHjDupkinoRcQbSo8h2ygL9pmGzd_YU4jn_bcMQF8BrTIlSioNel5GQ" } }
Public signature validation key, here in PEM format:
-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcensDzcMEkgiePz6DXB7cDuwFems hAFR90UNVQFCg8TGryvN7p7AbT55VxIXvYnvuAqIPQgefOnAdpTu3qdV5g== -----END PUBLIC KEY-----
Target URI:
https://example.com/users/456
JSON Body:
{ "name": "Jane Smith", "profession": "Hacker", ".secinf": { "uri": "https://example.com/users/456", "mtd": "PUT", "iat": 1551951900, "jws": "eyJhbGciOiJFUzI1NiJ9.._VWTXYcgr6OTCcJg6XZzPkHsLU-jUT T1HoQ92bihMIDlXR7xNfmxlHWSUc9cyFCxzsBy9yq33eFn3fApIH42SA" } }
Public signature validation key, here in PEM format:
-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcensDzcMEkgiePz6DXB7cDuwFems hAFR90UNVQFCg8TGryvN7p7AbT55VxIXvYnvuAqIPQgefOnAdpTu3qdV5g== -----END PUBLIC KEY-----
Target URI:
https://example.com/users/456
Signed URI:
https://example.com/users/456?.jws=eyJhbGciOiJSUzI1NiJ9.eyJodHUi OiI5R3FtRDBSRWRqSDFZNklvSXR3UjdKRURuU0pjVzNuSnhoM085eHQ3Zk1RQ1cy N3FtOEQyWUNtN1h4RzRwU1hwOGJFM3lTT3RzWlhIR0VJSWw1M05jQSIsIm10ZCI6 IkRFTEVURSIsImlhdCI6MTU1MTk1MTkwMCwiaGFvIjoiUzUxMiIsImhkciI6WyIz ZXBrQno4RUJwMUxYX01EdFd1WnFWZjFLYjJyalFNZzE5RjVvT2Fhbk91SVFpS1Z1 SHBrSG5WdWFLMlZZbVZ2bEpOSGxEY2NEeHFxVGQxNFU5VXg5USIsIngtZGVidWci XX0.YRTEbCrOy11c10HcPSDX_DCtl56S5qmcYWFcuG6wqsgg7vnCr22vCDhE1xJL xeM2hp_-gSmdxykJ-Q060xetax-nMmXUhrDtcRoeCfO12-xDTymZTJXtb11SX6Pn mt9CnM4ZOVrJVro7eLW8hCc4p5As7zDS2arNM_-IsWiNJ1T25EDb8ZS7kLLSA6Im lo31o8815kC0oHNI0q-lZeaOX3DhnL1tMJKZQzrItXvmZ0oqJ3kL8bxF6aglOFC0 zOYUU2kciIf55jVcfBgwupecFw-rN56QEg8PzA8YA-nGPWHBpxJUWWseY4qXZudR cQQZtms7Yc1yK7z3fNhht6Oh1A
Decoded JWS Payload:
{ "htu": "9GqmD0REdjH1Y6IoItwR7JEDnSJcW3nJxh3O9xt7fMQCW27qm8D2YC m7XxG4pSXp8bE3ySOtsZXHGEIIl53NcA", "mtd": "DELETE", "iat": 1551951900, "hao": "S512", "hdr": ["3epkBz8EBp1LX_MDtWuZqVf1Kb2rjQMg19F5oOaanOuIQiKVuHpkH nVuaK2VYmVvlJNHlDccDxqqTd14U9Ux9Q", "x-debug"] }
Note the overridden hash algorithm.
Required HTTP Headers:
x-debug: full
Public signature validation key, here in PEM format:
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhFWEXArvaZEpSP5qNX7x 4C4Hl28GJQTNvnDwkfqiWs63kXbdyPeS06bz6GnY3tfQ/093nGauWsimqKBmGAGM PtsV83Qxw1OIeO4ujbIIb9pema0qtVqs0MWlHxklZGFkYfAmbuEUFxYDeLDHe0bk kXbSlB7/t8pCSvc8HLgHjEQjYOlFRwjR0D+uLo+xgsCbpmCtYkB5lcT/zFgpRgY4 zJNLSv7GZiz2S4Fc5ArGjd34lL47+L8bozuYjqNOv9sqX0Zgll5XaJ1ndvr7UqZu 1xQFgm38reoM3IarBP/SkEFbt/v9iak602VO3k28fQhMaocP7JWR2YLT3kZM0+WT FwIDAQAB -----END PUBLIC KEY-----
This appendix briefly outlines a few other solutions addressing Signed HTTP Requests.
AWS provides a system for their clients using HTTP headers holding security constructs while the digested HTTP body may hold any valid media type. For more information see the [AWS]. Signatures may also be added to query strings in a similar fashion to Section 5.
HTTP Signatures is a system using HTTP headers holding security constructs while the (optional) digested HTTP body may hold any valid media type. This scheme has been adopted by the French Open Banking API [STET]. For more information see the Internet draft [HTTPSIG]. HTTP Signatures also supports signed response data.
The current (3.1) version of the Open Banking API [OBIE] use a scheme where a dedicated HTTP header holds a detached JWS signature covering a clear text JSON message in the HTTP body:
POST /foo HTTP/1.1 Host: example.com Content-Type: application/json x-jws-signature: eyJhbGciOiJSUzI1N..SD7xMbpL-2QgwUsAlMGzw Content-Length: 2765 { "something": "data", Additional application specific properties }
Notes:
The current version (Draft 06) of the financial API [FAPI] use a scheme where the payload is signed using JWS in Base64Url mode:
POST /foo HTTP/1.1 Host: example.com Content-Type: application/jws Content-Length: 1288 eyJhbGcRjIn0.ew0KICJfds56gty5ypc3MiOiA.2QgwUsA565656lMGzw
Notes:
The SHREQ specification is currently developed at: https://github.com/cyberphone/ietf-signed-http-requests.