Comparing version 0.3.0 to 0.4.0
@@ -12,2 +12,7 @@ // Load modules | ||
// MAC normalization format version | ||
exports.headerVersion = '1'; // Prevent comparison of mac values generated with different normalized string formats | ||
// Supported MAC algorithms | ||
@@ -20,22 +25,25 @@ | ||
exports.calculateMAC = function (key, algorithm, timestamp, nonce, method, uri, host, port, ext) { | ||
/* | ||
options = { | ||
header: 'core', // 'core', 'bewit' | ||
key: 'aoijedoaijsdlaksjdl', | ||
algorithm: 'hmac-sha-256', // 'hmac-sha-1', 'hmac-sha-256' | ||
timestamp: 1357718381034, | ||
nonce: 'd3d345f', | ||
method: 'GET', | ||
uri: '/resource?a=1&b=2', | ||
host: 'example.com', | ||
port: 8080, | ||
ext: 'app-specific-data' | ||
}; | ||
*/ | ||
// Parse request URI | ||
exports.calculateMAC = function (options) { | ||
var url = Url.parse(uri); | ||
var normalized = exports.generateNormalizedString(options); | ||
// Construct normalized req string | ||
var normalized = timestamp + '\n' + | ||
nonce + '\n' + | ||
method.toUpperCase() + '\n' + | ||
url.pathname + (url.search || '') + '\n' + | ||
host.toLowerCase() + '\n' + | ||
port + '\n' + | ||
(ext || '') + '\n'; | ||
// Lookup hash function | ||
var hashMethod = ''; | ||
switch (algorithm) { | ||
switch (options.algorithm) { | ||
@@ -49,3 +57,3 @@ case 'hmac-sha-1': hashMethod = 'sha1'; break; | ||
var hmac = Crypto.createHmac(hashMethod, key).update(normalized); | ||
var hmac = Crypto.createHmac(hashMethod, options.key).update(normalized); | ||
var digest = hmac.digest('base64'); | ||
@@ -55,1 +63,21 @@ return digest; | ||
exports.generateNormalizedString = function (options) { | ||
// Parse request URI | ||
var url = Url.parse(options.uri); | ||
// Construct normalized req string | ||
var normalized = options.header + '.' + exports.headerVersion + '\n' + | ||
options.timestamp + '\n' + | ||
options.nonce + '\n' + | ||
options.method.toUpperCase() + '\n' + | ||
url.pathname + (url.search || '') + '\n' + // Maintain trailing ? | ||
options.host.toLowerCase() + '\n' + | ||
options.port + '\n' + | ||
(options.ext || '') + '\n'; | ||
return normalized; | ||
}; |
@@ -186,3 +186,15 @@ // Load modules | ||
var mac = Crypto.calculateMAC(credentials.key, credentials.algorithm, attributes.ts, attributes.nonce, req.method, req.url, host.name, host.port, attributes.ext); | ||
var mac = Crypto.calculateMAC({ | ||
header: 'core', | ||
key: credentials.key, | ||
algorithm: credentials.algorithm, | ||
timestamp: attributes.ts, | ||
nonce: attributes.nonce, | ||
method: req.method, | ||
uri: req.url, | ||
host: host.name, | ||
port: host.port, | ||
ext: attributes.ext | ||
}); | ||
if (!Utils.fixedTimeComparison(mac, attributes.mac)) { | ||
@@ -236,6 +248,17 @@ return callback(Err.unauthorized('Bad mac'), credentials, attributes.ext); | ||
var timestamp = options.timestamp || Math.floor(now / 1000); | ||
var nonce = options.nonce || Utils.randomString(6); | ||
var mac = Crypto.calculateMAC(credentials.key, credentials.algorithm, timestamp, nonce, method, uri, host, port, options.ext); | ||
var artifacts = { | ||
header: 'core', | ||
key: credentials.key, | ||
algorithm: credentials.algorithm, | ||
timestamp: options.timestamp || Math.floor(now / 1000), | ||
nonce: options.nonce || Utils.randomString(6), | ||
method: method, | ||
uri: uri, | ||
host: host, | ||
port: port, | ||
ext: options.ext | ||
}; | ||
var mac = Crypto.calculateMAC(artifacts); | ||
if (!mac) { | ||
@@ -247,3 +270,3 @@ return ''; | ||
var header = 'Hawk id="' + credentials.id + '", ts="' + timestamp + '", nonce="' + nonce + (options.ext ? '", ext="' + Utils.escapeHeaderAttribute (options.ext) : '') + '", mac="' + mac + '"'; | ||
var header = 'Hawk id="' + credentials.id + '", ts="' + artifacts.timestamp + '", nonce="' + artifacts.nonce + (options.ext ? '", ext="' + Utils.escapeHeaderAttribute(options.ext) : '') + '", mac="' + mac + '"'; | ||
return header; | ||
@@ -250,0 +273,0 @@ }; |
@@ -117,3 +117,15 @@ // Load modules | ||
var mac = Crypto.calculateMAC(credentials.key, credentials.algorithm, bewit.exp, '', 'GET', url, host.name, host.port, bewit.ext); | ||
var mac = Crypto.calculateMAC({ | ||
header: 'bewit', | ||
key: credentials.key, | ||
algorithm: credentials.algorithm, | ||
timestamp: bewit.exp, | ||
nonce: '', | ||
method: 'GET', | ||
uri: url, | ||
host: host.name, | ||
port: host.port, | ||
ext: bewit.ext | ||
}); | ||
if (!Utils.fixedTimeComparison(mac, bewit.mac)) { | ||
@@ -159,3 +171,14 @@ return callback(Err.unauthorized('Bad mac'), credentials, bewit.ext); | ||
var exp = Math.floor(now / 1000) + ttlSec; | ||
var mac = Crypto.calculateMAC(credentials.key, credentials.algorithm, exp, '', 'GET', uri, host, port, options.ext); | ||
var mac = Crypto.calculateMAC({ | ||
header: 'bewit', | ||
key: credentials.key, | ||
algorithm: credentials.algorithm, | ||
timestamp: exp, | ||
nonce: '', | ||
method: 'GET', | ||
uri: uri, | ||
host: host, | ||
port: port, | ||
ext: options.ext | ||
}); | ||
@@ -162,0 +185,0 @@ if (!mac) { |
{ | ||
"name": "hawk", | ||
"description": "HTTP Hawk Authentication Scheme", | ||
"version": "0.3.0", | ||
"version": "0.4.0", | ||
"author": "Eran Hammer <eran@hueniverse.com> (http://hueniverse.com)", | ||
@@ -6,0 +6,0 @@ "contributors": [], |
@@ -6,3 +6,3 @@ ![hawk Logo](https://raw.github.com/hueniverse/hawk/master/images/hawk.png) | ||
Current version: **0.3.0** | ||
Current version: **0.4.0** | ||
@@ -184,2 +184,3 @@ [![Build Status](https://secure.travis-ci.org/hueniverse/hawk.png)](http://travis-ci.org/hueniverse/hawk) | ||
``` | ||
core.1 | ||
1353832234 | ||
@@ -194,2 +195,7 @@ j4h3g2 | ||
The 'core.1' normalized string header is used to prevent MAC values from being reused after a potential change in how the | ||
protocol creates the normalized string. For example, if a future version would switch the order of nonce and timestamp, it | ||
can create an exploit opportunity for cases where the nonce is similar in format to a timestamp. In addition, the header | ||
prevents switching MAC values between a header request and a bewit request. | ||
The request MAC is calculated using the specified algorithm "hmac-sha-256" and the key over the normalized request string. | ||
@@ -199,3 +205,3 @@ The result is base64-encoded to produce the request MAC: | ||
``` | ||
hpf5lg0G0rtKrT04CiRf0Q+IDjkGkyvKdMjtqu1XV/s= | ||
/4mn/w+FiIx5GO0TtwLmseu2YQc3xUqRV9sJmNHqcG0= | ||
``` | ||
@@ -209,3 +215,3 @@ | ||
Host: example.com:8000 | ||
Authorization: Hawk id="dh37fgj492je", ts="1353832234", ext="some-app-data", mac="hpf5lg0G0rtKrT04CiRf0Q+IDjkGkyvKdMjtqu1XV/s=" | ||
Authorization: Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", ext="some-app-data", mac="/4mn/w+FiIx5GO0TtwLmseu2YQc3xUqRV9sJmNHqcG0=" | ||
``` | ||
@@ -420,3 +426,3 @@ | ||
It is really had to include other headers. Headers can be changed by proxies and other intermediaries and there is no | ||
It is really hard to include other headers. Headers can be changed by proxies and other intermediaries and there is no | ||
well-established way to normalize them. The only straight-forward solution is to include the headers in some blob (say, | ||
@@ -423,0 +429,0 @@ bas64 encoded JSON) and include that with the request, an approach taken by JWT and other such formats. However, that |
@@ -25,8 +25,57 @@ // Load modules | ||
expect(Hawk.crypto.calculateMAC('dasdfasdf', 'hmac-sha-0', Date.now() / 1000, 'k3k4j5', 'GET', '/resource/something', 'example.com', 8080)).to.equal(''); | ||
expect(Hawk.crypto.calculateMAC({ | ||
header: 'core', | ||
key: 'dasdfasdf', | ||
algorithm: 'hmac-sha-0', | ||
timestamp: Math.floor(Date.now() / 1000), | ||
nonce: 'k3k4j5', | ||
method: 'GET', | ||
uri: '/resource/something', | ||
host: 'example.com', | ||
port: 8080 | ||
})).to.equal(''); | ||
done(); | ||
}); | ||
}); | ||
describe('#generateNormalizedString', function () { | ||
it('should return a valid normalized string', function (done) { | ||
expect(Hawk.crypto.generateNormalizedString({ | ||
header: 'core', | ||
key: 'dasdfasdf', | ||
algorithm: 'hmac-sha-256', | ||
timestamp: 1357747017, | ||
nonce: 'k3k4j5', | ||
method: 'GET', | ||
uri: '/resource/something', | ||
host: 'example.com', | ||
port: 8080 | ||
})).to.equal('core.1\n1357747017\nk3k4j5\nGET\n/resource/something\nexample.com\n8080\n\n'); | ||
done(); | ||
}); | ||
it('should return a valid normalized string (ext)', function (done) { | ||
expect(Hawk.crypto.generateNormalizedString({ | ||
header: 'core', | ||
key: 'dasdfasdf', | ||
algorithm: 'hmac-sha-256', | ||
timestamp: 1357747017, | ||
nonce: 'k3k4j5', | ||
method: 'GET', | ||
uri: '/resource/something', | ||
host: 'example.com', | ||
port: 8080, | ||
ext: 'this is some app data' | ||
})).to.equal('core.1\n1357747017\nk3k4j5\nGET\n/resource/something\nexample.com\n8080\nthis is some app data\n'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -85,3 +85,3 @@ // Load modules | ||
headers: { | ||
authorization: 'Hawk id="1", ts="1353788437", nonce="k3j4h2", mac="qrP6b5tiS2CO330rpjUEym/USBM=", ext="hello"', | ||
authorization: 'Hawk id="1", ts="1353788437", nonce="k3j4h2", mac="qk3onE3/tko0WylI4lQHMAra98A=", ext="hello"', | ||
host: 'example.com:8080' | ||
@@ -105,3 +105,3 @@ }, | ||
headers: { | ||
authorization: 'Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", mac="hpf5lg0G0rtKrT04CiRf0Q+IDjkGkyvKdMjtqu1XV/s=", ext="some-app-data"', | ||
authorization: 'Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", mac="/4mn/w+FiIx5GO0TtwLmseu2YQc3xUqRV9sJmNHqcG0=", ext="some-app-data"', | ||
host: 'example.com:8000' | ||
@@ -125,3 +125,3 @@ }, | ||
headers: { | ||
authorization: 'Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", mac="hpf5lg0G0rtKrT04CiRf0Q+IDjkGkyvKdMjtqu1XV/s=", ext="some-app-data"', | ||
authorization: 'Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", mac="/4mn/w+FiIx5GO0TtwLmseu2YQc3xUqRV9sJmNHqcG0=", ext="some-app-data"', | ||
host: 'example.com:8000' | ||
@@ -149,3 +149,3 @@ }, | ||
headers: { | ||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", mac="ZPa2zWC3WUAYXrwPzJ3DpF54xjQ2ZDLe8GF1ny6JJFI=", ext="hello"', | ||
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", mac="WBOsuQGaxVMdLd3Gzz5WqsS2AB0l4VkpsvRWbfWzYK4=", ext="hello"', | ||
host: 'example.com:8080' | ||
@@ -622,3 +622,3 @@ }, | ||
var header = Hawk.getAuthorizationHeader(credentials, 'POST', '/somewhere/over/the/rainbow', 'example.net', 443, { ext: 'Bazinga!', timestamp: 1353809207, nonce: 'Ygvqdz' }); | ||
expect(header).to.equal('Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ext="Bazinga!", mac="qSK1cZEkqPwE2ttBX8QSXxO+NE3epFMu4tyVpGKjdnU="'); | ||
expect(header).to.equal('Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ext="Bazinga!", mac="VW+mnJDkcWbM+fgBBeJzfUkrKReXmcl9bRH9WY6qjPg="'); | ||
done(); | ||
@@ -625,0 +625,0 @@ }); |
@@ -65,3 +65,3 @@ // Load modules | ||
method: 'GET', | ||
url: '/resource/4?a=1&b=2&bewit=MTIzNDU2XDQ1MDk5OTE1ODJcRUQ0ZHJtYytVQzAvaFpYQWR0QzVYOFlaU1NHc2pLYWhjSDVDdEhYaFJZUT1cc29tZS1hcHAtZGF0YQ' | ||
url: '/resource/4?a=1&b=2&bewit=MTIzNDU2XDQ1MTEzNDU4OTBcSTdWQWJqMWVtOG1vdmZLV1pLUVlvbEVlM2tsYkRlRU9yVllCYVdWMGdXRT1cc29tZS1hcHAtZGF0YQ' | ||
}; | ||
@@ -85,3 +85,3 @@ | ||
method: 'GET', | ||
url: '/resource/4?bewit=MTIzNDU2XDQ1MDk5OTE1ODJcRUQ0ZHJtYytVQzAvaFpYQWR0QzVYOFlaU1NHc2pLYWhjSDVDdEhYaFJZUT1cc29tZS1hcHAtZGF0YQ&a=1&b=2' | ||
url: '/resource/4?bewit=MTIzNDU2XDQ1MTEzNDU4OTBcSTdWQWJqMWVtOG1vdmZLV1pLUVlvbEVlM2tsYkRlRU9yVllCYVdWMGdXRT1cc29tZS1hcHAtZGF0YQ&a=1&b=2' | ||
}; | ||
@@ -105,3 +105,3 @@ | ||
method: 'GET', | ||
url: '/resource/4?bewit=MTIzNDU2XDQ1MDk5OTE3MTlcTUE2eWkwRWRwR0pEcWRwb0JkYVdvVDJrL0hDSzA1T0Y3MkhuZlVmVy96Zz1cc29tZS1hcHAtZGF0YQ' | ||
url: '/resource/4?bewit=MTIzNDU2XDQ1MTEzNDU5NjBcZlE5ejBiZUpzelIxMnkwR0tTYTFITEtKVm9OWVA3S0JLOVl2VXI5S0FvST1cc29tZS1hcHAtZGF0YQ' | ||
}; | ||
@@ -132,3 +132,15 @@ | ||
var ext = 'some-app-data'; | ||
var mac = Hawk.crypto.calculateMAC(credentials.key, credentials.algorithm, exp, '', 'POST', req.url, 'example.com', 8080, ext); | ||
var mac = Hawk.crypto.calculateMAC({ | ||
header: 'bewit', | ||
key: credentials.key, | ||
algorithm: credentials.algorithm, | ||
timestamp: exp, | ||
nonce: '', | ||
method: 'POST', | ||
uri: req.url, | ||
host: 'example.com', | ||
port: 8080, | ||
ext: ext | ||
}); | ||
var bewit = credentials.id + '\\' + exp + '\\' + mac + '\\' + ext; | ||
@@ -375,3 +387,3 @@ | ||
var bewit = Hawk.uri.getBewit(credentials, '/somewhere/over/the/rainbow', 'example.com', 443, 300, { localtimeOffsetMsec: 1356420407232 - Date.now(), ext: 'xandyandz' }); | ||
expect(bewit).to.equal('MTIzNDU2XDEzNTY0MjA3MDdcT2U3TzF4ZXNSTE5GTEphODBEdGRsdlVGbURzc0RnQ0gwUDRsWWxSWWloWT1ceGFuZHlhbmR6'); | ||
expect(bewit).to.equal('MTIzNDU2XDEzNTY0MjA3MDdcTWEveU4wU2dsWWNBWmJCTEk2cDNvZEppWlVKR2VDTWcyZ043MkF3aVNwZz1ceGFuZHlhbmR6'); | ||
done(); | ||
@@ -378,0 +390,0 @@ }); |
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
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
98856
1487
455