This commit is contained in:
Eran Hammer 2013-01-09 07:50:06 -08:00
Родитель 893ae24248
Коммит 39b5ae2c17
8 изменённых файлов: 121 добавлений и 30 удалений

Просмотреть файл

@ -3,7 +3,7 @@
<img align="right" src="https://raw.github.com/hueniverse/hawk/master/images/logo.png" /> **Hawk** is an HTTP authentication scheme using a message authentication code (MAC) algorithm to provide partial
HTTP request cryptographic verification. For more complex use cases such as access delegation, see [Oz](/hueniverse/oz).
Current version: **0.3.0**
Current version: **0.4.0**
[![Build Status](https://secure.travis-ci.org/hueniverse/hawk.png)](http://travis-ci.org/hueniverse/hawk)
@ -180,6 +180,7 @@ The client generates the authentication header by calculating a timestamp (e.g.
1970 00:00:00 GMT), generates a nonce, and constructs the normalized request string (newline separated values):
```
core.1
1353832234
j4h3g2
GET

Просмотреть файл

@ -9,6 +9,11 @@ var Url = require('url');
var internals = {};
// MAC normalization format version
exports.headerVersion = '1'; // Prevent comparison of mac values generated with different normalized string formats
// Supported MAC algorithms
exports.algorithms = ['hmac-sha-1', 'hmac-sha-256'];
@ -16,26 +21,42 @@ exports.algorithms = ['hmac-sha-1', 'hmac-sha-256'];
// Calculate the request MAC
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'
};
*/
exports.calculateMAC = function (options) {
// Parse request URI
var url = Url.parse(uri);
var url = Url.parse(options.uri);
// Construct normalized req string
var normalized = timestamp + '\n' +
nonce + '\n' +
method.toUpperCase() + '\n' +
var normalized = options.header + '.' + exports.headerVersion + '\n' +
options.timestamp + '\n' +
options.nonce + '\n' +
options.method.toUpperCase() + '\n' +
url.pathname + (url.search || '') + '\n' +
host.toLowerCase() + '\n' +
port + '\n' +
(ext || '') + '\n';
options.host.toLowerCase() + '\n' +
options.port + '\n' +
(options.ext || '') + '\n';
// Lookup hash function
var hashMethod = '';
switch (algorithm) {
switch (options.algorithm) {
case 'hmac-sha-1': hashMethod = 'sha1'; break;
case 'hmac-sha-256': hashMethod = 'sha256'; break;
@ -44,7 +65,7 @@ exports.calculateMAC = function (key, algorithm, timestamp, nonce, method, uri,
// MAC normalized req string
var hmac = Crypto.createHmac(hashMethod, key).update(normalized);
var hmac = Crypto.createHmac(hashMethod, options.key).update(normalized);
var digest = hmac.digest('base64');
return digest;
};

Просмотреть файл

@ -183,7 +183,19 @@ exports.authenticate = function (req, credentialsFunc, options, callback) {
// Calculate MAC
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)) {
return callback(Err.unauthorized('Bad mac'), credentials, attributes.ext);
}
@ -232,9 +244,20 @@ exports.getAuthorizationHeader = function (credentials, method, uri, host, port,
// Calculate signature
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) {
return '';
@ -242,7 +265,7 @@ exports.getAuthorizationHeader = function (credentials, method, uri, host, port,
// Construct header
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;
};

Просмотреть файл

@ -114,7 +114,19 @@ exports.authenticate = function (req, credentialsFunc, options, callback) {
// Calculate MAC
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)) {
return callback(Err.unauthorized('Bad mac'), credentials, bewit.ext);
}
@ -155,7 +167,18 @@ exports.getBewit = function (credentials, uri, host, port, ttlSec, options) {
// Calculate signature
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
});
if (!mac) {
return '';

Просмотреть файл

@ -1,7 +1,7 @@
{
"name": "hawk",
"description": "HTTP Hawk Authentication Scheme",
"version": "0.3.0",
"version": "0.4.0",
"author": "Eran Hammer <eran@hueniverse.com> (http://hueniverse.com)",
"contributors": [],
"repository": "git://github.com/hueniverse/hawk",

Просмотреть файл

@ -22,7 +22,18 @@ describe('Hawk', function () {
it('should return an empty value on unknown algorithm', function (done) {
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: Date.now() / 1000,
nonce: 'k3k4j5',
method: 'GET',
uri: '/resource/something',
host: 'example.com',
port: 8080
})).to.equal('');
done();
});
});

Просмотреть файл

@ -82,7 +82,7 @@ describe('Hawk', function () {
var req = {
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'
},
method: 'GET',
@ -101,7 +101,7 @@ describe('Hawk', function () {
var req = {
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'
},
method: 'GET',
@ -120,7 +120,7 @@ describe('Hawk', function () {
var req = {
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'
},
method: 'GET',
@ -143,7 +143,7 @@ describe('Hawk', function () {
var req = {
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'
},
method: 'GET',
@ -615,7 +615,7 @@ describe('Hawk', function () {
};
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();
});

Просмотреть файл

@ -62,7 +62,7 @@ describe('Hawk', function () {
host: 'example.com:8080'
},
method: 'GET',
url: '/resource/4?a=1&b=2&bewit=MTIzNDU2XDQ1MDk5OTE1ODJcRUQ0ZHJtYytVQzAvaFpYQWR0QzVYOFlaU1NHc2pLYWhjSDVDdEhYaFJZUT1cc29tZS1hcHAtZGF0YQ'
url: '/resource/4?a=1&b=2&bewit=MTIzNDU2XDQ1MTEzNDU4OTBcSTdWQWJqMWVtOG1vdmZLV1pLUVlvbEVlM2tsYkRlRU9yVllCYVdWMGdXRT1cc29tZS1hcHAtZGF0YQ'
};
Hawk.uri.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) {
@ -81,7 +81,7 @@ describe('Hawk', function () {
host: 'example.com:8080'
},
method: 'GET',
url: '/resource/4?bewit=MTIzNDU2XDQ1MDk5OTE1ODJcRUQ0ZHJtYytVQzAvaFpYQWR0QzVYOFlaU1NHc2pLYWhjSDVDdEhYaFJZUT1cc29tZS1hcHAtZGF0YQ&a=1&b=2'
url: '/resource/4?bewit=MTIzNDU2XDQ1MTEzNDU4OTBcSTdWQWJqMWVtOG1vdmZLV1pLUVlvbEVlM2tsYkRlRU9yVllCYVdWMGdXRT1cc29tZS1hcHAtZGF0YQ&a=1&b=2'
};
Hawk.uri.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) {
@ -100,7 +100,7 @@ describe('Hawk', function () {
host: 'example.com:8080'
},
method: 'GET',
url: '/resource/4?bewit=MTIzNDU2XDQ1MDk5OTE3MTlcTUE2eWkwRWRwR0pEcWRwb0JkYVdvVDJrL0hDSzA1T0Y3MkhuZlVmVy96Zz1cc29tZS1hcHAtZGF0YQ'
url: '/resource/4?bewit=MTIzNDU2XDQ1MTEzNDU5NjBcZlE5ejBiZUpzelIxMnkwR0tTYTFITEtKVm9OWVA3S0JLOVl2VXI5S0FvST1cc29tZS1hcHAtZGF0YQ'
};
Hawk.uri.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) {
@ -126,7 +126,19 @@ describe('Hawk', function () {
var exp = Math.floor(Date.now() / 1000) + 60;
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;
req.url += '&bewit=' + Hawk.utils.base64urlEncode(bewit);
@ -368,7 +380,7 @@ describe('Hawk', function () {
};
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();
});