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.)
Protocol Goals
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:
00010002hello
Which is:
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.
Edited:
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).
Feb 10, 2011 @ 14:55:18
As you mention, this feels alot like two-legged OAuth. Two legged OAuth gives you the signature plus the noonce which can protect you from replay attacks.
So, my first thought would be to incorporate two legged OAuth. Barring that, it appears as if you are not signing query parameters? If I am looking for this as a means to do trust between two systems I would think I would want to get at least the url or query parameters in the signature.
Feb 10, 2011 @ 15:13:43
OAuth is an entire authentication protocol, no? I want to be able to support messaging signing and still use traditional authentication mechanisms. Also message receivers may choose to ignore the signature (or not even know its there). Finally, I’m more interested in trust between multiple systems. In this case, using the URL and/or query parameters as part of the signature is a barrier here, as the resource may be forwarded to multiple endpoints through different URLs. Trust between two systems is uninteresting for me as doesn’t HTTPS/SSL solve this requirement, especially if you’re using traditional authentication mechanisms already just to be able to accept the request?
Feb 10, 2011 @ 15:24:00
Two legged takes the auth out of it.. it is really more of a trusted system approach. The systtems share a secret, and then messages and headers are signed using that secret. With the nonce and timestamp, you are protected from most replay attacks as well as trust.
Feb 10, 2011 @ 15:55:31
You’ll have to point out where OAuth isn’t an authentication protocol and defines simple message signing. As all I see/read now is stuff that is using the Authorization header to pass information. Again, I want the ability for servers/clients to ignore the signature if they don’t care.
Feb 10, 2011 @ 16:09:28
And also the ability to forward the message (and its signature) to multiple parties multiple times.
Feb 11, 2011 @ 13:15:00
2 legged is really more about system auth, as oppsed to user auth. But you are correct.. it is more of a 1-1 trust model, as opposed to a 1-many trust model.
Feb 10, 2011 @ 15:20:36
I would two-legged OAuth model with RFC 2617 challenge-response protocol.
Feb 10, 2011 @ 16:43:45
I’d combine all the headers you propose into a single header. (Content-Signature) and use a pattern that allows it to be extended by others in the future:
content-signature: {sig-type} : Base64(sig-body)
where sig-type is a registered type name (i.e. RHT) and sig-body follows the documented rules for the sig-type (i.e. sig:algol:signer:timestamp).
Feb 10, 2011 @ 17:06:34
Yeah, i kinda like it as you can specify in sig-type the order of attributes to add to the signature. The only thing I don’t like about an uber header is you have to parse it. You can extend it just as easily by adding additional headers. This is pretty much why I’ve moved from Link headers to custom headers so that clients wouldn’t have to parse the link header. Finally, Base64 doesn’t work very well for large signatures as it inserts cr/lf every 76 bytes.
Feb 10, 2011 @ 17:20:18
Yeah, Base64 might not be the best encoding, but I figure some encoding might be needed depending on the chars in each of the elements.
Personally, i’d like to see fewer headers in general as HTTP has quite a number of them and is getting a bit bloated, IMO. We parse stuff all the time; not sure how much of a downside that is, but I see your POV.
Finally, by adopting a single header approach you could reduce the parse-weight in cases where new sig-types are introduced in the future. Servers and clients will always know where to look for sig information.
Mar 03, 2011 @ 15:33:24
Just a note: base64 itself does not add new lines. It’s other specifications that require them.
http://tools.ietf.org/html/rfc3548#section-2.1
For example, MIME uses Base64 and requires a linefeed after 76 characters.So Base64 can be safely used in HTTP headers.
In fact, it already is being used by the Authorization header specification.
Feb 10, 2011 @ 20:15:28
SAML does this. Should probably look at picketlink.
Feb 10, 2011 @ 21:20:22
I’ve edited this blog entry to take into account some of the comments here.
Feb 10, 2011 @ 21:43:03
Bill:
Some other things to consider:
– If you allow optional elements (a variable collection), identifying each element in the collection will be important (al=…:s1=…:ts=…:s2=…) if only two elements are inluded, which are they? only three? etc. also allows for varying the _order_ in which the header elements appear (if that is at all desirable).
– If the timestamp is to be used for anything other than identifying the sig (i.e. you want to use it to invalidate old sigs), the timestamp might better be used as an expiration-timestamp. For example, I could hand you a representation of a link for a quote or bid that is only good for the next 15 mins, etc. IOW, the person signing the message is in control of it’s invalidation.
– Is this meant to be an Entity Header (valid for both Request and Response? ) or only a Response Header (sent by server)?
Once you get details worked out, I’d be happy to try to build a sample implementation to test out various uses.
Feb 10, 2011 @ 21:54:09
On second-reading, I may be missing the boat after your last edits. I’ll wait to see some IRL examples of your Request/Response messages and then consider my comments.
Thanks.
Feb 10, 2011 @ 22:23:33
Timestamp is used to calculate the signature. Receiver has option to expire it.
If you flip it and let the sender decide the expiration, is it still a secure protocol?
This is meant to be an entity header for both the request and response.
I’ve been implementing in parallel within Java. Don’t know your preferred language, but I’d really like to know if it will work on other languages like Python, Ruby, and PHP, specifically with SHA1RSA256 MD5RSA, etc…
Feb 10, 2011 @ 22:24:13
It seems like you understand what I’m trying to do.
Feb 11, 2011 @ 00:38:09
This isn’t in the header and AFAIK it doesn’t work for JSON, but it is a standard and libraries abound (javax.xml.crypto.dsig):
XML-Signature Syntax and Processing (http://www.w3.org/TR/2002/REC-xmldsig-core-20020212/)
Here’s somebody’s JSON equivalent: http://payswarm.com/specs/source/vocabs/signature.html
Feb 11, 2011 @ 13:08:50
Java has built in support for signature generation and verification. I plan on adding support for XML and I guess JSON signatures eventually. The only problems I forsee with those formats is that it will require an envelope for the message body, something I don’t particularly like.
Feb 11, 2011 @ 14:42:35
It’s great that Java implements support for sigs, but it’s more important that the solution is standards-based, as not all REST clients will be Java-based. Of course, as mentioned before, Javax.xml.crypto.dsig does implement the previously mentioned standard, so it’s a win-win for this purpose.
I think that you could still use headers as well, to trigger downstream processing, but my first thought is to leverage standards as much as possible, especially when it comes to anything related to cryptography.
I say this after having spent nearly 10 years designing crypto protocols and having to get them certified by government agencies (both in US and abroad). Now I go out of my way to ensure everything is standards based…
Feb 11, 2011 @ 14:57:49
Kent, definitely going to used standardized signature methodologies to create the signature and verify it. SHA1RSA, MD5RSA, etc… Python looks like it supports it as well as Ruby. What is new here is how the signature is transmitted.
With OAuth its part of an entire authentication and authorization protocol. With multipart/signed or XML signature, its a whole envelope format. Neither of which fits the goals of when I’m trying to accomplish. I will take your comment into consideration and look for more possible accomplished standards to reference.
Distributed Weekly 89 — Scott Banwart's Blog
Feb 11, 2011 @ 14:03:41
Feb 17, 2011 @ 20:29:50
Is the choice of including a signature (harmless perhaps) and the algorithm used for the signature determined by the resource? Can the client indicate what algorithms it will accept? – an Accept-Signature: along the lines of Accept: perhaps?
Feb 18, 2011 @ 15:14:30
The blog I linked mentioned something like providing an Accept-Signature negotiation. You have a use case for that? IMO, signature algorithm could be decided by any party depending on your usecase.
Feb 18, 2011 @ 16:19:27
Hey Bill,
I’d like to encourage you to submit this to IETF. As many people have pointed out, OAuth does have a similar mechanism but as you rightly pointed it’s entwined with the rest of the Authn/Authz problems they’re attempting to address.
Within the SAML TC, and within the Shibboleth project, there are people who would like to use exactly this sort of mechanism within request flows. If you’d like our support when going to IETF (or similar standards body) just drop my a line.
Thanks for publishing your thoughts on this.
Apr 01, 2011 @ 23:48:08
Is there any way to integrate or use the work going on with JSON Web Tokens?
Apr 04, 2011 @ 14:29:25
Resteasy has a few spis for integration, interceptors being one of them. I currently don’t have plans to support JSON Web Tokens, but if you’d like to contribute something give us a ring on the resteasy developer email list.
Sep 29, 2012 @ 23:40:35
Am I right that both the IETF drafts referenced at the top of this post are now expired? Is anyone else pushing this? I think it would be very valuable if a TLS loaded page could have a header specifying that some/all of its resources must be signed, allowing it to load other resources (JS, CSS, images) over standard HTTP (hence making them cacheable) but still ensure that they have not been tampered with in the middle.
Oct 16, 2012 @ 14:44:43
Its possible they are expired. But DKIM is an RFC. Doseta is just generalizing it a bit more to make it more flexible for other protocols