Comparing version 0.1.0 to 0.2.0
@@ -5,3 +5,3 @@ // Load modules | ||
var NodeUtil = require('util'); | ||
var Hoek = require('hoek'); | ||
var Utils = require('./utils'); | ||
@@ -16,4 +16,4 @@ | ||
Hoek.assert(this.constructor === internals.Error, 'Error must be instantiated using new'); | ||
Hoek.assert(code >= 400, 'Error code must be 4xx or 5xx'); | ||
Utils.assert(this.constructor === internals.Error, 'Error must be instantiated using new'); | ||
Utils.assert(code >= 400, 'Error code must be 4xx or 5xx'); | ||
@@ -60,4 +60,30 @@ Error.call(this); | ||
return internals.Error.unauthorizedWithTs(message); | ||
}; | ||
internals.Error.unauthorizedWithTs = function (message, now, ntp) { | ||
var err = new internals.Error(401, message); | ||
err.headers['WWW-Authenticate'] = 'Hawk' + (message ? ' error="' + Hoek.escapeHeaderAttribute(message) + '"' : ''); | ||
var attributes = ''; | ||
if (now) { | ||
attributes += 'ts="' + now + '"'; | ||
} | ||
if (ntp) { | ||
if (attributes) { | ||
attributes += ', '; | ||
} | ||
attributes += 'ntp="' + ntp + '"'; | ||
} | ||
if (message) { | ||
if (attributes) { | ||
attributes += ', '; | ||
} | ||
attributes += 'error="' + Utils.escapeHeaderAttribute(message) + '"'; | ||
} | ||
err.headers['WWW-Authenticate'] = 'Hawk' + (attributes ? ' ' + attributes : ''); | ||
return err; | ||
@@ -70,3 +96,3 @@ }; | ||
var err = new internals.Error(500, message); | ||
err.trace = Hoek.displayStack(1); | ||
err.trace = Utils.displayStack(1); | ||
err.data = data; | ||
@@ -73,0 +99,0 @@ |
173
lib/index.js
@@ -6,2 +6,3 @@ // Load modules | ||
var Err = require('./error'); | ||
var Utils = require('./utils'); | ||
@@ -11,7 +12,10 @@ | ||
var internals = { | ||
randomSource: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' | ||
}; | ||
var internals = {}; | ||
// Export utilities | ||
exports.utils = Utils; | ||
// Hawk authentication | ||
@@ -48,15 +52,24 @@ | ||
* | ||
* 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. | ||
* 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. | ||
* | ||
* nonceFunc - optional nonce validation function. The function signature is function(nonce, ts, callback) | ||
* where 'callback' must be called using the signature function(err). | ||
* nonceFunc - optional nonce validation function. The function signature is function(nonce, ts, callback) | ||
* where 'callback' must be called using the signature function(err). | ||
* | ||
* timestampSkewSec - 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 - local clock time offset express in a number of milliseconds (positive or negative). | ||
* Defaults to 0. | ||
* | ||
* ntp - hostname of the ntp server used to synchronize time between the client and the server. The | ||
* ntp server name is included when the client's timestamp is stale along with the server's | ||
* current timestamp. This allows browser-based clients to sync their application clock directly | ||
* with the server, while native clients can be smarter about managing time sync and caching | ||
* multiple clock offsets using the ntp server value provided. Defaults to 'pool.ntp.org'. | ||
*/ | ||
exports.authenticate = function (req, credentialsFunc, arg1, arg2) { | ||
exports.authenticate = function (req, credentialsFunc, options, callback) { | ||
var callback = (arg2 ? arg2 : arg1); | ||
var options = (arg2 ? arg1 : {}); | ||
// Default options | ||
@@ -66,3 +79,10 @@ | ||
options.nonceFunc = options.nonceFunc || function (nonce, ts, callback) { return callback(); }; | ||
options.timestampSkewSec = options.timestampSkewSec || 60; // 60 seconds | ||
options.localtimeOffsetMsec = options.localtimeOffsetMsec || 0; // 0 milliseconds | ||
options.ntp = options.ntp || 'pool.ntp.org'; | ||
// Application time | ||
var now = Date.now() + options.localtimeOffsetMsec; | ||
// Check required HTTP headers: host, authentication | ||
@@ -76,3 +96,3 @@ | ||
if (!req.headers.authorization) { | ||
return callback(Err.unauthorized('Missing Authorization header'), null, null); | ||
return callback(Err.unauthorizedWithTs('', now, options.ntp), null, null); | ||
} | ||
@@ -82,10 +102,50 @@ | ||
var attributes = exports.parseHeader(req.headers.authorization); | ||
var headerParts = req.headers.authorization.match(/^(\w+)(?:\s+(.*))?$/); // Header: scheme[ something] | ||
if (!headerParts) { | ||
return callback(Err.badRequest('Invalid header syntax'), null, null); | ||
} | ||
// Verify authentication scheme | ||
var scheme = headerParts[1]; | ||
if (scheme.toLowerCase() !== 'hawk') { | ||
return callback(Err.unauthorizedWithTs('', now, options.ntp), null, null); | ||
} | ||
if (attributes instanceof Error) { | ||
return callback(attributes, null, null); | ||
var attributesString = headerParts[2]; | ||
if (!attributesString) { | ||
return callback(Err.badRequest('Invalid header syntax'), null, null); | ||
} | ||
var attributes = {}; | ||
var errorMessage = ''; | ||
var verify = attributesString.replace(/(\w+)="([^"\\]*)"\s*(?:,\s*|$)/g, function ($0, $1, $2) { | ||
// Check valid attribute names | ||
if (['id', 'ts', 'nonce', 'ext', 'mac'].indexOf($1) === -1) { | ||
errorMessage = 'Unknown attribute: ' + $1; | ||
return; | ||
} | ||
// Allowed attribute value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9 | ||
if ($2.match(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~]+$/) === null) { | ||
errorMessage = 'Bad attribute value: ' + $1; | ||
return; | ||
} | ||
// Check for duplicates | ||
if (attributes.hasOwnProperty($1)) { | ||
errorMessage = 'Duplicate attribute: ' + $1; | ||
return; | ||
} | ||
attributes[$1] = $2; | ||
return ''; | ||
}); | ||
if (verify !== '') { | ||
return callback(Err.badRequest(errorMessage || 'Bad header format'), null, null); | ||
} | ||
// Verify required header attributes | ||
@@ -100,2 +160,8 @@ | ||
} | ||
// Check timestamp staleness | ||
if (Math.abs((attributes.ts * 1000) - now) > (options.timestampSkewSec * 1000)) { | ||
return callback(Err.unauthorizedWithTs('Stale timestamp', now, options.ntp), null, attributes.ext); | ||
} | ||
@@ -142,3 +208,3 @@ // Obtain host and port information | ||
var mac = exports.calculateMAC(credentials.key, credentials.algorithm, attributes.ts, attributes.nonce, req.method, req.url, host, port, attributes.ext); | ||
if (!exports.fixedTimeComparison(mac, attributes.mac)) { | ||
if (!Utils.fixedTimeComparison(mac, attributes.mac)) { | ||
return callback(Err.unauthorized('Bad mac'), credentials, attributes.ext); | ||
@@ -199,36 +265,2 @@ } | ||
// Extract attribute from MAC header (strict) | ||
exports.parseHeader = function (header) { | ||
var headerRegex = /^([Hh][Aa][Ww][Kk])(?:\s+(.*))?$/; | ||
var headerParts = header.match(headerRegex); | ||
if (!headerParts) { | ||
return Err.unauthorized('Incorrect scheme'); | ||
} | ||
if (!headerParts[2]) { | ||
return Err.badRequest('Invalid header format'); | ||
} | ||
var attributes = {}; | ||
var attributesRegex = /(id|ts|nonce|ext|mac)="([^"\\]*)"\s*(?:,\s*|$)/g; | ||
var verify = headerParts[2].replace(attributesRegex, function ($0, $1, $2) { | ||
if (attributes[$1] === undefined) { | ||
attributes[$1] = $2; | ||
return ''; | ||
} | ||
}); | ||
if (verify !== '') { | ||
return Err.badRequest('Unknown attributes'); | ||
} | ||
return attributes; | ||
}; | ||
// Generate an Authorization header for a given request | ||
@@ -255,3 +287,3 @@ | ||
timestamp = timestamp || Math.floor(((new Date()).getTime() / 1000)); | ||
nonce = nonce || exports.randomString(6); | ||
nonce = nonce || Utils.randomString(6); | ||
var mac = exports.calculateMAC(credentials.key, credentials.algorithm, timestamp, nonce, method, uri, host, port, ext); | ||
@@ -265,3 +297,3 @@ | ||
var header = 'Hawk id="' + credentials.id + '", ts="' + timestamp + '", nonce="' + nonce + (ext ? '", ext="' + ext : '') + '", mac="' + mac + '"'; | ||
var header = 'Hawk id="' + credentials.id + '", ts="' + timestamp + '", nonce="' + nonce + (ext ? '", ext="' + Utils.escapeHeaderAttribute (ext) : '') + '", mac="' + mac + '"'; | ||
return header; | ||
@@ -271,34 +303,1 @@ }; | ||
// Generate a random string of given size (not for crypto) | ||
exports.randomString = function (size) { | ||
var result = []; | ||
var len = internals.randomSource.length; | ||
for (var i = 0; i < size; ++i) { | ||
result.push(internals.randomSource[Math.floor(Math.random() * len)]); | ||
} | ||
return result.join(''); | ||
}; | ||
// Compare two strings using fixed time algorithm (to prevent time-based analysis of MAC digest match) | ||
exports.fixedTimeComparison = function (a, b) { | ||
var mismatch = (a.length === b.length ? 0 : 1); | ||
if (mismatch) { | ||
b = a; | ||
} | ||
for (var i = 0, il = a.length; i < il; ++i) { | ||
var ac = a.charCodeAt(i); | ||
var bc = b.charCodeAt(i); | ||
mismatch += (ac === bc ? 0 : 1); | ||
} | ||
return (mismatch === 0); | ||
}; | ||
{ | ||
"name": "hawk", | ||
"description": "HTTP Hawk Authentication Scheme", | ||
"version": "0.1.0", | ||
"version": "0.2.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.1.0** | ||
Current version: **0.2.0** | ||
@@ -14,2 +14,3 @@ [![Build Status](https://secure.travis-ci.org/hueniverse/hawk.png)](http://travis-ci.org/hueniverse/hawk) | ||
- [**Introduction**](#introduction) | ||
- [Time Synchronization](#time-synchronization) | ||
- [Usage Example](#usage-example) | ||
@@ -26,3 +27,3 @@ - [Protocol Example](#protocol-example) | ||
**Hawk** is an HTTP authentication scheme providing a method for making authenticated HTTP requests with | ||
**Hawk** is an HTTP authentication scheme providing methods for making authenticated HTTP requests with | ||
partial cryptographic verification of the request, covering the HTTP method, request URI, and host. | ||
@@ -45,9 +46,30 @@ | ||
Unlike the HTTP [Digest authentication scheme](http://www.ietf.org/rfc/rfc2617.txt), **Hawk** provides limited | ||
Unlike the HTTP [Digest authentication scheme](http://www.ietf.org/rfc/rfc2617.txt), **Hawk** provides optional | ||
protection against replay attacks which does not require prior interaction with the server. Instead, the client | ||
provides a timestamp which the server can use to prevent replay attacks outside a narrow time window. Also unlike | ||
Digest, this mechanism is not intended to protect the key itself (user's password in Digest) because the client | ||
and server both have access to the key material in the clear. | ||
provides a timestamp and a nonce which the server can use to prevent replay attacks outside a narrow time window. | ||
Also unlike Digest, this mechanism is not intended to protect the key itself (user's password in Digest) because | ||
the client and server must both have access to the key material in the clear. | ||
## Time Synchronization | ||
When making requests, the client includes a timestamp and nonce in order to enable the server to prevent replay | ||
attacks. The nonce is generated by the client and is a string unique across all requests with the same timestamp and | ||
key identifier combination. Without replay protection, an attacker can use a compromised (but otherwise valid and | ||
authenticated) request more than once, gaining long term access to a protected resource. | ||
Including a timestamp with the nonce removes the need to retain an infinite number of nonce values for future checks. | ||
The timestamp enables the server to restrict the time period after which a request with an old timestamp is rejected. | ||
However, this requires the client's clock to be in sync with the server's clock. Unlike other protocols, **Hawk** | ||
requires the client to ensure its clock is in sync. To accomplish that, the server provides the client with its current | ||
time in response to a bad timestamp or as part of a challenge. | ||
In addition, to increase the protocol scalability for clients communicating with many different servers, the server | ||
provides the name of an NTP server which can be used as a time reference for clock sync with the server. | ||
There is no expectation that the client will adjust its system clock to match the server. In fact, that would be a | ||
potential attack vector on the client. Instead, the client only uses the server's time to calculate an offset used only | ||
for communications with that particular server. | ||
## Usage Example | ||
@@ -59,3 +81,3 @@ | ||
var Http = require('http'); | ||
var Hawk = require('../lib/hawk'); | ||
var Hawk = require('hawk'); | ||
@@ -80,3 +102,3 @@ | ||
Hawk.authenticate(req, credentialsFunc, function (err, isAuthenticated, credentials, ext) { | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, isAuthenticated, credentials, ext) { | ||
@@ -95,3 +117,3 @@ res.writeHead(isAuthenticated ? 200 : 401, { 'Content-Type': 'text/plain' }); | ||
var Request = require('request'); | ||
var Hawk = require('../lib/hawk'); | ||
var Hawk = require('hawk'); | ||
@@ -134,7 +156,8 @@ | ||
The resource server returns the following authentication challenge: | ||
The resource server returns an authentication challenge. The challenge provides the client with the server's current | ||
time and NTP server used for clock sync, which enable the client to offset its clock when making requests. | ||
``` | ||
HTTP/1.1 401 Unauthorized | ||
WWW-Authenticate: Hawk | ||
WWW-Authenticate: Hawk ts="1353832200", ntp="pool.ntp.org" | ||
``` | ||
@@ -240,3 +263,27 @@ | ||
### Future Time Manipulation | ||
The protocol relies on a clock sync between the client and server. To accomplish this, the server informs the client of its | ||
current time as well as identifies the NTP server used (the client can opt to use either one to calculate the offset used for | ||
further interactions with the server). | ||
If an attacker is able to manipulate this information and cause the client to use an incorrect time, it would be able to cause | ||
the client to generate authenticated requests using time in the future. Such requests will fail when sent by the client, and will | ||
not likely leave a trace on the server (given the common implementation of nonce, if at all enforced). The attacker will then | ||
be able to replay the request at the correct time without detection. | ||
The client must only use the time information provided by the server if it is sent over a TLS connection and the server identity | ||
has been verified. | ||
### Client Clock Poisoning | ||
When receiving a request with a bad timestamp, the server provides the client with its current time as well as the name of an | ||
NTP server which can be used as a time reference. The client must never use the time received from the server to adjust its own | ||
clock, and must only use it to calculate an offset for communicating with that particular server. | ||
In addition, the client must not draw any correlation between the server's time provided via the 'ts' attribute and the current | ||
time at the NTP server indicated via the 'ntp' variable. In other works, the client must not make any conclusion about the NTP | ||
server indicated based on the server response. | ||
# Frequently Asked Questions | ||
@@ -247,3 +294,4 @@ | ||
If you are looking for some prose explaining how all this works, there isn't any. **Hawk** is being developed as an open source | ||
project instead of a standard. In other words, the [code](/hueniverse/hawk/tree/master/lib) is the specification. | ||
project instead of a standard. In other words, the [code](/hueniverse/hawk/tree/master/lib) is the specification. Not sure about | ||
something? Open an issue! | ||
@@ -268,4 +316,7 @@ ### Is it done? | ||
At this time, **Hawk** is only implemented in JavaScript as a node.js module. Check this space for future support of other | ||
languages (and such contributions are always welcome). | ||
**Hawk** is only officially implemented in JavaScript as a node.js module. However, others are actively porting it to other | ||
platforms. There is already a [PHP](https://github.com/alexbilbie/PHP-Hawk), | ||
[.NET](https://github.com/pcibraro/hawknet), and [JAVA](https://github.com/wealdtech/hawk) libraries available. The full list | ||
is maintained [here](https://github.com/hueniverse/hawk/issues?labels=port). Please add an issue is you are working on another | ||
port. A cross-platform test-suite is in the works. | ||
@@ -283,3 +334,3 @@ ### Why isn't the algorithm part of the challenge or dynamically negotiated? | ||
design violates the HTTP header boundaries, repeats information, and introduces other security issues because firewalls | ||
will not be aware of these “hidden” headers. In addition, any information repeated must be compared to the duplicated | ||
will not be aware of these "hidden" headers. In addition, any information repeated must be compared to the duplicated | ||
information in the header and therefore only moves the problem elsewhere. | ||
@@ -294,3 +345,14 @@ | ||
### Why bother with all this nonce and timestamp business? | ||
**Hawk** is an attempt to find a reasonable, practical compromise between security and usability. OAuth 1.0 got timestamp | ||
and nonces half the way right but failed when it came to scalability and consistent developer experience. **Hawk** addresses | ||
it by requiring the client to sync its clock, but provides it with tools to accomplish it. | ||
In general, replay protection is a matter of application-specific threat model. It is less of an issue on a TLS-protected | ||
system where the clients are implemented using best practices and are under the control of the server. Instead of dropping | ||
replay protection, **Hawk** offers a required time window and an optional nonce verification. Together, it provides developers | ||
with the ability to decide how to enforce their security policy without impacting the client's implementation. | ||
# Acknowledgements | ||
@@ -297,0 +359,0 @@ |
@@ -92,3 +92,3 @@ // Load modules | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) { | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Date.now() }, function (err, credentials, ext) { | ||
@@ -112,3 +112,3 @@ expect(err).to.not.exist; | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) { | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353832234000 - Date.now() }, function (err, credentials, ext) { | ||
@@ -121,2 +121,25 @@ expect(err).to.not.exist; | ||
it('should fail on a stale timestamp', function (done) { | ||
var req = { | ||
headers: { | ||
authorization: 'Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", mac="hpf5lg0G0rtKrT04CiRf0Q+IDjkGkyvKdMjtqu1XV/s=", ext="some-app-data"', | ||
host: 'example.com:8000' | ||
}, | ||
method: 'GET', | ||
url: '/resource/1?b=1&a=2' | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) { | ||
expect(err).to.exist; | ||
expect(err.toResponse().payload.message).to.equal('Stale timestamp'); | ||
var header = err.headers['WWW-Authenticate']; | ||
var ts = header.match(/^Hawk ts\=\"(\d+)\"\, ntp\=\"pool.ntp.org\"\, error=\"Stale timestamp\"$/); | ||
var now = Date.now(); | ||
expect(parseInt(ts[1], 10)).to.be.within(now - 1, now + 1); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on a replay', function (done) { | ||
@@ -135,2 +158,3 @@ | ||
var options = { | ||
localtimeOffsetMsec: 1353788437000 - Date.now(), | ||
nonceFunc: function (nonce, ts, callback) { | ||
@@ -172,6 +196,6 @@ | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) { | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Date.now() }, function (err, credentials, ext) { | ||
expect(err).to.exist; | ||
expect(err.toResponse().payload.message).to.equal('Incorrect scheme'); | ||
expect(err.toResponse().payload.message).to.equal(''); | ||
done(); | ||
@@ -181,2 +205,21 @@ }); | ||
it('should fail on an invalid authentication header: no scheme', function (done) { | ||
var req = { | ||
headers: { | ||
authorization: '!@#', | ||
host: 'example.com:8080' | ||
}, | ||
method: 'GET', | ||
url: '/resource/4?filter=a' | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Date.now() }, function (err, credentials, ext) { | ||
expect(err).to.exist; | ||
expect(err.toResponse().payload.message).to.equal('Invalid header syntax'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on an missing authorization header', function (done) { | ||
@@ -195,3 +238,6 @@ | ||
expect(err).to.exist; | ||
expect(err.toResponse().payload.message).to.equal('Missing Authorization header'); | ||
var header = err.headers['WWW-Authenticate']; | ||
var ts = header.match(/^Hawk ts\=\"(\d+)\"\, ntp\=\"pool.ntp.org\"$/); | ||
var now = Date.now(); | ||
expect(parseInt(ts[1], 10)).to.be.within(now - 1, now + 1); | ||
done(); | ||
@@ -211,3 +257,3 @@ }); | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) { | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Date.now() }, function (err, credentials, ext) { | ||
@@ -231,3 +277,3 @@ expect(err).to.exist; | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) { | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Date.now() }, function (err, credentials, ext) { | ||
@@ -251,3 +297,3 @@ expect(err).to.exist; | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) { | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Date.now() }, function (err, credentials, ext) { | ||
@@ -271,3 +317,3 @@ expect(err).to.exist; | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) { | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Date.now() }, function (err, credentials, ext) { | ||
@@ -291,3 +337,3 @@ expect(err).to.exist; | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) { | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Date.now() }, function (err, credentials, ext) { | ||
@@ -311,6 +357,6 @@ expect(err).to.exist; | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) { | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Date.now() }, function (err, credentials, ext) { | ||
expect(err).to.exist; | ||
expect(err.toResponse().payload.message).to.equal('Unknown attributes'); | ||
expect(err.toResponse().payload.message).to.equal('Unknown attribute: x'); | ||
done(); | ||
@@ -320,2 +366,78 @@ }); | ||
it('should fail on an bad authorization header format', function (done) { | ||
var req = { | ||
headers: { | ||
authorization: 'Hawk id="123\\", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"', | ||
host: 'example.com:8080' | ||
}, | ||
method: 'GET', | ||
url: '/resource/4?filter=a' | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Date.now() }, function (err, credentials, ext) { | ||
expect(err).to.exist; | ||
expect(err.toResponse().payload.message).to.equal('Bad header format'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on an bad authorization attribute value', function (done) { | ||
var req = { | ||
headers: { | ||
authorization: 'Hawk id="\t", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"', | ||
host: 'example.com:8080' | ||
}, | ||
method: 'GET', | ||
url: '/resource/4?filter=a' | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Date.now() }, function (err, credentials, ext) { | ||
expect(err).to.exist; | ||
expect(err.toResponse().payload.message).to.equal('Bad attribute value: id'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on an empty authorization attribute value', function (done) { | ||
var req = { | ||
headers: { | ||
authorization: 'Hawk id="", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"', | ||
host: 'example.com:8080' | ||
}, | ||
method: 'GET', | ||
url: '/resource/4?filter=a' | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Date.now() }, function (err, credentials, ext) { | ||
expect(err).to.exist; | ||
expect(err.toResponse().payload.message).to.equal('Bad attribute value: id'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on duplicated authorization attribute key', function (done) { | ||
var req = { | ||
headers: { | ||
authorization: 'Hawk id="123", id="456", ts="1353788437", nonce="k3j4h2", mac="/qwS4UjfVWMcUyW6EEgUH4jlr7T/wuKe3dKijvTvSos=", ext="hello"', | ||
host: 'example.com:8080' | ||
}, | ||
method: 'GET', | ||
url: '/resource/4?filter=a' | ||
}; | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Date.now() }, function (err, credentials, ext) { | ||
expect(err).to.exist; | ||
expect(err.toResponse().payload.message).to.equal('Duplicate attribute: id'); | ||
done(); | ||
}); | ||
}); | ||
it('should fail on an invalid authorization header format', function (done) { | ||
@@ -332,6 +454,6 @@ | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) { | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Date.now() }, function (err, credentials, ext) { | ||
expect(err).to.exist; | ||
expect(err.toResponse().payload.message).to.equal('Invalid header format'); | ||
expect(err.toResponse().payload.message).to.equal('Invalid header syntax'); | ||
done(); | ||
@@ -352,3 +474,3 @@ }); | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) { | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Date.now() }, function (err, credentials, ext) { | ||
@@ -377,3 +499,3 @@ expect(err).to.exist; | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) { | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Date.now() }, function (err, credentials, ext) { | ||
@@ -402,3 +524,3 @@ expect(err).to.exist; | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) { | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Date.now() }, function (err, credentials, ext) { | ||
@@ -432,3 +554,3 @@ expect(err).to.exist; | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) { | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Date.now() }, function (err, credentials, ext) { | ||
@@ -464,3 +586,3 @@ expect(err).to.exist; | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) { | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Date.now() }, function (err, credentials, ext) { | ||
@@ -496,3 +618,3 @@ expect(err).to.exist; | ||
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) { | ||
Hawk.authenticate(req, credentialsFunc, { localtimeOffsetMsec: 1353788437000 - Date.now() }, function (err, credentials, ext) { | ||
@@ -555,47 +677,3 @@ expect(err).to.exist; | ||
}); | ||
describe('#fixedTimeComparison', function () { | ||
var a = Hawk.randomString(50000); | ||
var b = Hawk.randomString(150000); | ||
it('should take the same amount of time comparing different string sizes', function (done) { | ||
var now = Date.now(); | ||
Hawk.fixedTimeComparison(b, a); | ||
var t1 = Date.now() - now; | ||
now = Date.now(); | ||
Hawk.fixedTimeComparison(b, b); | ||
var t2 = Date.now() - now; | ||
expect(t2 - t1).to.be.within(-1, 1); | ||
done(); | ||
}); | ||
it('should return true for equal strings', function (done) { | ||
expect(Hawk.fixedTimeComparison(a, a)).to.equal(true); | ||
done(); | ||
}); | ||
it('should return false for different strings (size, a < b)', function (done) { | ||
expect(Hawk.fixedTimeComparison(a, a + 'x')).to.equal(false); | ||
done(); | ||
}); | ||
it('should return false for different strings (size, a > b)', function (done) { | ||
expect(Hawk.fixedTimeComparison(a + 'x', a)).to.equal(false); | ||
done(); | ||
}); | ||
it('should return false for different strings (size, a = b)', function (done) { | ||
expect(Hawk.fixedTimeComparison(a + 'x', a + 'y')).to.equal(false); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
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
71004
14
921
353
3