diff --git a/lib/signer.js b/lib/signer.js index aa2ef3a..0e281d1 100644 --- a/lib/signer.js +++ b/lib/signer.js @@ -234,7 +234,9 @@ RequestSigner.prototype.sign = function (cb) { cb(e); return; } - alg = (this.rs_alg[0] || this.rs_key.type) + '-' + sigObj.hashAlgorithm; + alg = sigObj.hideAlgorithm ? + 'hs2019' : + (this.rs_alg[0] || this.rs_key.type) + '-' + sigObj.hashAlgorithm; var signature = sigObj.toString(); authz = FormatAuthz('Signature ', { keyId: this.rs_keyId, @@ -310,6 +312,9 @@ module.exports = { * pass to sshpk to parse the privateKey. * This doesn't do anything if algorithm is * HMAC. + * - {Boolean} hideAlgorithm optional; defaults to 'false'. + * if true, hides algorithm by writing "hs2019" + * to signature. * @return {Boolean} true if Authorization (and optionally Date) were added. * @throws {TypeError} on bad parameter types (input). * @throws {InvalidAlgorithmError} if algorithm was bad or incompatible with @@ -328,6 +333,7 @@ module.exports = { assert.optionalString(options.httpVersion, 'options.httpVersion'); assert.optionalNumber(options.expiresIn, 'options.expiresIn'); assert.optionalString(options.keyPassphrase, 'options.keyPassphrase'); + assert.optionalBool(options.hideAlgorithm, 'options.hideAlgorithm'); if (!request.getHeader('Date')) request.setHeader('Date', jsprim.rfc1123(new Date())); @@ -372,7 +378,9 @@ module.exports = { alg[1] = key.defaultHashAlgorithm(); } - options.algorithm = alg[0] + '-' + alg[1]; + options.algorithm = options.hideAlgorithm ? + 'hs2019' : + alg[0] + '-' + alg[1]; } var params = { diff --git a/lib/utils.js b/lib/utils.js index 65a4b8f..fb10440 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -35,9 +35,24 @@ function InvalidAlgorithmError(message) { } util.inherits(InvalidAlgorithmError, HttpSignatureError); -function validateAlgorithm(algorithm) { +/** + * @param algorithm {String} the algorithm of the signature + * @param publicKeyType {String?} fallback algorithm (public key type) for + * hs2019 + * @returns {[string, string]} + */ +function validateAlgorithm(algorithm, publicKeyType) { + assert.string(algorithm, 'algorithm'); + assert.optionalString(publicKeyType, 'publicKeyType'); + var alg = algorithm.toLowerCase().split('-'); + if (alg[0] === 'hs2019') { + return publicKeyType !== undefined ? + validateAlgorithm(publicKeyType + '-sha512') : + ['hs2019', 'sha512']; + } + if (alg.length !== 2) { throw (new InvalidAlgorithmError(alg[0].toUpperCase() + ' is not a ' + 'valid algorithm')); diff --git a/lib/verify.js b/lib/verify.js index 4dba400..362464f 100644 --- a/lib/verify.js +++ b/lib/verify.js @@ -30,9 +30,9 @@ module.exports = { pubkey = sshpk.parseKey(pubkey); assert.ok(sshpk.Key.isKey(pubkey, [1, 1]), 'pubkey must be a sshpk.Key'); - var alg = validateAlgorithm(parsedSignature.algorithm); + var alg = validateAlgorithm(parsedSignature.algorithm, pubkey.type); if (alg[0] === 'hmac' || alg[0] !== pubkey.type) - return (false); + return false; var v = pubkey.createVerify(alg[1]); v.update(parsedSignature.signingString); diff --git a/test/signer.test.js b/test/signer.test.js index 802e422..f5d464a 100644 --- a/test/signer.test.js +++ b/test/signer.test.js @@ -244,6 +244,28 @@ test('signing with unspecified algorithm', function(t) { req.end(); }); +test('hide algorithm (unspecified algorithm)', function(t) { + var req = http.request(httpOptions, function(res) { + t.end(); + }); + var opts = { + keyId: 'unit', + key: rsaPrivate, + headers: ['date', '(algorithm)'], + hideAlgorithm: true, + }; + + req._stringToSign = null; + t.ok(httpSignature.sign(req, opts)); + t.ok(req.getHeader('Authorization')); + t.strictEqual(typeof (opts.algorithm), 'string'); + t.strictEqual(opts.algorithm, 'hs2019'); + t.strictEqual(typeof (req._stringToSign), 'string'); + t.ok(req._stringToSign.match(/^date: [^\n]*\n\(algorithm\): [^\n]*$/)); + console.log('> ' + req.getHeader('Authorization')); + req.end(); +}); + test('signing opaque param', function(t) { var req = http.request(httpOptions, function(res) { t.end(); diff --git a/test/verify.test.js b/test/verify.test.js index 19dd062..16c0e0b 100644 --- a/test/verify.test.js +++ b/test/verify.test.js @@ -292,6 +292,123 @@ test('valid ecdsa', function(t) { }); +test('invalid hs2019', function(t) { + server.tester = function(req, res) { + var parsed = httpSignature.parseRequest(req); + t.ok(!httpSignature.verify(parsed, ecdsaPublic)); + + res.writeHead(200); + res.write(JSON.stringify(parsed, null, 2)); + res.end(); + }; + + options.headers.Date = jsprim.rfc1123(new Date()); + options.headers.Authorization = + 'Signature keyId="foo",algorithm="hs2019",signature="' + + uuid() + '"'; + + http.get(options, function(res) { + t.equal(res.statusCode, 200); + t.end(); + }); +}); + +test('invalid hs2019 (valid ecdsa-sha256)', function(t) { + server.tester = function(req, res) { + var parsed = httpSignature.parseRequest(req); + t.ok(!httpSignature.verify(parsed, ecdsaPublic)); + + res.writeHead(200); + res.write(JSON.stringify(parsed, null, 2)); + res.end(); + }; + + options.headers.Date = jsprim.rfc1123(new Date()); + var key = sshpk.parsePrivateKey(ecdsaPrivate); + var signer = key.createSign('sha256'); + signer.update('date: ' + options.headers.Date); + options.headers.Authorization = + 'Signature keyId="foo",algorithm="hs2019",signature="' + + signer.sign().toString() + '"'; + + http.get(options, function(res) { + t.equal(res.statusCode, 200); + t.end(); + }); +}); + +test('valid hs2019 (valid ecdsa-sha512)', function(t) { + server.tester = function(req, res) { + var parsed = httpSignature.parseRequest(req); + t.ok(httpSignature.verify(parsed, ecdsaPublic)); + + res.writeHead(200); + res.write(JSON.stringify(parsed, null, 2)); + res.end(); + }; + + options.headers.Date = jsprim.rfc1123(new Date()); + var key = sshpk.parsePrivateKey(ecdsaPrivate); + var signer = key.createSign('sha512'); + signer.update('date: ' + options.headers.Date); + options.headers.Authorization = + 'Signature keyId="foo",algorithm="hs2019",signature="' + + signer.sign().toString() + '"'; + + http.get(options, function(res) { + t.equal(res.statusCode, 200); + t.end(); + }); +}); + +test('valid hs2019 (valid dsa-sha512)', function(t) { + server.tester = function(req, res) { + var parsed = httpSignature.parseRequest(req); + t.ok(httpSignature.verify(parsed, dsaPublic)); + + res.writeHead(200); + res.write(JSON.stringify(parsed, null, 2)); + res.end(); + }; + + options.headers.Date = jsprim.rfc1123(new Date()); + var key = sshpk.parsePrivateKey(dsaPrivate); + var signer = key.createSign('sha512'); + signer.update('date: ' + options.headers.Date); + options.headers.Authorization = + 'Signature keyId="foo",algorithm="hs2019",signature="' + + signer.sign().toString() + '"'; + + http.get(options, function(res) { + t.equal(res.statusCode, 200); + t.end(); + }); +}); + +test('valid hs2019 (valid rsa-sha512)', function(t) { + server.tester = function(req, res) { + var parsed = httpSignature.parseRequest(req); + t.ok(httpSignature.verify(parsed, rsaPublic)); + + res.writeHead(200); + res.write(JSON.stringify(parsed, null, 2)); + res.end(); + }; + + options.headers.Date = jsprim.rfc1123(new Date()); + var signer = crypto.createSign('RSA-SHA512'); + signer.update('date: ' + options.headers.Date); + options.headers.Authorization = + 'Signature keyId="foo",algorithm="hs2019",signature="' + + signer.sign(rsaPrivate, 'base64') + '"'; + + http.get(options, function(res) { + t.equal(res.statusCode, 200); + t.end(); + }); +}); + + test('invalid date', function(t) { server.tester = function(req, res) { t.throws(function() {