http-signature
Advanced tools
Comparing version 1.3.2 to 1.3.3
@@ -7,2 +7,7 @@ # node-http-signature changelog | ||
## 1.3.3 | ||
- Add support for an opaque param in the Authorization header (#101) | ||
- Add support for adding the keyId and algorithm params into the signing string (#100) | ||
## 1.3.2 | ||
@@ -9,0 +14,0 @@ |
@@ -113,13 +113,14 @@ // Copyright 2012 Joyent, Inc. All rights reserved. | ||
} | ||
if (options.headers === undefined) { | ||
options.headers = [request.headers['x-date'] ? 'x-date' : 'date']; | ||
} | ||
assert.object(options, 'options'); | ||
assert.arrayOfString(options.headers, 'options.headers'); | ||
assert.optionalFinite(options.clockSkew, 'options.clockSkew'); | ||
var headers = request.headers; | ||
var headers = [request.headers['x-date'] ? 'x-date' : 'date']; | ||
if (options.headers !== undefined) { | ||
assert.arrayOfString(headers, 'options.headers'); | ||
headers = options.headers; | ||
} | ||
var authzHeaderName = options.authorizationHeaderName; | ||
var authz = headers[authzHeaderName] || headers[utils.HEADER.AUTH] || | ||
headers[utils.HEADER.SIG]; | ||
var authz = request.headers[authzHeaderName] || | ||
request.headers[utils.HEADER.AUTH] || request.headers[utils.HEADER.SIG]; | ||
@@ -138,3 +139,4 @@ if (!authz) { | ||
var i = 0; | ||
var state = authz === headers[utils.HEADER.SIG] ? State.Params : State.New; | ||
var state = authz === request.headers[utils.HEADER.SIG] ? | ||
State.Params : State.New; | ||
var substate = ParamsState.Name; | ||
@@ -145,3 +147,3 @@ var tmpName = ''; | ||
var parsed = { | ||
scheme: authz === headers[utils.HEADER.SIG] ? 'Signature' : '', | ||
scheme: authz === request.headers[utils.HEADER.SIG] ? 'Signature' : '', | ||
params: {}, | ||
@@ -241,3 +243,2 @@ signingString: '' | ||
// Check the algorithm against the official list | ||
parsed.params.algorithm = parsed.params.algorithm.toLowerCase(); | ||
try { | ||
@@ -275,2 +276,13 @@ validateAlgorithm(parsed.params.algorithm); | ||
request.url; | ||
} else if (h === '(keyid)') { | ||
parsed.signingString += '(keyid): ' + parsed.params.keyId; | ||
} else if (h === '(algorithm)') { | ||
parsed.signingString += '(algorithm): ' + parsed.params.algorithm; | ||
} else if (h === '(opaque)') { | ||
var opaque = parsed.params.opaque; | ||
if (opaque === undefined) { | ||
throw new MissingHeaderError('opaque param was not in the ' + | ||
authzHeaderName + ' header'); | ||
} | ||
parsed.signingString += '(opaque): ' + opaque; | ||
} else { | ||
@@ -306,3 +318,3 @@ var value = request.headers[h]; | ||
options.headers.forEach(function (hdr) { | ||
headers.forEach(function (hdr) { | ||
// Remember that we already checked any headers in the params | ||
@@ -314,2 +326,3 @@ // were in the request, so if this passes we're good. | ||
parsed.params.algorithm = parsed.params.algorithm.toLowerCase(); | ||
if (options.algorithms) { | ||
@@ -323,2 +336,3 @@ if (options.algorithms.indexOf(parsed.params.algorithm) === -1) | ||
parsed.keyId = parsed.params.keyId; | ||
parsed.opaque = parsed.params.opaque; | ||
return parsed; | ||
@@ -325,0 +339,0 @@ } |
@@ -20,7 +20,4 @@ // Copyright 2012 Joyent, Inc. All rights reserved. | ||
var AUTHZ_FMT = | ||
'Signature keyId="%s",algorithm="%s",headers="%s",signature="%s"'; | ||
var AUTHZ_PARAMS = [ 'keyId', 'algorithm', 'opaque', 'headers', 'signature' ]; | ||
var SIGNATURE_FMT = 'keyId="%s",algorithm="%s",headers="%s",signature="%s"'; | ||
///--- Specific Errors | ||
@@ -38,2 +35,21 @@ | ||
function FormatAuthz(prefix, params) { | ||
assert.string(prefix, 'prefix'); | ||
assert.object(params, 'params'); | ||
var authz = ''; | ||
for (var i = 0; i < AUTHZ_PARAMS.length; i++) { | ||
var param = AUTHZ_PARAMS[i]; | ||
var value = params[param]; | ||
if (value === undefined) | ||
continue; | ||
assert.string(value, 'params.' + param); | ||
authz += prefix + sprintf('%s="%s"', param, value); | ||
prefix = ','; | ||
} | ||
return (authz); | ||
} | ||
/* See createSigner() */ | ||
@@ -195,7 +211,8 @@ function RequestSigner(options) { | ||
authz = sprintf(AUTHZ_FMT, | ||
sig.keyId, | ||
sig.algorithm, | ||
self.rs_headers.join(' '), | ||
sig.signature); | ||
authz = FormatAuthz('Signature ', { | ||
keyId: sig.keyId, | ||
algorithm: sig.keyId, | ||
headers: self.rs_headers.join(' '), | ||
signature: sig.signature | ||
}); | ||
} catch (e) { | ||
@@ -217,7 +234,8 @@ cb(e); | ||
var signature = sigObj.toString(); | ||
authz = sprintf(AUTHZ_FMT, | ||
this.rs_keyId, | ||
alg, | ||
this.rs_headers.join(' '), | ||
signature); | ||
authz = FormatAuthz('Signature ', { | ||
keyId: this.rs_keyId, | ||
algorithm: alg, | ||
headers: this.rs_headers.join(' '), | ||
signature: signature | ||
}); | ||
cb(null, authz); | ||
@@ -296,2 +314,3 @@ } | ||
assert.string(options.keyId, 'options.keyId'); | ||
assert.optionalString(options.opaque, 'options.opaque'); | ||
assert.optionalArrayOfString(options.headers, 'options.headers'); | ||
@@ -302,4 +321,5 @@ assert.optionalString(options.httpVersion, 'options.httpVersion'); | ||
request.setHeader('Date', jsprim.rfc1123(new Date())); | ||
if (!options.headers) | ||
options.headers = ['date']; | ||
var headers = ['date']; | ||
if (options.headers) | ||
headers = options.headers; | ||
if (!options.httpVersion) | ||
@@ -314,9 +334,39 @@ options.httpVersion = '1.1'; | ||
var key = options.key; | ||
if (alg[0] === 'hmac') { | ||
if (typeof (key) !== 'string' && !Buffer.isBuffer(key)) | ||
throw (new TypeError('options.key must be a string or Buffer')); | ||
} else { | ||
if (typeof (key) === 'string' || Buffer.isBuffer(key)) | ||
key = sshpk.parsePrivateKey(options.key); | ||
assert.ok(sshpk.PrivateKey.isPrivateKey(key, [1, 2]), | ||
'options.key must be a sshpk.PrivateKey'); | ||
if (!PK_ALGOS[key.type]) { | ||
throw (new InvalidAlgorithmError(key.type.toUpperCase() + ' type ' + | ||
'keys are not supported')); | ||
} | ||
if (alg[0] === undefined) { | ||
alg[0] = key.type; | ||
} else if (key.type !== alg[0]) { | ||
throw (new InvalidAlgorithmError('options.key must be a ' + | ||
alg[0].toUpperCase() + ' key, was given a ' + | ||
key.type.toUpperCase() + ' key instead')); | ||
} | ||
if (alg[1] === undefined) { | ||
alg[1] = key.defaultHashAlgorithm(); | ||
} | ||
options.algorithm = alg[0] + '-' + alg[1]; | ||
} | ||
var i; | ||
var stringToSign = ''; | ||
for (i = 0; i < options.headers.length; i++) { | ||
if (typeof (options.headers[i]) !== 'string') | ||
for (i = 0; i < headers.length; i++) { | ||
if (typeof (headers[i]) !== 'string') | ||
throw new TypeError('options.headers must be an array of Strings'); | ||
var h = options.headers[i].toLowerCase(); | ||
var h = headers[i].toLowerCase(); | ||
@@ -341,2 +391,12 @@ if (h === 'request-line') { | ||
request.path; | ||
} else if (h === '(keyid)') { | ||
stringToSign += '(keyid): ' + options.keyId; | ||
} else if (h === '(algorithm)') { | ||
stringToSign += '(algorithm): ' + options.algorithm; | ||
} else if (h === '(opaque)') { | ||
var opaque = options.opaque; | ||
if (opaque == undefined || opaque === '') { | ||
throw new MissingHeaderError('options.opaque was not in the request'); | ||
} | ||
stringToSign += '(opaque): ' + opaque; | ||
} else { | ||
@@ -350,3 +410,3 @@ var value = request.getHeader(h); | ||
if ((i + 1) < options.headers.length) | ||
if ((i + 1) < headers.length) | ||
stringToSign += '\n'; | ||
@@ -362,28 +422,6 @@ } | ||
if (alg[0] === 'hmac') { | ||
if (typeof (options.key) !== 'string' && !Buffer.isBuffer(options.key)) | ||
throw (new TypeError('options.key must be a string or Buffer')); | ||
var hmac = crypto.createHmac(alg[1].toUpperCase(), options.key); | ||
var hmac = crypto.createHmac(alg[1].toUpperCase(), key); | ||
hmac.update(stringToSign); | ||
signature = hmac.digest('base64'); | ||
} else { | ||
var key = options.key; | ||
if (typeof (key) === 'string' || Buffer.isBuffer(key)) | ||
key = sshpk.parsePrivateKey(options.key); | ||
assert.ok(sshpk.PrivateKey.isPrivateKey(key, [1, 2]), | ||
'options.key must be a sshpk.PrivateKey'); | ||
if (!PK_ALGOS[key.type]) { | ||
throw (new InvalidAlgorithmError(key.type.toUpperCase() + ' type ' + | ||
'keys are not supported')); | ||
} | ||
if (alg[0] !== undefined && key.type !== alg[0]) { | ||
throw (new InvalidAlgorithmError('options.key must be a ' + | ||
alg[0].toUpperCase() + ' key, was given a ' + | ||
key.type.toUpperCase() + ' key instead')); | ||
} | ||
var signer = key.createSign(alg[1]); | ||
@@ -396,3 +434,4 @@ signer.update(stringToSign); | ||
} | ||
options.algorithm = key.type + '-' + sigObj.hashAlgorithm; | ||
assert.strictEqual(alg[1], sigObj.hashAlgorithm, | ||
'hash algorithm mismatch'); | ||
signature = sigObj.toString(); | ||
@@ -403,11 +442,16 @@ assert.notStrictEqual(signature, '', 'empty signature produced'); | ||
var authzHeaderName = options.authorizationHeaderName || 'Authorization'; | ||
var prefix = authzHeaderName.toLowerCase() === utils.HEADER.SIG ? | ||
'' : 'Signature '; | ||
var FMT = authzHeaderName.toLowerCase() === utils.HEADER.SIG ? | ||
SIGNATURE_FMT : AUTHZ_FMT; | ||
var params = { | ||
'keyId': options.keyId, | ||
'algorithm': options.algorithm, | ||
'signature': signature | ||
}; | ||
if (options.opaque) | ||
params.opaque = options.opaque; | ||
if (options.headers) | ||
params.headers = options.headers.join(' '); | ||
request.setHeader(authzHeaderName, sprintf(FMT, | ||
options.keyId, | ||
options.algorithm, | ||
options.headers.join(' '), | ||
signature)); | ||
request.setHeader(authzHeaderName, FormatAuthz(prefix, params)); | ||
@@ -414,0 +458,0 @@ return true; |
{ | ||
"name": "http-signature", | ||
"description": "Reference implementation of Joyent's HTTP Signature scheme.", | ||
"version": "1.3.2", | ||
"version": "1.3.3", | ||
"license": "MIT", | ||
@@ -6,0 +6,0 @@ "author": "Joyent, Inc", |
37260
857