Subject: Signature #2: No CBOR | Re: Formalizing the HTTP State Tokens proposal.

From: Kari Hurtta <[email-masked]>
Date: Mon, 20 May 2019 07:28:51 +0300 (EEST)

HTTP State Tokens
https://tools.ietf.org/html/draft-west-http-state-tokens-00

Ilari Liusvaara:
> - The request signing mechansims looks like one that would break if
>   there is some CDN or reverse proxy in the path that adds a header
>   or a few (sometimes with some non-standard one in the mix). Or is it
>   expected that all CDNs or reverse proxies on path for application
>   using this mechanism can rewrite the MAC?

On my post "Signature" (
<[email-masked]>
https://lists.w3.org/Archives/Public/ietf-http-wg/2019AprJun/0112.html )

I suggested to exclude certain listed header fields and header fields
listed in the Connection header field.

But that is not enough.  It is better that middleman
does not rewrite signature.

Considering my "Typical request for origin"

---------------------------------------------
       [ browser ]
             
            ⇓ https

  [ TLS termination and load balancing ]

     ⇓ http                 ⇓ http

   [ cache ]               [ cache ]

      ⇓ http                  ⇓ http

  [ -----  reapplying TLS --------- ]

              ⇓  https

  [ TLS termination and load balancing ]

      ⇓ http                 ⇓ http

   [ origin web server ]  [ origin web server ]

----------------------

In here many places can add

X-Frowarded-For
X-Forwarded-Proto
Forwarded                  (RFC 7239)
X-Forwarded-Host
X-Forwarded-Port
Via
CDN-Loop                   (RFC 8586)

header fields to request.  And perhaps some others.

Therefore the list of signed header field's names need to be added
to Sec-Http-State request header field.

Using is CBOR was justified with:

3.2.  CBOR representation of exchange response headers
https://tools.ietf.org/html/draft-yasskin-http-origin-signed-responses-05#section-3.2

|   To sign an exchange's response headers, they need to be serialized
|   into a byte string.  Since intermediaries and distributors
|   (Appendix A.2) might rearrange, add, or just reserialize headers, we
|   can't use the literal bytes of the headers as this serialization.

When  the list of signed header field's names are on
Sec-Http-State request header field usage of CBOR
is NOT justified.

Order of header fields is given on the list of signed header
field's names on Sec-Http-State request header field. This gives
both spelling (lowercase or not) and order of header fields.
There is nothing what using of CBOR adds.

Other details need to be specified anyway. For example:

|   o  The byte string ':status' to the byte string containing the
|      response's 3-digit status code, and
|
|   o  For each response header field, the header field's lowercase name
|      as a byte string to the header field's value as a byte string.

These was not given by CBOR.

There is some requirements:

The list of signed header field's name must not include
names of header fields where header field name occurs
several times on request.

The list of signed header field's names must not include
names of header fields which occur on Connection header field.

Also following header field's names must not be included
to the list of signed header field's names:

  *  Connection 

  *  Any header field name starting with Proxy-

  *  Keep-Alive

  *  Trailer

  *  Transfer-Encoding

  *  Upgrade

  *  Forwarded

  *  Any header field name starting with X-Frowarded-

  *  Via

  *  CDN-Loop

If the  list of signed header field's names on
Sec-Http-State request header field includes non-existing
header field, this header field is just skipped
when calculating signature. These header field
names effectively works as "don't add" list, because
adding of them to request on transit changes signature.

If he  list of signed header field's names on
Sec-Http-State request header field includes header fields
which have more than one occurrence, FAIL calculating
of signature.

So

5.2.  Generate a request's signature
https://tools.ietf.org/html/draft-west-http-state-tokens-00#section-5.2

 ⇒

--------

   || indicates catenation
   0  indicates octet 0 (ASCII NUL).

   Note that redirection causes  Sec-Http-State request header
   field and signature to be recalculated.

   This operates with request, which is httpRequest on
   4.5. HTTP-network-or-cache fetch on Fetch standard.

   Given a request, a base64-encoded token value, and a key:

   1. Generate decimal nonce value for request ( this may be implemented
      by storing nonce value to HTTP State Token for request's
      URL and incrementing that nonce. 

   2. Store generated nonce value as "nonce" for return triple.

   3. Let "serialized-request" to be

      ":method:" || request's method || 0 ||
      ":token:" || base64-encoded token value || 0 ||
      ":url:" || request's current URL || 0 ||
      :nonce:" || request's decimal nonce value || 0

   4. Let "signed list" to be header field names
      from request's  header list, excluding

      o header fields mentioned on Connection: header field

      o header fields which have more than one occurrence
        on request's  header list

      o Connection 

      o Any header field name starting with Proxy-

     o Keep-Alive

     o Trailer

     o Transfer-Encoding

     o Upgrade

     o Forwarded

     o Any header field name starting with X-Frowarded-

     o Via

     o CDN-Loop
  
     [TBD some other?]

   5. [TBD: Add possible  "don't add" header field names
            to "signed list". ]

   6. For each "field name" in  "signed list"

      1. Retrieve "header value" of ""field name" from
         request's  header list

      2. If  "header value" not found, skip next substeps

      3. If more that one  "header value" found, fail this algorithm

      4. Append "field name" value ||  ":" || "header value" value || 0
         to "serialized-request"

         Note that "serialized-request" uses field name spelling
	 from "signed list" and not header field name spelling
	 from request's  header list.

         Specially, when checking signature, order and
	 spelling of header field names from "signed-fields"
         is used.

   7. Let "signed-fields" to be values from "signed list"
      with comma as separator.

   8. Store "signed-fields" as  "signed-fields" for return triple.

   9. Let "sig" to be result  of computing HMAC-SHA256 [RFC2104] over
       "serialized-request".

   10. Store "sig" as "sig" for return triple.

   11. Return triple ( "sig", "signed-fields", "nonce" ).

-----

If there is retries on

  4.6. HTTP-network fetch
  https://fetch.spec.whatwg.org/#http-network-fetch

these use same "Sec-Http-State" header field and
therefore same nonce on every retry.

If server gets second request with same nonce value,
it may

    1. If request is identical (including request body)
       than original request with same nonce,
       server MAY return cached copy of response
       from original request with same nonce.
       Response MAY include update "Age" header field.
       Server MUST NOT execute request with
       have same nonce.

       [TBD: Should re-execution of request to be
             allowed, if request do not have
	     side effects? ]

    2. Otherwise server MAY return response with
       status 4xx "Duplicated nonce value".

If server selects to not keep track used nonce values
(per HTTP State Token key), server MAY process request
as new.

Note: Keeping track used nonce values is hard.
      Therefore server may elect to ignore nonce
      (except for signature validation).

TBD: Should nonce value generation to be defined
     more rigorously, and then allow server return
     4xx "Duplicated or bad nonce value" when
     nonce value is far off from next expected
     nonce value?

     If nonce is defined to be stored on
     HTTP State Token for origin and incremented
     from here, this also makes middleman
     hard to do own requests, because all
     request need change nonce and therefore
     recalculate signature.

5.1.  Attach HTTP State Tokens to a request
https://tools.ietf.org/html/draft-west-http-state-tokens-00#section-5.1

|   9.   If "request-token"'s "key" is not null, then insert a member
|        into "header-value" whose key is "sig", and whose value is the
|        result of executing Section 5.2 on request, "serialized-value",
|        and "request-token"'s "key".

 ⇒

--------

   9.   If "request-token"'s "key" is not null, then
      
        9.1   Execute Section 5.2 on request, "serialized-value",
              and "request-token"'s "key". Result have triple
	      ( "sig", "signed-fields", "nonce" )

        9.2  Insert a member into "header-value" whose key is "sig",
	     whose value is "sig" from triple (using byte sequence
	     (sh-binary, [draft-ietf-httpbis-header-structure-10], Section 3.10)
	     syntax).

        9.3  Insert a member into "header-value" whose key is 
             "signed-fields",  whose value is "signed-fields"
	     from triple (using string syntax, [draft-ietf-httpbis-header-structure-10],
	     Section 3.8).

        9.4  Insert a member into "header-value" whose key is
	     "nonce",  whose value is "nonce" from triple
	     (using integer syntax, [draft-ietf-httpbis-header-structure-10],
	     Section 3.6).

--------

4.1.  The 'Sec-Http-State' HTTP Header Field

https://tools.ietf.org/html/draft-west-http-state-tokens-00#section-4.1

|   The dictionary MAY contain:

Add
 ⇒

--------

   o Exactly one member whose key is "nonce" , and whose value is
     integer ([draft-ietf-httpbis-header-structure-10], Section 3.6).

   o Exactly one member whose key is "signed-fields", and whose value is
     string ( [draft-ietf-httpbis-header-structure-10], Section 3.6).

---------