Merge pull request #141 from jcwilson/master

adding support for receiving credentials.key during server-side nonce verification
This commit is contained in:
Eran Hammer 2015-06-13 11:56:09 -07:00
Родитель a4721cc1dc 1fed56055b
Коммит 1e44abb0c3
3 изменённых файлов: 107 добавлений и 26 удалений

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

@ -16,7 +16,7 @@ var internals = {};
/*
req: node's HTTP request object or an object as follows:
var request = {
method: 'GET',
url: '/resource/4?a=1&b=2',
@ -24,21 +24,21 @@ var internals = {};
port: 8080,
authorization: 'Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", ext="some-app-ext-data", mac="6R4rV5iE+NPoym+WwjeHzjAGXUtLNIxmo1vpMofpLAE="'
};
credentialsFunc: required function to lookup the set of Hawk credentials based on the provided credentials id.
The credentials include the MAC key, MAC algorithm, and other attributes (such as username)
needed by the application. This function is the equivalent of verifying the username and
password in Basic authentication.
var credentialsFunc = function (id, callback) {
// Lookup credentials in database
db.lookup(id, function (err, item) {
if (err || !item) {
return callback(err);
}
var credentials = {
// Required
key: item.key,
@ -46,27 +46,27 @@ var internals = {};
// Application specific
user: item.user
};
return callback(null, credentials);
});
};
options: {
hostHeaderName: optional header field name, used to override the default 'Host' header when used
behind a cache of a proxy. Apache2 changes the value of the 'Host' header while preserving
the original (which is what the module must verify) in the 'x-forwarded-host' header field.
Only used when passed a node Http.ServerRequest object.
nonceFunc: optional nonce validation function. The function signature is function(nonce, ts, callback)
nonceFunc: optional nonce validation function. The function signature is function(key, nonce, ts, callback)
where 'callback' must be called using the signature function(err).
timestampSkewSec: optional number of seconds of permitted clock skew for incoming timestamps. Defaults to 60 seconds.
Provides a +/- skew which means actual allowed window is double the number of seconds.
localtimeOffsetMsec: optional local clock time offset express in a number of milliseconds (positive or negative).
Defaults to 0.
payload: optional payload for validation. The client calculates the hash value and includes it via the 'hash'
header attribute. The server always ensures the value provided has been included in the request
MAC. When this option is provided, it validates the hash value itself. Validation is done by calculating
@ -85,10 +85,10 @@ var internals = {};
exports.authenticate = function (req, credentialsFunc, options, callback) {
callback = Hoek.nextTick(callback);
// Default options
options.nonceFunc = options.nonceFunc || function (nonce, ts, nonceCallback) { return nonceCallback(); }; // No validation
options.nonceFunc = options.nonceFunc || function (key, nonce, ts, nonceCallback) { return nonceCallback(); }; // No validation
options.timestampSkewSec = options.timestampSkewSec || 60; // 60 seconds
// Application time
@ -182,7 +182,7 @@ exports.authenticate = function (req, credentialsFunc, options, callback) {
// Check nonce
options.nonceFunc(attributes.nonce, attributes.ts, function (err) {
options.nonceFunc(credentials.key, attributes.nonce, attributes.ts, function (err) {
if (err) {
return callback(Boom.unauthorized('Invalid nonce', 'Hawk'), credentials, artifacts);
@ -325,7 +325,7 @@ exports.authenticateBewit = function (req, credentialsFunc, options, callback) {
// Extract bewit
// 1 2 3 4
// 1 2 3 4
var resource = request.url.match(/^(\/.*)([\?&])bewit\=([^&$]*)(?:&(.+))?$/);
if (!resource) {
return callback(Boom.unauthorized(null, 'Hawk'));
@ -445,10 +445,10 @@ exports.authenticateBewit = function (req, credentialsFunc, options, callback) {
exports.authenticateMessage = function (host, port, message, authorization, credentialsFunc, options, callback) {
callback = Hoek.nextTick(callback);
// Default options
options.nonceFunc = options.nonceFunc || function (nonce, ts, nonceCallback) { return nonceCallback(); }; // No validation
options.nonceFunc = options.nonceFunc || function (key, nonce, ts, nonceCallback) { return nonceCallback(); }; // No validation
options.timestampSkewSec = options.timestampSkewSec || 60; // 60 seconds
// Application time
@ -456,13 +456,13 @@ exports.authenticateMessage = function (host, port, message, authorization, cred
var now = Utils.now(options.localtimeOffsetMsec); // Measure now before any other processing
// Validate authorization
if (!authorization.id ||
!authorization.ts ||
!authorization.nonce ||
!authorization.hash ||
!authorization.mac) {
return callback(Boom.badRequest('Invalid authorization'))
}
@ -514,7 +514,7 @@ exports.authenticateMessage = function (host, port, message, authorization, cred
// Check nonce
options.nonceFunc(authorization.nonce, authorization.ts, function (err) {
options.nonceFunc(credentials.key, authorization.nonce, authorization.ts, function (err) {
if (err) {
return callback(Boom.unauthorized('Invalid nonce', 'Hawk'), credentials);

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

@ -137,7 +137,7 @@ describe('Hawk', function () {
var auth = Hawk.client.message('example.com', 8080, 'some message', { credentials: credentials });
expect(auth).to.exist();
Hawk.server.authenticateMessage('example.com', 8080, 'some message', auth, credentialsFunc, { nonceFunc: function (nonce, ts, callback) { callback (new Error('kaboom')); } }, function (err, credentials) {
Hawk.server.authenticateMessage('example.com', 8080, 'some message', auth, credentialsFunc, { nonceFunc: function (key, nonce, ts, callback) { callback (new Error('kaboom')); } }, function (err, credentials) {
expect(err).to.exist();
expect(err.message).to.equal('Invalid nonce');

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

@ -190,13 +190,13 @@ describe('Hawk', function () {
var memoryCache = {};
var options = {
localtimeOffsetMsec: 1353788437000 - Hawk.utils.now(),
nonceFunc: function (nonce, ts, callback) {
nonceFunc: function (key, nonce, ts, callback) {
if (memoryCache[nonce]) {
if (memoryCache[key + nonce]) {
return callback(new Error());
}
memoryCache[nonce] = true;
memoryCache[key + nonce] = true;
return callback();
}
};
@ -215,6 +215,72 @@ describe('Hawk', function () {
});
});
it('does not error on nonce collision if keys differ', function (done) {
var reqSteve = {
method: 'GET',
url: '/resource/4?filter=a',
host: 'example.com',
port: 8080,
authorization: 'Hawk id="123", ts="1353788437", nonce="k3j4h2", mac="bXx7a7p1h9QYQNZ8x7QhvDQym8ACgab4m3lVSFn4DBw=", ext="hello"'
};
var reqBob = {
method: 'GET',
url: '/resource/4?filter=a',
host: 'example.com',
port: 8080,
authorization: 'Hawk id="456", ts="1353788437", nonce="k3j4h2", mac="LXfmTnRzrLd9TD7yfH+4se46Bx6AHyhpM94hLCiNia4=", ext="hello"'
};
var credentialsFunc = function (id, callback) {
var credentials = {
'123': {
id: id,
key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
algorithm: (id === '1' ? 'sha1' : 'sha256'),
user: 'steve'
},
'456': {
id: id,
key: 'xrunpaw3489ruxnpa98w4rxnwerxhqb98rpaxn39848',
algorithm: (id === '1' ? 'sha1' : 'sha256'),
user: 'bob'
}
};
return callback(null, credentials[id]);
};
var memoryCache = {};
var options = {
localtimeOffsetMsec: 1353788437000 - Hawk.utils.now(),
nonceFunc: function (key, nonce, ts, callback) {
if (memoryCache[key + nonce]) {
return callback(new Error());
}
memoryCache[key + nonce] = true;
return callback();
}
};
Hawk.server.authenticate(reqSteve, credentialsFunc, options, function (err, credentials, artifacts) {
expect(err).to.not.exist();
expect(credentials.user).to.equal('steve');
Hawk.server.authenticate(reqBob, credentialsFunc, options, function (err, credentials, artifacts) {
expect(err).to.not.exist();
expect(credentials.user).to.equal('bob');
done();
});
});
});
it('errors on an invalid authentication header: wrong scheme', function (done) {
var req = {
@ -970,6 +1036,21 @@ describe('Hawk', function () {
});
});
});
it('errors on nonce collision', function (done) {
credentialsFunc('123456', function (err, credentials) {
var auth = Hawk.client.message('example.com', 8080, 'some message', { credentials: credentials });
Hawk.server.authenticateMessage('example.com', 8080, 'some message', auth, credentialsFunc, {nonceFunc: function (key, nonce, ts, nonceCallback) { nonceCallback(true); }}, function (err, credentials) {
expect(err).to.exist();
expect(err.message).to.equal('Invalid nonce');
done();
});
});
});
});
describe('#authenticatePayloadHash', function () {