Tuesday, October 16, 2007

HTTP response signing strawman

If we revise the abstract model for generating a Signature header along the lines suggested in my previous post, we get this:

  1. Choose which key (security token) to use and create one or more identifiers for it.  One possible kind of key would be an X.509 certificate.
  2. Choose which response headers to sign.  This would include at least Content-Type and probably Date and Expires.  It would not include hop-to-hop headers.
  3. Compute the digest (cryptographic hash) of the full entity body of the requested URI. Base64-encode the digest.
  4. Create a  Signature header template; this differs from the final Signature header only in that it has a blank string at the point where the final Signature header will have the base64-encoded signature value. It can specify the following information:
    • the type of key;
    • one or more identifiers for the key;
    • an identifier for the suite of cryptographic algorithms to be used;
    • an identifier for the header canonicalization algorithm to be used;
    • a list of the names of the response headers to be signed;
    • the request URI;
    • the base64 encoded digest (from step 4).
  5. Combine the response headers that are to be signed with the Signature header template.
  6. Canonicalize the headers from the previous step.  This ensures that the canonicalization of the headers as seen by the origin server are the same as the canonicalization of the headers as seen by the client, even if there are one or more HTTP/1.1 conforming proxies between the client and the origin server.
  7. Compute the cryptographic hash of the canonicalized headers.
  8. Sign the cryptographic hash created in the previous step.  Base64-encode this to create the signature value.
  9. Create the final Signature header by inserting the base64-encoded signature value from the previous step into the Signature header template from step 5.

Note that when verifying the signature, as well as checking the signature value, you have to compute the digest of the entity body and check that it matches the digest specified in the Signature header.

The syntax could be something like this:

Signature = "Signature" ":" #signature-spec
signature-spec = key-type 1*( ";" signature-param )
key-type = "x509" | key-type-extension
signature-param =
   "value" = <"> <Base64 encoded signature> <">
   | "canon" = "basic" | canon-extension
   | "headers" = <"> 1#field-name <">
   | "request-uri" = quoted-string
   | "digest" = <"> <Base64 encoded digest> <">
   | "crypt" = ( "rsa-sha1" | crypt-extension )
   | "key-uri" = quoted-string
   | "key-uid" = sha1-fingerprint | uid-extension
   | signature-param-extension
sha1-fingerprint = <"> "sha1" 20(":" 2UHEX) <">
UHEX = DIGIT | "A" | "B" | "C" | "D" | "E" | "F"
uid-extension = <"> uid-type ":" 1*uid-char <">
uid-type = token
uid-char = <any CHAR except CTLs, <\> and <">>
key-type-extension = token
canon-extension = token
crypt-extension = token
hash-func-extension = token
signature-param-extension =
   token "=" (token | quoted-string)

There are several issues I'm not sure about.

  • Should this be generalized to support signing of (some kinds of) HTTP request?
  • What is the right way to canonicalize HTTP headers?
  • Rather than having a digest parameter, would it be better to use the Digest header from RFC 3230 and then include that in the list of headers to be signed?
  • Should the time period during which the signature is valid be specified explicitly by parameters in the Signature header rather than being inferred from other headers, such as Date and Expires (which would of course need to be included in the list of headers to sign)?
  • Should support for security tokens other than X.509 certificates be specified?

6 comments:

Davanum Srinivas (dims) said...

James,

typo.."the base64 encoded digest (from step 4)." should be "the base64 encoded digest (from step 3)."

Looks good! +1 to do something similar for signing requests. One thing that i also try to do is think about the reverse. Start from what's on the wire and figure out what steps you would need on say the client's end to check the signed response.

Please talk to Ruchith when you get a chance on how we added support for supporting lots of users (w/o adding certs from each of them into the server's keystore). Here's a write up he did based on original notes from werner http://wso2.org/library/255.

katre said...

That's a lot of data you're packing into one header. Given that everything except the actual signature value, would it be better to have a bunch of Signature-* headers, and declare that all headers in Signature-Signed-Headers + Signature-* are signed?

James Clark said...

I admit there's a lot of information. However, in terms of numbers of parameters there are precedents. Think of the Authorization header when using Digest authentication (section 3.2.2 of RFC 2617). Or think of the Cache-Control header.

One of the things that makes me reluctant to split it up is the fact that there can be multiple signatures (in separate Signature headers and/or in a single header separated by commas). If you put things in separate headers, then you are going to have to end up pairing the n-th entries in each of your Signature-* headers. This seems like a significant complexity hit.

The thing that mosts concerns me here is whether we are going to hit limits on header lengths in common implementations (especially limits to lengths after headers with the same name are merged), since several of are parameters can be long. Does anybody know what these limits are?

Blaine said...

Really seriously, OAuth ;)

the current signing algorithm that we've defined covers most of the cases. Signing headers, etc, could be defined as either a second version or as an extension.

Currently negotiation of secrets is done with the express intent of identifying users, but alternative methods could be established that allow for key / secret exchange without user intervention.

James Clark said...

I've read the OAuth spec. It seems to me it's solving a completely different problem related to controlling access to protected resources. The problem I'm trying to solve is to provide a cache-friendly way to verify the integrity of resources transferred from servers to clients. Can you give some more detail on how you think OAuth could be used to solve this problem?

Blaine said...

OAuth specifies essentially two things: a very simple method for exchanging keys and secrets, and a cache-friendly (we hope!) method for verifying the integrity of requests.

One of the primary goals of OAuth was to allow for authenticated requests to be made securely (i.e., without revealing the secrets, and without allowing man in the middle attacks) without requiring SSL.

That it's for authenticated requests to protected resources is only one interpretation of the token / secret pairs. There's work underway to extend the spec to include "discovery", or automatic negotiation of the consumer key, and a similar approach could be used for the token.

OAuth doesn't specify signing the response body, but because it allows for both symmetric and asymmetric keys, adding such an extension would be "trivial". If there were a way to easily negotiate keys and secrets for a more generic HTTP request / response signing approach, then OAuth could adopt greatly simplified signing algorithms. My guess is that negotiating those things will look a lot like OAuth's existing process.

About Me