http-signature
Advanced tools
Comparing version 0.9.11 to 0.10.0
@@ -49,4 +49,4 @@ # Abstract | ||
credentials := "Signature" params digitalSignature | ||
params := 1#(keyId | [algorithm] | [headers] | [ext]) | ||
credentials := "Signature" params | ||
params := 1#(keyId | algorithm | [headers] | [ext] | signature) | ||
digitalSignature := plain-string | ||
@@ -58,2 +58,3 @@ | ||
ext := "ext" "=" <"> plain-string <"> | ||
signature := "signature" "=" <"> plain-string <"> | ||
@@ -97,17 +98,23 @@ headers-value := plain-string | ||
### Digital Signature | ||
#### signature | ||
The `digitalSignature` portion of the credentials is a REQUIRED field, and is | ||
REQUIRED. The `signature` parameter is a `Base64` encoded digital signature | ||
generated by the client. The client uses the `algorithm` and `headers` request | ||
parameters to form a canonicalized `signing string`. This `signing string` is | ||
then signed with the key associated with `keyId` and the algorithm corresponding | ||
to `algorithm`. The result is then `Base64` encoded. | ||
then signed with the key associated with `keyId` and the algorithm | ||
corresponding to `algorithm`. The `signature` parameter is then set to the | ||
`Base64` encoding of the signature. | ||
### Signing String Composition | ||
In order to generate the string that is signed with a key, the client MUST | ||
take the values of each HTTP header specified by `headers`, in the order they | ||
appear, and separate with an ASCII newline `\n`. The last header in the list | ||
MUST NOT include a trailing ASCII newline. | ||
In order to generate the string that is signed with a key, the client MUST take | ||
the values of each HTTP header specified by `headers` in the order they appear. | ||
1. If the header name is not `request-line` then append the lowercased header | ||
name followed with an ASCII colon `:` and an ASCII space ` `. | ||
2. If the header name is `request-line` then appened the HTTP request line, | ||
otherwise append the header value. | ||
3. If value is not the last value then append an ASCII newline `\n`. The string | ||
MUST NOT include a trailing ASCII newline. | ||
# Example Requests | ||
@@ -124,21 +131,40 @@ | ||
The "rsa-key-1" keyId refers to a private key known to the client and a public | ||
key known to the server. The "hmac-key-1" keyId refers to key known to the | ||
client and server. | ||
## Default parameterization | ||
Authorization: Signature keyId="123" Base64(RSA-SHA256(Tue, 07 Jun 2011 20:51:35 GMT)) | ||
The authorization header and signature would be generated as: | ||
Authorization: Signature keyId="rsa-key-1",algorithm="rsa-sha256",signature="Base64(RSA-SHA256(signing string))" | ||
The client would compose the signing string as: | ||
date: Tue, 07 Jun 2011 20:51:35 GMT | ||
## Header List | ||
Authorization: Signature keyId="123",headers="content-type Date content-md5" Base64(RSA-SHA256(Tue, 07 Jun 2011 20:51:35 GMT)) | ||
The authorization header and signature would be generated as: | ||
Authorization: Signature keyId="rsa-key-1",algorithm="rsa-sha256",headers="request-line date content-type content-md5",signature="Base64(RSA-SHA256(signing string))" | ||
The client would compose the signing string as (`+ "\n"` inserted for | ||
readability): | ||
application/json + "\n" | ||
Tue, 07 Jun 2011 20:51:35 GMT + "\n" | ||
h0auK8hnYJKmHTLhKtMTkQ== | ||
POST /foo HTTP/1.1 + "\n" | ||
date: Tue, 07 Jun 2011 20:51:35 GMT + "\n" | ||
content-type: application/json + "\n" | ||
content-md5: h0auK8hnYJKmHTLhKtMTkQ== | ||
## Algorithm | ||
Authorization: Signature keyId="123",algorithm="hmac-sha1" Base64(HMAC-SHA1(Tue, 07 Jun 2011 20:51:35 GMT)) | ||
The authorization header and signature would be generated as: | ||
Authorization: Signature keyId="hmac-key-1",algorithm="hmac-sha1",signature="Base64(HMAC-SHA1(signing string))" | ||
The client would compose the signing string as: | ||
date: Tue, 07 Jun 2011 20:51:35 GMT | ||
# Signing Algorithms | ||
@@ -151,2 +177,3 @@ | ||
* rsa-sha512 | ||
* dsa-sha1 | ||
* hmac-sha1 | ||
@@ -253,27 +280,23 @@ * hmac-sha256 | ||
``` | ||
Thu, 05 Jan 2012 21:31:40 GMT | ||
``` | ||
date: Thu, 05 Jan 2012 21:31:40 GMT | ||
The Authorization header would be: | ||
Authorization: Signature keyId="Test",algorithm="rsa-sha256" MDyO5tSvin5FBVdq3gMBTwtVgE8U/JpzSwFvY7gu7Q2tiZ5TvfHzf/RzmRoYwO8PoV1UGaw6IMwWzxDQkcoYOwvG/w4ljQBBoNusO/mYSvKrbqxUmZi8rNtrMcb82MS33bai5IeLnOGl31W1UbL4qE/wL8U9wCPGRJlCFLsTgD8= | ||
Authorization: Signature keyId="Test",algorithm="rsa-sha256",signature="JldXnt8W9t643M2Sce10gqCh/+E7QIYLiI+bSjnFBGCti7s+mPPvOjVb72sbd1FjeOUwPTDpKbrQQORrm+xBYfAwCxF3LBSSzORvyJ5nRFCFxfJ3nlQD6Kdxhw8wrVZX5nSem4A/W3C8qH5uhFTRwF4ruRjh+ENHWuovPgO/HGQ=" | ||
### All Headers | ||
Parameterized to include all headers, the string to sign would be: | ||
Parameterized to include all headers, the string to sign would be (`+ "\n"` | ||
inserted for readability): | ||
``` | ||
/foo?param=value&pet=dog HTTP/1.1 | ||
example.com | ||
Thu, 05 Jan 2012 21:31:40 GMT | ||
application/json | ||
Sd/dVLAcvNLSq16eXua5uQ== | ||
18 | ||
``` | ||
POST /foo?param=value&pet=dog HTTP/1.1 + "\n" | ||
host: example.com + "\n" | ||
date: Thu, 05 Jan 2012 21:31:40 GMT + "\n" | ||
content-type: application/json + "\n" | ||
content-md5: Sd/dVLAcvNLSq16eXua5uQ== + "\n" | ||
content-length: 18 | ||
The Authorization header would be: | ||
Authorization: Signature | ||
keyId="Test",algorithm="rsa-sha256",headers="request-line host date content-type content-md5 content-length" gVrKP7wVh1+FmWbNlhj0pNXIe9XmeOA6EcnoOKAvUILnwaMFzaKaam9UmeDPwjC9TdT+jSRqjtyZE49kZcSpYAHxGlPQ4ziXFRfPprlN/3Xwg3sUOGqbBiS3WFuY3QOOWv4tzc5p70g74U/QvHNNiYMcjoz89vRJhefbFSNwCDs= | ||
Authorization: Signature keyId="Test",algorithm="rsa-sha256",headers="request-line host date content-type content-md5 content-length",signature="Gm7W/r+e90REDpWytALMrft4MqZxCmslOTOvwJX17ViEBA5E65QqvWI0vIH3l/vSsGiaMVmuUgzYsJLYMLcm5dGrv1+a+0fCoUdVKPZWHyImQEqpLkopVwqEH67LVECFBqFTAKlQgBn676zrfXQbb+b/VebAsNUtvQMe6cTjnDY=" | ||
@@ -22,4 +22,3 @@ // Copyright 2012 Joyent, Inc. All rights reserved. | ||
New: 0, | ||
Params: 1, | ||
Signature: 2 | ||
Params: 1 | ||
}; | ||
@@ -29,3 +28,5 @@ | ||
Name: 0, | ||
Value: 1 | ||
Quote: 1, | ||
Value: 2, | ||
Comma: 3 | ||
}; | ||
@@ -93,5 +94,5 @@ | ||
* "content-md5" | ||
* ] | ||
* ], | ||
* "signature": "base64" | ||
* }, | ||
* "signature": "base64", | ||
* "signingString": "ready to be passed to crypto.verify()" | ||
@@ -144,3 +145,2 @@ * } | ||
params: {}, | ||
signature: '', | ||
signingString: '', | ||
@@ -173,10 +173,22 @@ | ||
case ParamsState.Name: | ||
var code = c.charCodeAt(0); | ||
// restricted name of A-Z / a-z | ||
if ((code >= 0x41 && code <= 0x5a) || // A-Z | ||
(code >= 0x61 && code <= 0x7a)) { // a-z | ||
tmpName += c; | ||
} else if (c === '=') { | ||
if (tmpName.length === 0) | ||
throw new InvalidHeaderError('bad param format'); | ||
substate = ParamsState.Quote; | ||
} else { | ||
throw new InvalidHeaderError('bad param format'); | ||
} | ||
break; | ||
case ParamsState.Quote: | ||
if (c === '"') { | ||
parsed.params[tmpName] = ''; | ||
tmpValue = ''; | ||
substate = ParamsState.Value; | ||
} else if (c === ' ') { | ||
state = State.Signature; | ||
} else if (c !== '=' && c !== ',') { | ||
tmpName += c; | ||
} else { | ||
throw new InvalidHeaderError('bad param format'); | ||
} | ||
@@ -188,6 +200,14 @@ break; | ||
parsed.params[tmpName] = tmpValue; | ||
substate = ParamsState.Comma; | ||
} else { | ||
tmpValue += c; | ||
} | ||
break; | ||
case ParamsState.Comma: | ||
if (c === ',') { | ||
tmpName = ''; | ||
substate = ParamsState.Name; | ||
} else { | ||
tmpValue += c; | ||
throw new InvalidHeaderError('bad param format'); | ||
} | ||
@@ -201,7 +221,2 @@ break; | ||
case State.Signature: | ||
parsed.signature += c; | ||
break; | ||
default: | ||
@@ -233,4 +248,4 @@ throw new Error('Invalid substate'); | ||
if (!parsed.signature) | ||
throw new InvalidHeaderError('signature was empty'); | ||
if (!parsed.params.signature) | ||
throw new InvalidHeaderError('signature was not specified'); | ||
@@ -248,13 +263,12 @@ // Check the algorithm against the official list | ||
var value; | ||
if (h !== 'request-line') { | ||
value = request.headers[h]; | ||
var value = request.headers[h]; | ||
if (!value) | ||
throw new MissingHeaderError(h + ' was not in the request'); | ||
parsed.signingString += h + ': ' + value; | ||
} else { | ||
value = | ||
parsed.signingString += | ||
request.method + ' ' + request.url + ' HTTP/' + request.httpVersion; | ||
} | ||
parsed.signingString += value; | ||
if ((i + 1) < parsed.params.headers.length) | ||
@@ -261,0 +275,0 @@ parsed.signingString += '\n'; |
@@ -23,3 +23,4 @@ // Copyright 2012 Joyent, Inc. All rights reserved. | ||
var Authorization = 'Signature keyId="%s",algorithm="%s",headers="%s" %s'; | ||
var Authorization = | ||
'Signature keyId="%s",algorithm="%s",headers="%s",signature="%s"'; | ||
@@ -105,2 +106,3 @@ | ||
* - {String} algorithm optional; defaults to 'rsa-sha256'. | ||
* - {String} httpVersion optional; defaults to '1.1'. | ||
* @return {Boolean} true if Authorization (and optionally Date) were added. | ||
@@ -118,2 +120,3 @@ * @throws {TypeError} on bad parameter types (input). | ||
assert.optionalArrayOfString(options.headers, 'options.headers'); | ||
assert.optionalString(options.httpVersion, 'options.httpVersion'); | ||
@@ -126,2 +129,4 @@ if (!request.getHeader('Date')) | ||
options.algorithm = 'rsa-sha256'; | ||
if (!options.httpVersion) | ||
options.httpVersion = '1.1'; | ||
@@ -140,14 +145,15 @@ options.algorithm = options.algorithm.toLowerCase(); | ||
var h = options.headers[i].toLowerCase(); | ||
request.getHeader(h); | ||
var value = request.getHeader(h); | ||
if (!value) { | ||
if (h === 'request-line') { | ||
value = request.method + ' ' + request.path + ' HTTP/1.1'; | ||
} else { | ||
if (h !== 'request-line') { | ||
var value = request.getHeader(h); | ||
if (!value) { | ||
throw new MissingHeaderError(h + ' was not in the request'); | ||
} | ||
stringToSign += h + ': ' + value; | ||
} else { | ||
value = | ||
stringToSign += | ||
request.method + ' ' + request.path + ' HTTP/' + options.httpVersion; | ||
} | ||
stringToSign += value; | ||
if ((i + 1) < options.headers.length) | ||
@@ -154,0 +160,0 @@ stringToSign += '\n'; |
@@ -32,9 +32,9 @@ // Copyright 2011 Joyent, Inc. All rights reserved. | ||
if (alg[1] === 'HMAC') { | ||
var hmac = crypto.createHmac(alg[2].toLowerCase(), key); | ||
var hmac = crypto.createHmac(alg[2].toUpperCase(), key); | ||
hmac.update(parsedSignature.signingString); | ||
return (hmac.digest('base64') === parsedSignature.signature); | ||
return (hmac.digest('base64') === parsedSignature.params.signature); | ||
} else { | ||
var verify = crypto.createVerify(alg[0]); | ||
verify.update(parsedSignature.signingString); | ||
return verify.verify(key, parsedSignature.signature, 'base64'); | ||
return verify.verify(key, parsedSignature.params.signature, 'base64'); | ||
} | ||
@@ -41,0 +41,0 @@ } |
@@ -5,3 +5,3 @@ { | ||
"description": "Reference implementation of Joyent's HTTP Signature Scheme", | ||
"version": "0.9.11", | ||
"version": "0.10.0", | ||
"repository": { | ||
@@ -25,4 +25,4 @@ "type": "git", | ||
"node-uuid": "1.4.0", | ||
"tap": "0.3.1" | ||
"tap": "0.4.2" | ||
} | ||
} |
@@ -0,3 +1,5 @@ | ||
# node-http-signature | ||
node-http-signature is a node.js library that has client and server components | ||
for Joyent's `HTTP Signature Scheme`. | ||
for Joyent's [HTTP Signature Scheme](http_signing.md). | ||
@@ -4,0 +6,0 @@ ## Usage |
Sorry, the diff of this file is not supported yet
36192
621
76