4/5/11: After a lot of feedback from the IETF HTTP WG, I found some work is already being done in this area in the DOSETA specification. I’ll be retiring Content-Signature for the time being.
3/23/11: I’ve been encouraged to bring this to the IETF and have submitted an Internet-Draft on the subject. Please go there to see further iterations on this specification.
Recently a RESTEasy user asked for the ability to digitally sign requests and responses. They were pushing HTTP requests through one or more intermediaries and wanted to make sure that the integrity of the message was maintained as it hopped around the network. They needed digital signatures.
There’s always been multipart/signed, but I never really liked the data format. One, what if some clients support the format and some don’t? Two, signature data seems really to belong in the HTTP header rather than enclosed within an envelope. I found a nice blog that shared and added a bunch more to the conversation. So, without finding a match by doing a google search, I decided to define our own protocol. (FYI, OAuth does have signatures as part of its protocol, but I wanted something that could be orthogonal to authentication as the client and server may not be using OAuth for authentication.)
The protocol goals and features we wanted to have were:
- Metadata defining exactly how the message was signed
- Ability to specify application metadata about the signature and have that metadata be a part of the signature
- Simplicity of headers. Have all signature information be stored within HTTP request or response headers. This makes it easier for frameworks and client and server code in general to handle signature verification.
- Expiration. We wanted the option to expire signatures.
- Signer information. We wanted the ability to know who signed the message. This would allow receivers to look up verification keys within internal registries.
- Ability to ignore the signature if you don’t care about that information or if the client or server doesn’t know how to process it.
- Ability to forward representation/message to multiple endpoints/receivers
- Allow multiple different URLs to publish the same signed message
- Although it could be used as an authorization mechanism, it is not meant to replace existing OAuth or Digest protocols that ensure message integrity
The Content-Signature Header
The Content-Signature header contains all signature information. It is an entity header that is transmitted along with a request or response. It is a semicolon ‘;’ delimited list of name value pairs. Values must be enclosed within quotes if they use any delimiting character within their name or value. These attributes are metadata describing the signature as well as the signature itself. Also, the Content-Signature may have more than one value, in other words, more than one signature may be included with the Content-Signature header. Multiple signatures are delimited by the ‘,’ character.
These are the core attributes of the Content-Signature header:
signature – (required) This is the hex encoded signature of the message. Hex encoding was chosen over Base64 because Base64 inserts cr/lf characters after 76 bytes which screws up HTTP header parsing.
values – (optional) this is a colon “:” delimited list of attributes that are included within Content-Signature header that are used to calculate the signature. The order of these listed attributes defines how they are combined to calculate the signature. The message body is always last when calculating the signature. If this attribute is omitted, then no Content-Signature attribute is used within the calculation of the signature.
headers –(optional) List of colon “:” delimited HTTP request or response headers that were included within the signature calculation. The order of these listed headers defines how they are combined to calculate the signature.
algorithm – (optional) The algorithm used to sign the message. The allowable values here are the same as those allowed by java.security.Signature.getInstance(). If there is a W3C RFC registry of signing algorithms we could use those instead.
signer – (optional) This is the identity of the signer of the message. It allows the receiver to look up verification keys within an internal registry. It also allows applications to know who sent the message.
id – (optional) This is the identity of the signature. It could be used to describe the purpose of a particular signature included with the Content-Signature header.
timestamp – (optional) The time and date the message was signed. This gives the receiver the option to refuse old signed messages. The format of this timestamp is the Date format described in RFC 2616.
expiration – (optional) The time and date the message should be expired. This gives the sender the option to set an expiration date on the message. The format of this attribute is the Date format described in RFC 2616.
signature-refs – This is a ‘:’ delimited list referencing other signatures by their id attribute within the Content-Signature header. This means that these referenced signature values will be included within the calculation of the current signature. The hex-encoded value of the referenced signature will be used .
Other attributes may be added later depending on user requirements and interest. URI and query parameters were specifically left out of the protocol as integrity between two parties should be handled by HTTPS/SSL, the Digest authentication scheme discussed in RFC 2617, or OAuth. Remember, the point of writing this protocol is so that representations can be signed and exchanged between multiple parties on multiple machines and URLs.
Signing and Verifying a message
The signer of a message decides which Content-Signature attributes and HTTP headers it wants to include within the full signature. The signature is calculated by signing the concatenation of
attribute-values + header-values + signature-refs + message-body
Attribute-values pertain to the list of attribute names defined within the ‘values’ attribute of the Content-Signature element. Header-values pertain to the list of header names defined within the ‘headers’ attribute of the Content-Signature element. Signature-refs pertains to referenced signatures that also appear in the Content-Signature header. Attributes must always precede headers. Headers must precede signature refs. The message-body always comes last. For example, if the signer decides to include the signer, expiration attributes and Content-Type and Date headers with a text/plain message of “hello world”, the base for the signature would look like this:
billSunday, 06-Nov-11 08:49:37 GMTtext/plainFriday, 11-Feb-11 07:49:37 GMThello world
The Content-Signature header transmitted would look like:
Content-Signature: values=signer:expiration; headers=Content-Type:Date; signer=bill; expiration="Sunday, 06-Nov-11 08:49:37 GMT"; signature=0f341265ffa32211333f6ab2d1
To verify a signature, the verifier would recreate the signature string by concatenating the attributes specified in the “values” attribute, HTTP headers defined in “headers” attribute, and finally the message body. Then apply the verification algorithm.
If there is an attribute declared within the “values” attribute that isn’t specified in the Content-Signature header, it is assumed it is a secret held between the signer and verifier. i.e. the signer. The value of this attribute must be determined in an undefined way.
If there is a header declared within the “headers” attribute that doesn’t exist, the server may choose to abort if it cannot figure out how to reproduce this value.
Here’s an example of multiple signatures. Let’s say the Content-Signature header is initially set up like this with a message body of “hello”:
Content-Signature: id=husband; signature=0001, id=wife; signature=0002
Here, we have two initial signatures signed by two different entities, husband and wife (found by their id attribute). We want to define a third signature, marriage, that includes those signatures.
Content-Signature: id=husband; signature=0001, id=wife; signature=0002, id=marriage; signature-refs=husband:wife signature=00033
The marriage signature would be calculate by the signing of this string:
husband’s signature + wife’s signature + message body
If there is a signature reference declared within the signature-refs attribute that doesn’t exist, the server may choose to abort if it cannot figure out how to reproduce this value.
Other similar protocols out there?
I only spent about an hour looking to see if there were similar protocols out there. If somebody knows, let me know. It would be cool to get feedback on this proposal as well.
People in the comments section of this entry keep mentioning two-legged OAuth, but I don’t see how they describe anything other than in the Authorization header. This is something we don’t want as we want to be able to use traditional authentication mechanisms so that signing can be supported on servers or clients that don’t understand OAuth (or don’t want to use it).