If we revise the abstract model for generating a Signature header along the lines suggested in my previous post, we get this:
- 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.
- 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.
- Compute the digest (cryptographic hash) of the full entity body of the requested URI. Base64-encode the digest.
- 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).
- Combine the response headers that are to be signed with the Signature header template.
- 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.
- Compute the cryptographic hash of the canonicalized headers.
- Sign the cryptographic hash created in the previous step. Base64-encode this to create the signature value.
- 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
"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
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
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?