http-signature
Advanced tools
Comparing version 0.11.0 to 1.0.0
@@ -123,6 +123,6 @@ # Abstract | ||
Host: example.org | ||
Date: Tue, 07 Jun 2011 20:51:35 GMT | ||
Date: Tue, 07 Jun 2014 20:51:35 GMT | ||
Content-Type: application/json | ||
Content-MD5: h0auK8hnYJKmHTLhKtMTkQ== | ||
Content-Length: 123 | ||
Digest: SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE= | ||
Content-Length: 18 | ||
@@ -141,3 +141,3 @@ The "rsa-key-1" keyId refers to a private key known to the client and a public | ||
date: Tue, 07 Jun 2011 20:51:35 GMT | ||
date: Tue, 07 Jun 2014 20:51:35 GMT | ||
@@ -148,3 +148,3 @@ ## Header List | ||
Authorization: Signature keyId="rsa-key-1",algorithm="rsa-sha256",headers="request-line date content-type content-md5",signature="Base64(RSA-SHA256(signing string))" | ||
Authorization: Signature keyId="rsa-key-1",algorithm="rsa-sha256",headers="(request-target) date content-type digest",signature="Base64(RSA-SHA256(signing string))" | ||
@@ -154,6 +154,6 @@ The client would compose the signing string as (`+ "\n"` inserted for | ||
POST /foo HTTP/1.1 + "\n" | ||
(request-target) post /foo + "\n" | ||
date: Tue, 07 Jun 2011 20:51:35 GMT + "\n" | ||
content-type: application/json + "\n" | ||
content-md5: h0auK8hnYJKmHTLhKtMTkQ== | ||
digest: SHA-256=Base64(SHA256(Body)) | ||
@@ -268,5 +268,5 @@ ## Algorithm | ||
Host: example.com | ||
Date: Thu, 05 Jan 2012 21:31:40 GMT | ||
Date: Thu, 05 Jan 2014 21:31:40 GMT | ||
Content-Type: application/json | ||
Content-MD5: Sd/dVLAcvNLSq16eXua5uQ== | ||
Digest: SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE= | ||
Content-Length: 18 | ||
@@ -280,3 +280,3 @@ | ||
date: Thu, 05 Jan 2012 21:31:40 GMT | ||
date: Thu, 05 Jan 2014 21:31:40 GMT | ||
@@ -292,7 +292,7 @@ The Authorization header would be: | ||
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" | ||
(request-target): post /foo?param=value&pet=dog | ||
host: example.com | ||
date: Thu, 05 Jan 2014 21:31:40 GMT | ||
content-type: application/json | ||
digest: SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE= | ||
content-length: 18 | ||
@@ -302,3 +302,2 @@ | ||
Authorization: Signature keyId="Test",algorithm="rsa-sha256",headers="request-line host date content-type content-md5 content-length",signature="H/AaTDkJvLELy4i1RujnKlS6dm8QWiJvEpn9cKRMi49kKF+mohZ15z1r+mF+XiKS5kOOscyS83olfBtsVhYjPg2Ei3/D9D4Mvb7bFm9IaLJgYTFFuQCghrKQQFPiqJN320emjHxFowpIm1BkstnEU7lktH/XdXVBo8a6Uteiztw=" | ||
Authorization: Signature keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date content-type digest content-length",signature="jgSqYK0yKclIHfF9zdApVEbDp5eqj8C4i4X76pE+XHoxugXv7qnVrGR+30bmBgtpR39I4utq17s9ghz/2QFVxlnToYAvbSVZJ9ulLd1HQBugO0jOyn9sXOtcN7uNHBjqNCqUsnt0sw/cJA6B6nJZpyNqNyAXKdxZZItOuhIs78w=" |
@@ -6,3 +6,3 @@ // Copyright 2015 Joyent, Inc. | ||
var verify = require('./verify'); | ||
var util = require('./util'); | ||
var utils = require('./utils'); | ||
@@ -20,6 +20,8 @@ | ||
signRequest: signer.signRequest, | ||
createSigner: signer.createSigner, | ||
isSigner: signer.isSigner, | ||
sshKeyToPEM: util.sshKeyToPEM, | ||
sshKeyFingerprint: util.fingerprint, | ||
pemToRsaSSHKey: util.pemToRsaSSHKey, | ||
sshKeyToPEM: utils.sshKeyToPEM, | ||
sshKeyFingerprint: utils.fingerprint, | ||
pemToRsaSSHKey: utils.pemToRsaSSHKey, | ||
@@ -26,0 +28,0 @@ verify: verify.verifySignature, |
@@ -5,2 +5,3 @@ // Copyright 2012 Joyent, Inc. All rights reserved. | ||
var util = require('util'); | ||
var utils = require('./utils'); | ||
@@ -11,11 +12,7 @@ | ||
var Algorithms = { | ||
'rsa-sha1': true, | ||
'rsa-sha256': true, | ||
'rsa-sha512': true, | ||
'dsa-sha1': true, | ||
'hmac-sha1': true, | ||
'hmac-sha256': true, | ||
'hmac-sha512': true | ||
}; | ||
var HASH_ALGOS = utils.HASH_ALGOS; | ||
var PK_ALGOS = utils.PK_ALGOS; | ||
var HttpSignatureError = utils.HttpSignatureError; | ||
var InvalidAlgorithmError = utils.InvalidAlgorithmError; | ||
var validateAlgorithm = utils.validateAlgorithm; | ||
@@ -35,14 +32,5 @@ var State = { | ||
///--- Specific Errors | ||
function HttpSignatureError(message, caller) { | ||
if (Error.captureStackTrace) | ||
Error.captureStackTrace(this, caller || HttpSignatureError); | ||
this.message = message; | ||
this.name = caller.name; | ||
} | ||
util.inherits(HttpSignatureError, Error); | ||
function ExpiredRequestError(message) { | ||
@@ -71,4 +59,7 @@ HttpSignatureError.call(this, message, ExpiredRequestError); | ||
function StrictParsingError(message) { | ||
HttpSignatureError.call(this, message, StrictParsingError); | ||
} | ||
util.inherits(StrictParsingError, HttpSignatureError); | ||
///--- Exported API | ||
@@ -95,3 +86,3 @@ | ||
* "date" or "x-date", | ||
* "content-md5" | ||
* "digest" | ||
* ], | ||
@@ -108,2 +99,4 @@ * "signature": "base64" | ||
* - algorithms: algorithms to support (default: all). | ||
* - strict: should enforce latest spec parsing | ||
* (default: false). | ||
* @return {Object} parsed out object (see above). | ||
@@ -117,2 +110,4 @@ * @throws {TypeError} on invalid input. | ||
* in options. | ||
* @throws {StrictParsingError} if old attributes are used in strict parsing | ||
* mode. | ||
* @throws {ExpiredRequestError} if the value of date or x-date exceeds skew. | ||
@@ -158,3 +153,2 @@ */ | ||
} | ||
}; | ||
@@ -254,5 +248,11 @@ | ||
parsed.params.algorithm = parsed.params.algorithm.toLowerCase(); | ||
if (!Algorithms[parsed.params.algorithm]) | ||
throw new InvalidParamsError(parsed.params.algorithm + | ||
' is not supported'); | ||
try { | ||
validateAlgorithm(parsed.params.algorithm); | ||
} catch (e) { | ||
if (e instanceof InvalidAlgorithmError) | ||
throw (new InvalidParamsError(parsed.params.algorithm + ' is not ' + | ||
'supported')); | ||
else | ||
throw (e); | ||
} | ||
@@ -264,10 +264,24 @@ // Build the signingString | ||
if (h !== 'request-line') { | ||
if (h === 'request-line') { | ||
if (!options.strict) { | ||
/* | ||
* We allow headers from the older spec drafts if strict parsing isn't | ||
* specified in options. | ||
*/ | ||
parsed.signingString += | ||
request.method + ' ' + request.url + ' HTTP/' + request.httpVersion; | ||
} else { | ||
/* Strict parsing doesn't allow older draft headers. */ | ||
throw (new StrictParsingError('request-line is not a valid header ' + | ||
'with strict parsing enabled.')); | ||
} | ||
} else if (h === '(request-target)') { | ||
parsed.signingString += | ||
'(request-target): ' + request.method.toLowerCase() + ' ' + | ||
request.url; | ||
} else { | ||
var value = request.headers[h]; | ||
if (!value) | ||
if (value === undefined) | ||
throw new MissingHeaderError(h + ' was not in the request'); | ||
parsed.signingString += h + ': ' + value; | ||
} else { | ||
parsed.signingString += | ||
request.method + ' ' + request.url + ' HTTP/' + request.httpVersion; | ||
} | ||
@@ -274,0 +288,0 @@ |
@@ -6,86 +6,250 @@ // Copyright 2012 Joyent, Inc. All rights reserved. | ||
var http = require('http'); | ||
var util = require('util'); | ||
var sshpk = require('sshpk'); | ||
var jsprim = require('jsprim'); | ||
var utils = require('./utils'); | ||
var sprintf = require('util').format; | ||
var HASH_ALGOS = utils.HASH_ALGOS; | ||
var PK_ALGOS = utils.PK_ALGOS; | ||
var InvalidAlgorithmError = utils.InvalidAlgorithmError; | ||
var HttpSignatureError = utils.HttpSignatureError; | ||
var validateAlgorithm = utils.validateAlgorithm; | ||
///--- Globals | ||
var Algorithms = { | ||
'rsa-sha1': true, | ||
'rsa-sha256': true, | ||
'rsa-sha512': true, | ||
'dsa-sha1': true, | ||
'hmac-sha1': true, | ||
'hmac-sha256': true, | ||
'hmac-sha512': true | ||
}; | ||
var Authorization = | ||
var AUTHZ_FMT = | ||
'Signature keyId="%s",algorithm="%s",headers="%s",signature="%s"'; | ||
///--- Specific Errors | ||
function MissingHeaderError(message) { | ||
this.name = 'MissingHeaderError'; | ||
this.message = message; | ||
this.stack = (new Error()).stack; | ||
HttpSignatureError.call(this, message, MissingHeaderError); | ||
} | ||
MissingHeaderError.prototype = new Error(); | ||
util.inherits(MissingHeaderError, HttpSignatureError); | ||
function InvalidAlgorithmError(message) { | ||
this.name = 'InvalidAlgorithmError'; | ||
this.message = message; | ||
this.stack = (new Error()).stack; | ||
function StrictParsingError(message) { | ||
HttpSignatureError.call(this, message, StrictParsingError); | ||
} | ||
InvalidAlgorithmError.prototype = new Error(); | ||
util.inherits(StrictParsingError, HttpSignatureError); | ||
/* See createSigner() */ | ||
function RequestSigner(options) { | ||
assert.object(options, 'options'); | ||
var alg = []; | ||
if (options.algorithm !== undefined) { | ||
assert.string(options.algorithm, 'options.algorithm'); | ||
alg = validateAlgorithm(options.algorithm); | ||
} | ||
this.rs_alg = alg; | ||
///--- Internal Functions | ||
/* | ||
* RequestSigners come in two varieties: ones with an rs_signFunc, and ones | ||
* with an rs_signer. | ||
* | ||
* rs_signFunc-based RequestSigners have to build up their entire signing | ||
* string within the rs_lines array and give it to rs_signFunc as a single | ||
* concat'd blob. rs_signer-based RequestSigners can add a line at a time to | ||
* their signing state by using rs_signer.update(), thus only needing to | ||
* buffer the hash function state and one line at a time. | ||
*/ | ||
if (options.sign !== undefined) { | ||
assert.func(options.sign, 'options.sign'); | ||
this.rs_signFunc = options.sign; | ||
function _pad(val) { | ||
if (parseInt(val, 10) < 10) { | ||
val = '0' + val; | ||
} else if (alg[0] === 'hmac' && options.key !== undefined) { | ||
assert.string(options.keyId, 'options.keyId'); | ||
this.rs_keyId = options.keyId; | ||
if (typeof (options.key) !== 'string' && !Buffer.isBuffer(options.key)) | ||
throw (new TypeError('options.key for HMAC must be a string or Buffer')); | ||
/* | ||
* Make an rs_signer for HMACs, not a rs_signFunc -- HMACs digest their | ||
* data in chunks rather than requiring it all to be given in one go | ||
* at the end, so they are more similar to signers than signFuncs. | ||
*/ | ||
this.rs_signer = crypto.createHmac(alg[1].toUpperCase(), options.key); | ||
this.rs_signer.sign = function () { | ||
var digest = this.digest('base64'); | ||
return ({ | ||
hashAlgorithm: alg[1], | ||
toString: function () { return (digest); } | ||
}); | ||
}; | ||
} else if (options.key !== undefined) { | ||
var key = options.key; | ||
if (typeof (key) === 'string' || Buffer.isBuffer(key)) | ||
key = sshpk.parsePrivateKey(key); | ||
assert.ok(key instanceof sshpk.PrivateKey, | ||
'options.key must be a sshpk.PrivateKey'); | ||
this.rs_key = key; | ||
assert.string(options.keyId, 'options.keyId'); | ||
this.rs_keyId = options.keyId; | ||
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')); | ||
} | ||
this.rs_signer = key.createSign(alg[1]); | ||
} else { | ||
throw (new TypeError('options.sign (func) or options.key is required')); | ||
} | ||
return val; | ||
this.rs_headers = []; | ||
this.rs_lines = []; | ||
} | ||
/** | ||
* Adds a header to be signed, with its value, into this signer. | ||
* | ||
* @param {String} header | ||
* @param {String} value | ||
* @return {String} value written | ||
*/ | ||
RequestSigner.prototype.writeHeader = function (header, value) { | ||
assert.string(header, 'header'); | ||
header = header.toLowerCase(); | ||
assert.string(value, 'value'); | ||
function _rfc1123() { | ||
var date = new Date(); | ||
this.rs_headers.push(header); | ||
var months = ['Jan', | ||
'Feb', | ||
'Mar', | ||
'Apr', | ||
'May', | ||
'Jun', | ||
'Jul', | ||
'Aug', | ||
'Sep', | ||
'Oct', | ||
'Nov', | ||
'Dec']; | ||
var days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; | ||
return days[date.getUTCDay()] + ', ' + | ||
_pad(date.getUTCDate()) + ' ' + | ||
months[date.getUTCMonth()] + ' ' + | ||
date.getUTCFullYear() + ' ' + | ||
_pad(date.getUTCHours()) + ':' + | ||
_pad(date.getUTCMinutes()) + ':' + | ||
_pad(date.getUTCSeconds()) + | ||
' GMT'; | ||
} | ||
if (this.rs_signFunc) { | ||
this.rs_lines.push(header + ': ' + value); | ||
} else { | ||
var line = header + ': ' + value; | ||
if (this.rs_headers.length > 0) | ||
line = '\n' + line; | ||
this.rs_signer.update(line); | ||
} | ||
return (value); | ||
}; | ||
/** | ||
* Adds a default Date header, returning its value. | ||
* | ||
* @return {String} | ||
*/ | ||
RequestSigner.prototype.writeDateHeader = function () { | ||
return (this.writeHeader('date', jsprim.rfc1123(new Date()))); | ||
}; | ||
/** | ||
* Adds the request target line to be signed. | ||
* | ||
* @param {String} method, HTTP method (e.g. 'get', 'post', 'put') | ||
* @param {String} path | ||
*/ | ||
RequestSigner.prototype.writeTarget = function (method, path) { | ||
assert.string(method, 'method'); | ||
assert.string(path, 'path'); | ||
method = method.toLowerCase(); | ||
this.writeHeader('(request-target)', method + ' ' + path); | ||
}; | ||
/** | ||
* Calculate the value for the Authorization header on this request | ||
* asynchronously. | ||
* | ||
* @param {Func} callback (err, authz) | ||
*/ | ||
RequestSigner.prototype.sign = function (cb) { | ||
assert.func(cb, 'callback'); | ||
if (this.rs_headers.length < 1) | ||
throw (new Error('At least one header must be signed')); | ||
var alg, authz; | ||
if (this.rs_signFunc) { | ||
var data = this.rs_lines.join('\n'); | ||
var self = this; | ||
this.rs_signFunc(data, function (err, sig) { | ||
if (err) { | ||
cb(err); | ||
return; | ||
} | ||
try { | ||
assert.object(sig, 'signature'); | ||
assert.string(sig.keyId, 'signature.keyId'); | ||
assert.string(sig.algorithm, 'signature.algorithm'); | ||
assert.string(sig.signature, 'signature.signature'); | ||
alg = validateAlgorithm(sig.algorithm); | ||
authz = sprintf(AUTHZ_FMT, | ||
sig.keyId, | ||
sig.algorithm, | ||
self.rs_headers.join(' '), | ||
sig.signature); | ||
} catch (e) { | ||
cb(e); | ||
return; | ||
} | ||
cb(null, authz); | ||
}); | ||
} else { | ||
try { | ||
var sigObj = this.rs_signer.sign(); | ||
} catch (e) { | ||
cb(e); | ||
return; | ||
} | ||
alg = (this.rs_alg[0] || this.rs_key.type) + '-' + sigObj.hashAlgorithm; | ||
var signature = sigObj.toString(); | ||
authz = sprintf(AUTHZ_FMT, | ||
this.rs_keyId, | ||
alg, | ||
this.rs_headers.join(' '), | ||
signature); | ||
cb(null, authz); | ||
} | ||
}; | ||
///--- Exported API | ||
module.exports = { | ||
/** | ||
* Identifies whether a given object is a request signer or not. | ||
* | ||
* @param {Object} object, the object to identify | ||
* @returns {Boolean} | ||
*/ | ||
isSigner: function (obj) { | ||
if (typeof (obj) === 'object' && obj instanceof RequestSigner) | ||
return (true); | ||
return (false); | ||
}, | ||
/** | ||
* Creates a request signer, used to asynchronously build a signature | ||
* for a request (does not have to be an http.ClientRequest). | ||
* | ||
* @param {Object} options, either: | ||
* - {String} keyId | ||
* - {String|Buffer} key | ||
* - {String} algorithm (optional, required for HMAC) | ||
* or: | ||
* - {Func} sign (data, cb) | ||
* @return {RequestSigner} | ||
*/ | ||
createSigner: function createSigner(options) { | ||
return (new RequestSigner(options)); | ||
}, | ||
/** | ||
* Adds an 'Authorization' header to an http.ClientRequest object. | ||
@@ -100,2 +264,7 @@ * | ||
* | ||
* The optional flag indicates whether parsing should use strict enforcement | ||
* of the version draft-cavage-http-signatures-04 of the spec or beyond. | ||
* The default is to be loose and support | ||
* older versions for compatibility. | ||
* | ||
* @param {Object} request an instance of http.ClientRequest. | ||
@@ -106,7 +275,12 @@ * @param {Object} options signing parameters object: | ||
* - {Array} headers optional; defaults to ['date']. | ||
* - {String} algorithm optional; defaults to 'rsa-sha256'. | ||
* - {String} algorithm optional (unless key is HMAC); | ||
* default is the same as the sshpk default | ||
* signing algorithm for the type of key given | ||
* - {String} httpVersion optional; defaults to '1.1'. | ||
* - {Boolean} strict optional; defaults to 'false'. | ||
* @return {Boolean} true if Authorization (and optionally Date) were added. | ||
* @throws {TypeError} on bad parameter types (input). | ||
* @throws {InvalidAlgorithmError} if algorithm was bad. | ||
* @throws {InvalidAlgorithmError} if algorithm was bad or incompatible with | ||
* the given key. | ||
* @throws {sshpk.KeyParseError} if key was bad. | ||
* @throws {MissingHeaderError} if a header to be signed was specified but | ||
@@ -124,15 +298,14 @@ * was not present. | ||
if (!request.getHeader('Date')) | ||
request.setHeader('Date', _rfc1123()); | ||
request.setHeader('Date', jsprim.rfc1123(new Date())); | ||
if (!options.headers) | ||
options.headers = ['date']; | ||
if (!options.algorithm) | ||
options.algorithm = 'rsa-sha256'; | ||
if (!options.httpVersion) | ||
options.httpVersion = '1.1'; | ||
options.algorithm = options.algorithm.toLowerCase(); | ||
var alg = []; | ||
if (options.algorithm) { | ||
options.algorithm = options.algorithm.toLowerCase(); | ||
alg = validateAlgorithm(options.algorithm); | ||
} | ||
if (!Algorithms[options.algorithm]) | ||
throw new InvalidAlgorithmError(options.algorithm + ' is not supported'); | ||
var i; | ||
@@ -146,11 +319,26 @@ var stringToSign = ''; | ||
if (h !== 'request-line') { | ||
if (h === 'request-line') { | ||
if (!options.strict) { | ||
/** | ||
* We allow headers from the older spec drafts if strict parsing isn't | ||
* specified in options. | ||
*/ | ||
stringToSign += | ||
request.method + ' ' + request.path + ' HTTP/' + | ||
options.httpVersion; | ||
} else { | ||
/* Strict parsing doesn't allow older draft headers. */ | ||
throw (new StrictParsingError('request-line is not a valid header ' + | ||
'with strict parsing enabled.')); | ||
} | ||
} else if (h === '(request-target)') { | ||
stringToSign += | ||
'(request-target): ' + request.method.toLowerCase() + ' ' + | ||
request.path; | ||
} else { | ||
var value = request.getHeader(h); | ||
if (!value) { | ||
if (value === undefined || value === '') { | ||
throw new MissingHeaderError(h + ' was not in the request'); | ||
} | ||
stringToSign += h + ': ' + value; | ||
} else { | ||
stringToSign += | ||
request.method + ' ' + request.path + ' HTTP/' + options.httpVersion; | ||
} | ||
@@ -162,15 +350,43 @@ | ||
var alg = options.algorithm.match(/(hmac|rsa)-(\w+)/); | ||
var signature; | ||
if (alg[1] === 'hmac') { | ||
var hmac = crypto.createHmac(alg[2].toUpperCase(), options.key); | ||
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); | ||
hmac.update(stringToSign); | ||
signature = hmac.digest('base64'); | ||
} else { | ||
var signer = crypto.createSign(options.algorithm.toUpperCase()); | ||
var key = options.key; | ||
if (typeof (key) === 'string' || Buffer.isBuffer(key)) | ||
key = sshpk.parsePrivateKey(options.key); | ||
assert.ok(key instanceof sshpk.PrivateKey, | ||
'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]); | ||
signer.update(stringToSign); | ||
signature = signer.sign(options.key, 'base64'); | ||
var sigObj = signer.sign(); | ||
if (!HASH_ALGOS[sigObj.hashAlgorithm]) { | ||
throw (new InvalidAlgorithmError(sigObj.hashAlgorithm.toUpperCase() + | ||
' is not a supported hash algorithm')); | ||
} | ||
options.algorithm = key.type + '-' + sigObj.hashAlgorithm; | ||
signature = sigObj.toString(); | ||
assert.notStrictEqual(signature, '', 'empty signature produced'); | ||
} | ||
request.setHeader('Authorization', sprintf(Authorization, | ||
request.setHeader('Authorization', sprintf(AUTHZ_FMT, | ||
options.keyId, | ||
@@ -177,0 +393,0 @@ options.algorithm, |
@@ -5,5 +5,11 @@ // Copyright 2015 Joyent, Inc. | ||
var crypto = require('crypto'); | ||
var sshpk = require('sshpk'); | ||
var utils = require('./utils'); | ||
var HASH_ALGOS = utils.HASH_ALGOS; | ||
var PK_ALGOS = utils.PK_ALGOS; | ||
var InvalidAlgorithmError = utils.InvalidAlgorithmError; | ||
var HttpSignatureError = utils.HttpSignatureError; | ||
var validateAlgorithm = utils.validateAlgorithm; | ||
///--- Exported API | ||
@@ -20,15 +26,17 @@ | ||
* @throws {TypeError} if you pass in bad arguments. | ||
* @throws {InvalidAlgorithmError} | ||
*/ | ||
verifySignature: function verifySignature(parsedSignature, pubkey) { | ||
assert.object(parsedSignature, 'parsedSignature'); | ||
assert.string(pubkey, 'pubkey'); | ||
if (typeof (pubkey) === 'string' || Buffer.isBuffer(pubkey)) | ||
pubkey = sshpk.parseKey(pubkey); | ||
assert.ok(pubkey instanceof sshpk.Key, 'pubkey must be a sshpk.Key'); | ||
var alg = parsedSignature.algorithm.match(/^(RSA|DSA)-(\w+)/); | ||
if (!alg || alg.length !== 3) | ||
throw new TypeError('parsedSignature: unsupported algorithm ' + | ||
parsedSignature.algorithm); | ||
var alg = validateAlgorithm(parsedSignature.algorithm); | ||
if (alg[0] === 'hmac' || alg[0] !== pubkey.type) | ||
return (false); | ||
var verify = crypto.createVerify(alg[0]); | ||
verify.update(parsedSignature.signingString); | ||
return verify.verify(pubkey, parsedSignature.params.signature, 'base64'); | ||
var v = pubkey.createVerify(alg[1]); | ||
v.update(parsedSignature.signingString); | ||
return (v.verify(parsedSignature.params.signature, 'base64')); | ||
}, | ||
@@ -44,2 +52,3 @@ | ||
* @throws {TypeError} if you pass in bad arguments. | ||
* @throws {InvalidAlgorithmError} | ||
*/ | ||
@@ -50,11 +59,34 @@ verifyHMAC: function verifyHMAC(parsedSignature, secret) { | ||
var alg = parsedSignature.algorithm.match(/^HMAC-(\w+)/); | ||
if (!alg || alg.length !== 2) | ||
throw new TypeError('parsedSignature: unsupported algorithm ' + | ||
parsedSignature.algorithm); | ||
var alg = validateAlgorithm(parsedSignature.algorithm); | ||
if (alg[0] !== 'hmac') | ||
return (false); | ||
var hmac = crypto.createHmac(alg[1].toUpperCase(), secret); | ||
var hashAlg = alg[1].toUpperCase(); | ||
var hmac = crypto.createHmac(hashAlg, secret); | ||
hmac.update(parsedSignature.signingString); | ||
return (hmac.digest('base64') === parsedSignature.params.signature); | ||
/* | ||
* Now double-hash to avoid leaking timing information - there's | ||
* no easy constant-time compare in JS, so we use this approach | ||
* instead. See for more info: | ||
* https://www.isecpartners.com/blog/2011/february/double-hmac- | ||
* verification.aspx | ||
*/ | ||
var h1 = crypto.createHmac(hashAlg, secret); | ||
h1.update(hmac.digest()); | ||
h1 = h1.digest(); | ||
var h2 = crypto.createHmac(hashAlg, secret); | ||
h2.update(new Buffer(parsedSignature.params.signature, 'base64')); | ||
h2 = h2.digest(); | ||
/* Node 0.8 returns strings from .digest(). */ | ||
if (typeof (h1) === 'string') | ||
return (h1 === h2); | ||
/* And node 0.10 lacks the .equals() method on Buffers. */ | ||
if (Buffer.isBuffer(h1) && !h1.equals) | ||
return (h1.toString('binary') === h2.toString('binary')); | ||
return (h1.equals(h2)); | ||
} | ||
}; |
{ | ||
"name": "http-signature", | ||
"description": "Reference implementation of Joyent's HTTP Signature scheme.", | ||
"version": "0.11.0", | ||
"version": "1.0.0", | ||
"license": "MIT", | ||
@@ -23,3 +23,4 @@ "author": "Joyent, Inc", | ||
"engines": { | ||
"node": ">=0.8" | ||
"node": ">=0.8", | ||
"npm": ">=1.3.7" | ||
}, | ||
@@ -32,5 +33,8 @@ "main": "lib/index.js", | ||
"assert-plus": "^0.1.5", | ||
"asn1": "0.1.11", | ||
"ctype": "0.5.3" | ||
"jsprim": "^1.2.0", | ||
"sshpk": "^1.4.0" | ||
}, | ||
"peerDependencies": { | ||
"sshpk": "^1.4.0" | ||
}, | ||
"devDependencies": { | ||
@@ -37,0 +41,0 @@ "node-uuid": "^1.4.1", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
45335
12
788
0
4
3
+ Addedjsprim@^1.2.0
+ Addedsshpk@^1.4.0
+ Addedasn1@0.2.6(transitive)
+ Addedassert-plus@1.0.0(transitive)
+ Addedbcrypt-pbkdf@1.0.2(transitive)
+ Addedcore-util-is@1.0.2(transitive)
+ Addeddashdash@1.14.1(transitive)
+ Addedecc-jsbn@0.1.2(transitive)
+ Addedextsprintf@1.3.0(transitive)
+ Addedgetpass@0.1.7(transitive)
+ Addedjsbn@0.1.1(transitive)
+ Addedjson-schema@0.4.0(transitive)
+ Addedjsprim@1.4.2(transitive)
+ Addedsafer-buffer@2.1.2(transitive)
+ Addedsshpk@1.18.0(transitive)
+ Addedtweetnacl@0.14.5(transitive)
+ Addedverror@1.10.0(transitive)
- Removedasn1@0.1.11
- Removedctype@0.5.3
- Removedasn1@0.1.11(transitive)
- Removedctype@0.5.3(transitive)