зеркало из https://github.com/mozilla/hawk.git
Merge pull request #141 from jcwilson/master
adding support for receiving credentials.key during server-side nonce verification
This commit is contained in:
Коммит
1e44abb0c3
|
@ -16,7 +16,7 @@ var internals = {};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
req: node's HTTP request object or an object as follows:
|
req: node's HTTP request object or an object as follows:
|
||||||
|
|
||||||
var request = {
|
var request = {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '/resource/4?a=1&b=2',
|
url: '/resource/4?a=1&b=2',
|
||||||
|
@ -24,21 +24,21 @@ var internals = {};
|
||||||
port: 8080,
|
port: 8080,
|
||||||
authorization: 'Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", ext="some-app-ext-data", mac="6R4rV5iE+NPoym+WwjeHzjAGXUtLNIxmo1vpMofpLAE="'
|
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.
|
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)
|
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
|
needed by the application. This function is the equivalent of verifying the username and
|
||||||
password in Basic authentication.
|
password in Basic authentication.
|
||||||
|
|
||||||
var credentialsFunc = function (id, callback) {
|
var credentialsFunc = function (id, callback) {
|
||||||
|
|
||||||
// Lookup credentials in database
|
// Lookup credentials in database
|
||||||
db.lookup(id, function (err, item) {
|
db.lookup(id, function (err, item) {
|
||||||
|
|
||||||
if (err || !item) {
|
if (err || !item) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
var credentials = {
|
var credentials = {
|
||||||
// Required
|
// Required
|
||||||
key: item.key,
|
key: item.key,
|
||||||
|
@ -46,27 +46,27 @@ var internals = {};
|
||||||
// Application specific
|
// Application specific
|
||||||
user: item.user
|
user: item.user
|
||||||
};
|
};
|
||||||
|
|
||||||
return callback(null, credentials);
|
return callback(null, credentials);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
options: {
|
options: {
|
||||||
|
|
||||||
hostHeaderName: optional header field name, used to override the default 'Host' header when used
|
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
|
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.
|
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.
|
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).
|
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.
|
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.
|
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).
|
localtimeOffsetMsec: optional local clock time offset express in a number of milliseconds (positive or negative).
|
||||||
Defaults to 0.
|
Defaults to 0.
|
||||||
|
|
||||||
payload: optional payload for validation. The client calculates the hash value and includes it via the 'hash'
|
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
|
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
|
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) {
|
exports.authenticate = function (req, credentialsFunc, options, callback) {
|
||||||
|
|
||||||
callback = Hoek.nextTick(callback);
|
callback = Hoek.nextTick(callback);
|
||||||
|
|
||||||
// Default options
|
// 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
|
options.timestampSkewSec = options.timestampSkewSec || 60; // 60 seconds
|
||||||
|
|
||||||
// Application time
|
// Application time
|
||||||
|
@ -182,7 +182,7 @@ exports.authenticate = function (req, credentialsFunc, options, callback) {
|
||||||
|
|
||||||
// Check nonce
|
// Check nonce
|
||||||
|
|
||||||
options.nonceFunc(attributes.nonce, attributes.ts, function (err) {
|
options.nonceFunc(credentials.key, attributes.nonce, attributes.ts, function (err) {
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(Boom.unauthorized('Invalid nonce', 'Hawk'), credentials, artifacts);
|
return callback(Boom.unauthorized('Invalid nonce', 'Hawk'), credentials, artifacts);
|
||||||
|
@ -325,7 +325,7 @@ exports.authenticateBewit = function (req, credentialsFunc, options, callback) {
|
||||||
|
|
||||||
// Extract bewit
|
// Extract bewit
|
||||||
|
|
||||||
// 1 2 3 4
|
// 1 2 3 4
|
||||||
var resource = request.url.match(/^(\/.*)([\?&])bewit\=([^&$]*)(?:&(.+))?$/);
|
var resource = request.url.match(/^(\/.*)([\?&])bewit\=([^&$]*)(?:&(.+))?$/);
|
||||||
if (!resource) {
|
if (!resource) {
|
||||||
return callback(Boom.unauthorized(null, 'Hawk'));
|
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) {
|
exports.authenticateMessage = function (host, port, message, authorization, credentialsFunc, options, callback) {
|
||||||
|
|
||||||
callback = Hoek.nextTick(callback);
|
callback = Hoek.nextTick(callback);
|
||||||
|
|
||||||
// Default options
|
// 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
|
options.timestampSkewSec = options.timestampSkewSec || 60; // 60 seconds
|
||||||
|
|
||||||
// Application time
|
// 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
|
var now = Utils.now(options.localtimeOffsetMsec); // Measure now before any other processing
|
||||||
|
|
||||||
// Validate authorization
|
// Validate authorization
|
||||||
|
|
||||||
if (!authorization.id ||
|
if (!authorization.id ||
|
||||||
!authorization.ts ||
|
!authorization.ts ||
|
||||||
!authorization.nonce ||
|
!authorization.nonce ||
|
||||||
!authorization.hash ||
|
!authorization.hash ||
|
||||||
!authorization.mac) {
|
!authorization.mac) {
|
||||||
|
|
||||||
return callback(Boom.badRequest('Invalid authorization'))
|
return callback(Boom.badRequest('Invalid authorization'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -514,7 +514,7 @@ exports.authenticateMessage = function (host, port, message, authorization, cred
|
||||||
|
|
||||||
// Check nonce
|
// Check nonce
|
||||||
|
|
||||||
options.nonceFunc(authorization.nonce, authorization.ts, function (err) {
|
options.nonceFunc(credentials.key, authorization.nonce, authorization.ts, function (err) {
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(Boom.unauthorized('Invalid nonce', 'Hawk'), credentials);
|
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 });
|
var auth = Hawk.client.message('example.com', 8080, 'some message', { credentials: credentials });
|
||||||
expect(auth).to.exist();
|
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).to.exist();
|
||||||
expect(err.message).to.equal('Invalid nonce');
|
expect(err.message).to.equal('Invalid nonce');
|
||||||
|
|
|
@ -190,13 +190,13 @@ describe('Hawk', function () {
|
||||||
var memoryCache = {};
|
var memoryCache = {};
|
||||||
var options = {
|
var options = {
|
||||||
localtimeOffsetMsec: 1353788437000 - Hawk.utils.now(),
|
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());
|
return callback(new Error());
|
||||||
}
|
}
|
||||||
|
|
||||||
memoryCache[nonce] = true;
|
memoryCache[key + nonce] = true;
|
||||||
return callback();
|
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) {
|
it('errors on an invalid authentication header: wrong scheme', function (done) {
|
||||||
|
|
||||||
var req = {
|
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 () {
|
describe('#authenticatePayloadHash', function () {
|
||||||
|
|
Загрузка…
Ссылка в новой задаче