Socket
Socket
Sign inDemoInstall

hawk

Package Overview
Dependencies
Maintainers
1
Versions
85
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

hawk - npm Package Compare versions

Comparing version 0.2.0 to 0.3.0

lib/crypto.js

2

example/usage.js

@@ -58,3 +58,3 @@ // Load modules

headers: {
authorization: Hawk.getAuthorizationHeader(internals.credentials.dh37fgj492je, 'GET', '/resource/1?b=1&a=2', '127.0.0.1', 8000, 'some-app-data')
authorization: Hawk.getAuthorizationHeader(internals.credentials.dh37fgj492je, 'GET', '/resource/1?b=1&a=2', '127.0.0.1', 8000, { ext: 'some-app-data' })
}

@@ -61,0 +61,0 @@ };

// Load modules
var Crypto = require('crypto');
var URL = require('url');
var Crypto = require('./crypto');
var Err = require('./error');
var Utils = require('./utils');
var Uri = require('./uri');

@@ -14,4 +14,7 @@

// Export utilities
// Export sub-modules
exports.crypto = Crypto;
exports.error = exports.Error = Err;
exports.uri = Uri;
exports.utils = Utils;

@@ -58,9 +61,9 @@

*
* timestampSkewSec - 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.
*
* localtimeOffsetMsec - 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.
*
* ntp - hostname of the ntp server used to synchronize time between the client and the server. The
* ntp - optional 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

@@ -76,28 +79,26 @@ * current timestamp. This allows browser-based clients to sync their application clock directly

