Signing of HTTP Messages

Abstract

When communicating with version 2 and later of DAX REST API using the HTTP protocol you will need to sign all requests with a private key. DAX has the public key on record and will verify the signature with the callers public key.

This is how it works …

Signature

All transactions with DAX are considered high security and having an additional signature in the HTTP header allows the server to ensure that even if the transport channel has been compromised the content of the message from a client has not been compromised and/or tampered with.

Components of the signature (signature parameters)

There are a number of different parameters of the signature header that are needed - here we will detail those parameters. The order of the parameters are not important.

A complete signature header can look like this:

Example signature header:

realm="dax" algorithm="sha256withrsa" headers="(request-target) cache-control date content-length" signature="zYLDJ9pBayO5QpFkYo1b1r5h0j9sK/Sy8lzAwz2hTdwQy..."

realm

REQUIRED. The realm parameter signals the server which realm is being used. A realm is used to group together a set of users, credentials and roles. In DAX the realm to use is dax.

Example:

realm="dax"

signature

REQUIRED. This is the base 64 encoded signature generated by the client. The client uses the algorithm and headers signature parameters to form a signing string and this string is later signed with the private key associated with the calling client using the specified algorithm. The signature parameter is then set to the base 64 encoding of the signature.

Example:

signature="zYLDJ9pBayO5QpFkYo1b1r5h0j9sK/Sy8lzAwz2hTdwQyXj6ZrjgCAdc..."

headers

REQUIRED. The headers parameter is used to specify the ordered list of HTTP headers that has been used when generating the signature. The headers should be lowercased with surround quotes and if multiple headers are used they should be separated by a single space character. If there are duplicate headers the last header will be used.

Mandatory headers when communicating with DAX are date and the special (request-target) (more on that later).

If a header is in the list but not available in the request, the request will be denied.

Example:

headers="(request-target) date content-type"

algorithm

REQUIRED. The algorithm parameter is used to specifiy the digital signature algorithm that was used when generating the signature. DAX supports a single algorithm, sha256 hashed and signed with RSA, SHA256withRSA.

Example:

algorithm="sha256withrsa"

Signature string construction

In order to generate the string that is signed with a key, the client MUST use the values of each HTTP header field in the headers signature parameter, in the order they appear in the headers signature parameter. It’s also important to note that the enforced encoding is utf-8 and that information needs to be included either as a Content-Type header or as an Accept-Charset header. And of course, all necessary strings must be calculated and hashed using utf-8.

  1. The special (request-target) header is used to specify the HTTP request’s target. To put it together you use the lowercased request method (i.e. get, post, put, delete etc.), a single space character and then the request’s target path. Lowercasing is only used for the request method and not the target path.
  2. Create the headers field by joining the lowercased header field name followed by a colon (:), a single space and the header field value. Always trim the value before adding it. If there are multiple instances of the header being used it should be concatenated to the previous header separated with a single comma (,).
  3. All values in the string should end with a newline \n character.

Example GET request

GET /api/v2/DaxEndPoint HTTP/1.1
Host: dax.amido.se
Date: 2020-05-17T14:44:30+02:00
X-Example: Example header
           with some whitespace.
Cache-Control: max-age=60
Cache-Control: must-revalidate

The following example illustrates how the signature would be constructed if the headers part of the signature looked like this:

headers="(request-target) host date cache-control

Example signing string:

(request-target): get /api/v2/DaxEndPoint\n
host: dax.amido.se\n
date: 2020-05-17T14:44:30+02:00\n
cache-control: max-age=60,must-revalidate\n

Example POST request

When DAX gets a request with a body the body MUST be included in the signing string.

POST /api/v2/DaxEndPoint HTTP/1.1
Host: dax.amido.se
Date: 2020-05-17T14:44:30+02:00
X-Example: Example header
           with some whitespace.
Cache-Control: max-age=60
Cache-Control: must-revalidate
Content-Length: 18

{"hello": "world"}

The following example illustrates how the signature would be constructed if the headers part of the signature looked like this:

headers="(request-target) host date cache-control content-length

Example signing string:

(request-target): post /api/v2/DaxEndPoint\n
host: dax.amido.se\n
date: 2020-05-17T14:44:30+02:00\n
cache-control: max-age=60,must-revalidate\n
content-length: 18\n
{"hello": "world"}

DAX mandatory headers

The following headers are mandatory to use when calling DAX:

  1. (request-target)
  2. date (as an ISO-8601 timestamp with timezone information, see examples above)

Creating the actual signature

In order to create a signature, a client must use the contents of the HTTP message, the headers value, and the signature string construction algorithm to create the signature string.

The algorithm must then be used to generate a digital signature on the signature string.

The signature is then generated by base 64 encoding the output of the digital signature algorithm. For example, assume that the algorithm value was SHA256withRSA. This would signal to the server that the data has been signed with an RSA Private Key and that the signature string hashing function is SHA-256. The signature should be a byte[] which is then base 64 encoded and placed in the signature value.

Complete request example with signature:

GET /api/v2/DaxEndPoint HTTP/1.1
Host: dax.amido.se
Signature: realm="dax" algorithm="sha256withrsa" headers="(request-target) cache-control date" signature="zYLDJ9pBayO5QpFkYo1b1r5h0j9sK/Sy8lzAwz2hTdwQy..."
Date: 2020-05-17T14:44:30+02:00
X-Example: Example header
           with some whitespace.
Cache-Control: max-age=60
Cache-Control: must-revalidate

The signature string using algorithm sha256withrsa could conceptually be described as:

base64encode(rsasign(sha256(byte-array-from-utf8(signingstring)))).

Final words

If you’re using .net core we have .net standard SDK available upon request that takes care of all complexities during REST API requests. The code for this SDK will be published at a later date.