options.hostHeaderName = (options.hostHeaderName ? options.hostHeaderName.toLowerCase() : 'host');
options.nonceFunc = options.nonceFunc || function (nonce, ts, callback) { return callback(); };
options.nonceFunc = options.nonceFunc || function (nonce, ts, callback) { return callback(); }; // No validation
options.timestampSkewSec = options.timestampSkewSec || 60; // 60 seconds
options.localtimeOffsetMsec = options.localtimeOffsetMsec || 0; // 0 milliseconds
options.ntp = options.ntp || 'pool.ntp.org';
options.ntp = options.ntp || 'pool.ntp.org'; // pool.ntp.org
// Application time
var now = Date.now() + options.localtimeOffsetMsec;
var now = Date.now() + (options.localtimeOffsetMsec || 0);
// Check required HTTP headers: host, authentication
// Obtain host and port information
var hostHeader = req.headers[options.hostHeaderName];
if (!hostHeader) {
return callback(Err.badRequest('Missing Host header'), null, null);
var host = Utils.parseHost(req, options.hostHeaderName);
if (!host) {
return callback(Err.badRequest('Invalid Host header'));
}
// Parse HTTP Authorization header
if (!req.headers.authorization) {
return callback(Err.unauthorizedWithTs('', now, options.ntp), null, null);
return callback(Err.unauthorizedWithTs('', now, options.ntp));
}
// Parse HTTP Authorization header
var headerParts = req.headers.authorization.match(/^(\w+)(?:\s+(.*))?$/); // Header: scheme[ something]
if (!headerParts) {
return callback(Err.badRequest('Invalid header syntax'), null, null);
return callback(Err.badRequest('Invalid header syntax'));
}

@@ -107,3 +108,3 @@

if (scheme.toLowerCase() !== 'hawk') {
return callback(Err.unauthorizedWithTs('', now, options.ntp), null, null);
return callback(Err.unauthorizedWithTs('', now, options.ntp));
}

@@ -113,3 +114,3 @@

if (!attributesString) {
return callback(Err.badRequest('Invalid header syntax'), null, null);
return callback(Err.badRequest('Invalid header syntax'));
}

@@ -147,3 +148,3 @@

if (verify !== '') {
return callback(Err.badRequest(errorMessage || 'Bad header format'), null, null);
return callback(Err.badRequest(errorMessage || 'Bad header format'));
}

@@ -167,17 +168,2 @@

// Obtain host and port information
var hostHeaderRegex = /^(?:(?:\r\n)?[\t ])*([^:]+)(?::(\d+))?(?:(?:\r\n)?[\t ])*$/; // Does not support IPv6
var hostParts = hostHeader.match(hostHeaderRegex);
if (!hostParts ||
hostParts.length !== 3 ||
!hostParts[1]) {
return callback(Err.badRequest('Bad Host header'), null, attributes.ext);
}
var host = hostParts[1];
var port = (hostParts[2] ? hostParts[2] : (req.connection && req.connection.encrypted ? 443 : 80));
// Fetch Hawk credentials

@@ -192,3 +178,3 @@

if (!credentials) {
return callback(Err.unauthorized('Missing credentials'), null, attributes.ext);
return callback(Err.unauthorized('Unknown credentials'), null, attributes.ext);
}

@@ -202,3 +188,3 @@

if (['hmac-sha-1', 'hmac-sha-256'].indexOf(credentials.algorithm) === -1) {
if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {
return callback(Err.internal('Unknown algorithm'), credentials, attributes.ext);

@@ -209,3 +195,3 @@ }

var mac = exports.calculateMAC(credentials.key, credentials.algorithm, attributes.ts, attributes.nonce, req.method, req.url, host, port, attributes.ext);
var mac = Crypto.calculateMAC(credentials.key, credentials.algorithm, attributes.ts, attributes.nonce, req.method, req.url, host.name, host.port, attributes.ext);
if (!Utils.fixedTimeComparison(mac, attributes.mac)) {

@@ -231,38 +217,2 @@ return callback(Err.unauthorized('Bad mac'), credentials, attributes.ext);

// Calculate the request MAC
exports.calculateMAC = function (key, algorithm, timestamp, nonce, method, uri, host, port, ext) {
// Parse request URI
var url = URL.parse(uri);
// 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) {
case 'hmac-sha-1': hashMethod = 'sha1'; break;
case 'hmac-sha-256': hashMethod = 'sha256'; break;
default: return '';
}
// MAC normalized req string
var hmac = Crypto.createHmac(hashMethod, key).update(normalized);
var digest = hmac.digest('base64');
return digest;
};
// Generate an Authorization header for a given request

@@ -272,6 +222,14 @@

* credentials is an object with the following keys: 'id, 'key', 'algorithm'.
* options is an object with the following optional keys: 'ext', 'timestamp', 'nonce', 'localtimeOffsetMsec'
*/
exports.getAuthorizationHeader = function (credentials, method, uri, host, port, ext, timestamp, nonce) {
exports.getAuthorizationHeader = function (credentials, method, uri, host, port, options) {
options = options || {};
options.ext = (options.ext === null || options.ext === undefined ? '' : options.ext); // Zero is valid value
// Application time
var now = Date.now() + (options.localtimeOffsetMsec || 0);
// Check request

@@ -289,5 +247,5 @@

timestamp = timestamp || Math.floor(((new Date()).getTime() / 1000));
nonce = nonce || Utils.randomString(6);
var mac = exports.calculateMAC(credentials.key, credentials.algorithm, timestamp, nonce, method, uri, host, port, 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);

@@ -300,3 +258,3 @@ if (!mac) {

var header = 'Hawk id="' + credentials.id + '", ts="' + timestamp + '", nonce="' + nonce + (ext ? '", ext="' + Utils.escapeHeaderAttribute (ext) : '') + '", mac="' + mac + '"';
var header = 'Hawk id="' + credentials.id + '", ts="' + timestamp + '", nonce="' + nonce + (options.ext ? '", ext="' + Utils.escapeHeaderAttribute (options.ext) : '') + '", mac="' + mac + '"';
return header;

@@ -303,0 +261,0 @@ };

@@ -68,1 +68,27 @@ // Load modules

// Extract host and port from request
exports.parseHost = function (req, hostHeaderName) {
hostHeaderName = (hostHeaderName ? hostHeaderName.toLowerCase() : 'host');
var hostHeader = req.headers[hostHeaderName];
if (!hostHeader) {
return null;
}
var hostHeaderRegex = /^(?:(?:\r\n)?[\t ])*([^:]+)(?::(\d+))?(?:(?:\r\n)?[\t ])*$/; // Does not support IPv6
var hostParts = hostHeader.match(hostHeaderRegex);
if (!hostParts ||
hostParts.length !== 3 ||
!hostParts[1]) {
return null;
}
return {
name: hostParts[1],
port: (hostParts[2] ? hostParts[2] : (req.connection && req.connection.encrypted ? 443 : 80))
};
};
{
"name": "hawk",
"description": "HTTP Hawk Authentication Scheme",
"version": "0.2.0",
"version": "0.3.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.2.0**
Current version: **0.3.0**

@@ -18,3 +18,15 @@ [![Build Status](https://secure.travis-ci.org/hueniverse/hawk.png)](http://travis-ci.org/hueniverse/hawk)

<p></p>
- [**Single URI Authorization**](#single-uri-authorization)
- [Usage Example](#bewit-usage-example)
<p></p>
- [**Security Considerations**](#security-considerations)
- [MAC Keys Transmission](#mac-keys-transmission)
- [Confidentiality of Requests](#confidentiality-of-requests)
- [Spoofing by Counterfeit Servers](#spoofing-by-counterfeit-servers)
- [Plaintext Storage of Credentials](#plaintext-storage-of-credentials)
- [Entropy of Keys](#entropy-of-keys)
- [Coverage Limitations](#coverage-limitations)
- [Future Time Manipulation](#future-time-manipulation)
- [Client Clock Poisoning](#client-clock-poisoning)
- [Bewit Limitations](#bewit-limitations)
<p></p>

@@ -51,3 +63,6 @@ - [**Frequently Asked Questions**](#frequently-asked-questions)

In addition, **Hawk** supports a method for granting third-parties temporary access to individual resources using
a query parameter called _bewit_ (leather straps used to attach a tracking device to the leg of a hawk).
## Time Synchronization

@@ -100,6 +115,6 @@

Hawk.authenticate(req, credentialsFunc, {}, function (err, isAuthenticated, credentials, ext) {
Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) {
res.writeHead(isAuthenticated ? 200 : 401, { 'Content-Type': 'text/plain' });
res.end(isAuthenticated ? 'Hello ' + credentials.user : 'Shoosh!');
res.writeHead(!err ? 200 : 401, { 'Content-Type': 'text/plain' });
res.end(!err ? 'Hello ' + credentials.user : 'Shoosh!');
});

@@ -132,3 +147,3 @@ };

headers: {
authorization: Hawk.getAuthorizationHeader(credentials, 'GET', '/resource/1?b=1&a=2', 'example.com', 8000, 'some-app-data')
authorization: Hawk.getAuthorizationHeader(credentials, 'GET', '/resource/1?b=1&a=2', 'example.com', 8000, { ext: 'some-app-data' })
}

@@ -202,2 +217,75 @@ };

# Single URI Authorization
There are often cases in which limited and short-term access is granted to protected resource to a third party which does not
have access to the shared credentials. For example, displaying a protected image on a web page accessed by anyone. **Hawk**
provides limited support for such URIs in the form of a _bewit_ - a URI query parameter appended to the request URI which contains
the necessary credentials to authenticate the request.
Because of the significant security risks involved in issuing such access, bewit usage is purposely limited to only GET requests
and for a finite period of time. Both the client and server can issue bewit credentials, however, the server should not use the same
credentials as the client to maintain clear traceability as to who issued which credentials.
In order to simplify implementation, bewit credentials do not support single-use policy and can be replayed multiple times within
the granted access timeframe.
## Bewit Usage Example
Server code:
```javascript
var Http = require('http');
var Hawk = require('hawk');
// Credentials lookup function
var credentialsFunc = function (id, callback) {
var credentials = {
key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
algorithm: 'hmac-sha-256'
};
return callback(null, credentials);
};
// Create HTTP server
var handler = function (req, res) {
Hawk.uri.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) {
res.writeHead(!err ? 200 : 401, { 'Content-Type': 'text/plain' });
res.end(!err ? 'Access granted' : 'Shoosh!');
});
};
Http.createServer(handler).listen(8000, 'example.com');
```
Bewit code generation:
```javascript
var Request = require('request');
var Hawk = require('hawk');
// Client credentials
var credentials = {
id: 'dh37fgj492je',
key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
algorithm: 'hmac-sha-256'
}
// Generate bewit
var duration = 60 * 5; // 5 Minutes
var bewit = Hawk.uri.getBewit(credentials, '/resource/1?b=1&a=2', 'example.com', 8080, duration, { ext: 'some-app-data' });
var uri = 'http://example.com:8000/resource/1?b=1&a=2' + '&bewit=' + bewit;
```
# Security Considerations

@@ -285,3 +373,11 @@

### Bewit Limitations
Special care must be taken when issuing bewit credentials to third parties. Bewit credentials are valid until expiration and cannot
be revoked or limited without using other means. Whatever resource they grant access to will be completely exposed to anyone with
access to the bewit credentials which act as bearer credentials for that particular resource. While bewit usage is limited to GET
requests only and therefore cannot be used to perform transactions or change server state, it can still be used to expose private
and sensitive information.
# Frequently Asked Questions

@@ -355,3 +451,3 @@

**Hawk** is a derivative work of the [HTTP MAC Authentication Scheme](http://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-05) proposal
Co-authored by Ben Adida, Adam Barth, and Eran Hammer, which in turn was based on the OAuth 1.0 community specification.
co-authored by Ben Adida, Adam Barth, and Eran Hammer, which in turn was based on the OAuth 1.0 community specification.

@@ -358,0 +454,0 @@ Special thanks to Ben Laurie for his always insightful feedback and advice.

@@ -43,3 +43,3 @@ // Load modules

req.headers.authorization = Hawk.getAuthorizationHeader(credentials, req.method, req.url, 'example.com', 8080, 'some-app-data');
req.headers.authorization = Hawk.getAuthorizationHeader(credentials, req.method, req.url, 'example.com', 8080, { ext: 'some-app-data' });

@@ -68,3 +68,3 @@ Hawk.authenticate(req, credentialsFunc, {}, function (err, credentials, ext) {

req.headers.authorization = Hawk.getAuthorizationHeader(credentials, req.method, req.url, 'example.com', 8080, 'some-app-data');
req.headers.authorization = Hawk.getAuthorizationHeader(credentials, req.method, req.url, 'example.com', 8080, { ext: 'some-app-data' });
req.url = '/something/else';

@@ -255,3 +255,3 @@

expect(err).to.exist;
expect(err.toResponse().payload.message).to.equal('Missing Host header');
expect(err.toResponse().payload.message).to.equal('Invalid Host header');
done();

@@ -465,3 +465,3 @@ });

expect(err).to.exist;
expect(err.toResponse().payload.message).to.equal('Bad Host header');
expect(err.toResponse().payload.message).to.equal('Invalid Host header');
done();

@@ -514,3 +514,3 @@ });

expect(err).to.exist;
expect(err.toResponse().payload.message).to.equal('Missing credentials');
expect(err.toResponse().payload.message).to.equal('Unknown credentials');
done();

@@ -612,11 +612,2 @@ });

describe('#calculateMAC', function () {
it('should return an empty value on unknown algorithm', function (done) {
expect(Hawk.calculateMAC('dasdfasdf', 'hmac-sha-0', Date.now() / 1000, 'k3k4j5', 'GET', '/resource/something', 'example.com', 8080)).to.equal('');
done();
});
});
describe('#getAuthorizationHeader', function () {

@@ -632,3 +623,3 @@

var header = Hawk.getAuthorizationHeader(credentials, 'POST', '/somewhere/over/the/rainbow', 'example.net', 443, 'Bazinga!', 1353809207, 'Ygvqdz');
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="');

@@ -645,3 +636,3 @@ done();

var header = Hawk.getAuthorizationHeader(credentials, 'POST', '/somewhere/over/the/rainbow', 'example.net', 443, 'Bazinga!', 1353809207);
var header = Hawk.getAuthorizationHeader(credentials, 'POST', '/somewhere/over/the/rainbow', 'example.net', 443, { ext: 'Bazinga!', timestamp: 1353809207 });
expect(header).to.equal('');

@@ -659,3 +650,3 @@ done();

var header = Hawk.getAuthorizationHeader(credentials, 'POST', '/somewhere/over/the/rainbow', 'example.net', 443, 'Bazinga!', 1353809207);
var header = Hawk.getAuthorizationHeader(credentials, 'POST', '/somewhere/over/the/rainbow', 'example.net', 443, { ext: 'Bazinga!', timestamp: 1353809207 });
expect(header).to.equal('');

@@ -662,0 +653,0 @@ done();

@@ -46,3 +46,3 @@ // Load modules

expect(t2 - t1).to.be.within(-1, 1);
expect(t2 - t1).to.be.within(-2, 2);
done();

@@ -49,0 +49,0 @@ });

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc