Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

hmac-authentication

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

hmac-authentication - npm Package Compare versions

Comparing version 0.0.2 to 1.0.0

.travis.yml

127

index.js

@@ -5,14 +5,30 @@ /* jshint node: true */

var bufferEq = require('buffer-equal-constant-time');
var crypto = require('crypto');
var url = require('url');
var exports = module.exports = {};
module.exports = HmacAuth;
HmacAuth.AuthenticationError = AuthenticationError;
exports.NO_SIGNATURE = 1;
exports.INVALID_FORMAT = 2;
exports.UNSUPPORTED_ALGORITHM = 3;
exports.MATCH = 4;
exports.MISMATCH = 5;
function HmacAuth(digestName, key, signatureHeader, headers) {
this.digestName = digestName.toLowerCase();
try {
crypto.createHash(digestName);
} catch (_) {
throw new Error(
'HMAC authentication digest is not supported: ' + digestName);
}
this.key = key;
this.signatureHeader = signatureHeader.toLowerCase();
this.headers = headers.map(function(h) { return h.toLowerCase(); });
}
HmacAuth.NO_SIGNATURE = 1;
HmacAuth.INVALID_FORMAT = 2;
HmacAuth.UNSUPPORTED_ALGORITHM = 3;
HmacAuth.MATCH = 4;
HmacAuth.MISMATCH = 5;
var resultStrings = [
'',
'NO_SIGNATURE',

@@ -25,30 +41,54 @@ 'INVALID_FORMAT',

exports.resultCodeToString = function(code) {
if (code < 1 || code >= resultStrings.length) { return; }
return resultStrings[code];
HmacAuth.resultCodeToString = function(code) {
return resultStrings[code - 1];
};
function signedHeaders(req, headers) {
return headers.map(function(header) { return req.get(header) || ''; });
return headers.map(function(header) {
var value = req.headers[header];
if (Array.isArray(value)) { value = value.join(','); }
return value || '';
});
}
exports.stringToSign = function(req, headers) {
HmacAuth.prototype.stringToSign = function(req) {
var parsedUrl = url.parse(req.url);
var hashUrl = parsedUrl.path + (parsedUrl.hash || '');
return [
req.method, signedHeaders(req, headers).join('\n'), req.url
].join('\n');
req.method, signedHeaders(req, this.headers).join('\n'), hashUrl
].join('\n') + '\n';
};
exports.requestSignature = function(
req, rawBody, digestName, headers, secretKey) {
var hmac = crypto.createHmac(digestName, secretKey);
hmac.update(exports.stringToSign(req, headers));
HmacAuth.prototype.signRequest = function(req, rawBody) {
req.headers[this.signatureHeader] = this.requestSignature(req, rawBody);
};
HmacAuth.prototype.requestSignature = function(req, rawBody) {
return requestSignature(this, req, rawBody, this.digestName);
};
function requestSignature(auth, req, rawBody, digestName) {
var hmac = crypto.createHmac(digestName, auth.key);
hmac.update(auth.stringToSign(req));
hmac.update(rawBody || '');
return digestName + ' ' + hmac.digest('base64');
}
HmacAuth.prototype.signatureFromHeader = function(req) {
return req.headers[this.signatureHeader];
};
exports.validateRequest = function(req, rawBody, headers, secretKey) {
var header = req.get('Gap-Signature');
if (!header) { return [exports.NO_SIGNATURE]; }
// Replace bufferEq() once https://github.com/nodejs/node/issues/3043 is
// resolved and the standard library implementation is available.
function compareSignatures(lhs, rhs) {
var lbuf = new Buffer(lhs);
var rbuf = new Buffer(rhs);
return bufferEq(lbuf, rbuf) ? HmacAuth.MATCH : HmacAuth.MISMATCH;
}
HmacAuth.prototype.authenticateRequest = function(req, rawBody) {
var header = this.signatureFromHeader(req);
if (!header) { return [HmacAuth.NO_SIGNATURE]; }
var components = header.split(' ');
if (components.length != 2) { return [exports.INVALID_FORMAT, header]; }
if (components.length !== 2) { return [HmacAuth.INVALID_FORMAT, header]; }
var digestName = components[0];

@@ -58,17 +98,16 @@ try {

} catch (e) {
return [exports.UNSUPPORTED_ALGORITHM, header];
return [HmacAuth.UNSUPPORTED_ALGORITHM, header];
}
var computed = exports.requestSignature(
req, rawBody, digestName, headers, secretKey);
var result = (header == computed) ? exports.MATCH : exports.MISMATCH;
return [result, header, computed];
var computed = requestSignature(this, req, rawBody, digestName);
return [compareSignatures(header, computed), header, computed];
};
function ValidationError(result, header, computed) {
this.name = 'ValidationError';
function AuthenticationError(signatureHeader, result, header, computed) {
this.name = 'AuthenticationError';
this.signatureHeader = signatureHeader;
this.result = result;
this.header = header;
this.computed = computed;
this.message = 'hmac signature request validation failed: ' +
exports.resultCodeToString(result);
this.message = signatureHeader + ' authentication failed: ' +
HmacAuth.resultCodeToString(result);
if (header) { this.message += ' header: "' + header + '"'; }

@@ -78,19 +117,23 @@ if (computed) { this.message += ' computed: "' + computed + '"'; }

}
ValidationError.prototype = Object.create(Error.prototype);
ValidationError.prototype.constructor = ValidationError;
exports.ValidationError = ValidationError;
AuthenticationError.prototype = Object.create(Error.prototype);
AuthenticationError.prototype.constructor = AuthenticationError;
exports.middlewareValidator = function(headers, secretKey) {
HmacAuth.middlewareAuthenticator = function(
secretKey, signatureHeader, headers) {
// Since the object is only used for authentication, the digestName can be
// anything valid. The actual digest function used during authentication
// depends on the digest name used as a prefix to the signature header.
var auth = new HmacAuth('sha1', secretKey, signatureHeader, headers);
return function(req, res, buf, encoding) {
var rawBody = buf.toString(encoding);
var validationResult = exports.validateRequest(
req, rawBody, headers, secretKey);
var result = validationResult[0];
var authenticationResult = auth.authenticateRequest(req, rawBody);
var result = authenticationResult[0];
if (result != exports.MATCH) {
var header = validationResult[1];
var computed = validationResult[2];
throw new ValidationError(result, header, computed);
if (result != HmacAuth.MATCH) {
var header = authenticationResult[1];
var computed = authenticationResult[2];
throw new AuthenticationError(signatureHeader, result, header, computed);
}
};
};
{
"name": "hmac-authentication",
"version": "0.0.2",
"version": "1.0.0",
"description": "Signs and validates HTTP requests based on a shared-secret HMAC signature",

@@ -31,3 +31,6 @@ "main": "index.js",

"node-mocks-http": "^1.4.4"
},
"dependencies": {
"buffer-equal-constant-time": "^1.0.1"
}
}
# hmac-authentication npm
Signs and validates HTTP requests based on a shared-secret HMAC signature.
Signs and authenticates HTTP requests based on a shared-secret HMAC signature.
Developed in parallel with the following packages for other languages:
- Go: [github.com/18F/hmacauth](https://github.com/18F/hmacauth/)
- Ruby: [hmac_authentication](https://rubygems.org/gems/hmac_authentication)
## Installation

@@ -11,7 +15,8 @@

## Validating incoming requests
## Authenticating incoming requests
Assuming you're using [Express](https://www.npmjs.com/package/express), during
initialization of your application, where `config.headers` is a list of
headers factored into the signature and `config.secretKey` is the shared
initialization of your application, where `config.signatureHeader` identifies
the header containing the message signature, `config.headers` is a list of
headers factored into the signature, and `config.secretKey` is the shared
secret between your application and the service making the request:

@@ -22,8 +27,9 @@

var bodyParser = require('bodyParser');
var hmacAuthentication = require('hmac-authentication');
var HmacAuth = require('hmac-authentication');
var config = require('./config.json');
function doLaunch(config) {
var middlewareOptions = {
verify: hmacAuthentication.middlewareValidator(
config.headers, config.secretKey)
verify: HmacAuth.middlewareAuthenticator(
config.secretKey, config.signatureHeader, config.headers)
};

@@ -37,11 +43,41 @@ var server = express();

If you're not using Express, you can use the function `validateRequest(req,
rawBody, headers, secretKey)` directly, where `rawBody` has already been
converted to a string.
If you're not using Express, you can use something similar to the following:
```js
var HmacAuth = require('hmac-authentication');
var config = require('./config.json');
// When only used for authentication, it doesn't matter what the first
// argument is, because the hash algorithm used for authentication will be
// parsed from the incoming request signature header.
var auth = new HmacAuth(
'sha1', config.secretKey, config.signatureHeader, config.headers);
// rawBody must be a string.
function requestHandler(req, rawBody) {
var authenticationResult = auth.authenticateRequest(req, rawBody);
if (authenticationResult[0] != HmacAuth.MATCH) {
// Handle authentication failure...
}
}
```
## Signing outgoing requests
Call `requestSignature(request, rawBody, digestName, headers, secretKey)` to
sign a request before sending. `rawBody` and `digestName` must be strings.
Do something similar to the following. `rawBody` must be a string.
```js
var HmacAuth = require('hmac-authentication');
var config = require('./config.json');
var auth = new HmacAuth(
config.digestName, config.secretKey, config.signatureHeader, config.headers);
function makeRequest(req, rawBody) {
// Prepare request...
auth.signRequest(req, rawBody);
}
```
## Public domain

@@ -48,0 +84,0 @@

@@ -8,3 +8,3 @@ /* jshint node: true */

var httpMocks = require('node-mocks-http');
var validator = require('../index');
var HmacAuth = require('../index');

@@ -29,18 +29,49 @@ var expect = chai.expect;

var auth = new HmacAuth('SHA1', 'foobar', 'GAP-Signature', HEADERS);
describe('HmacAuth constructor', function() {
it('should lowercase the hash function and all header names', function() {
expect(auth.digestName).to.eql('sha1');
expect(auth.key).to.eql('foobar');
expect(auth.signatureHeader).to.eql('gap-signature');
expect(auth.headers).to.eql([
'content-length',
'content-md5',
'content-type',
'date',
'authorization',
'x-forwarded-user',
'x-forwarded-email',
'x-forwarded-access-token',
'cookie',
'gap-auth'
]);
});
it('should throw if the hash function is not supported', function() {
var bogusAuth;
var f = function() {
bogusAuth = new HmacAuth('bogus', 'foobar', 'GAP-Signature', HEADERS);
};
expect(f).to.throw(
Error, 'HMAC authentication digest is not supported: bogus');
});
});
describe('resultCodeToString', function() {
it('should return undefined for out-of-range values', function() {
expect(validator.resultCodeToString(0)).to.be.undefined;
expect(validator.resultCodeToString(6)).to.be.undefined;
expect(HmacAuth.resultCodeToString(0)).to.be.undefined;
expect(HmacAuth.resultCodeToString(6)).to.be.undefined;
});
it('should return the correct matching strings', function() {
expect(validator.resultCodeToString(validator.NO_SIGNATURE))
expect(HmacAuth.resultCodeToString(HmacAuth.NO_SIGNATURE))
.to.eql('NO_SIGNATURE');
expect(validator.resultCodeToString(validator.INVALID_FORMAT))
expect(HmacAuth.resultCodeToString(HmacAuth.INVALID_FORMAT))
.to.eql('INVALID_FORMAT');
expect(validator.resultCodeToString(validator.UNSUPPORTED_ALGORITHM))
expect(HmacAuth.resultCodeToString(HmacAuth.UNSUPPORTED_ALGORITHM))
.to.eql('UNSUPPORTED_ALGORITHM');
expect(validator.resultCodeToString(validator.MATCH))
expect(HmacAuth.resultCodeToString(HmacAuth.MATCH))
.to.eql('MATCH');
expect(validator.resultCodeToString(validator.MISMATCH))
expect(HmacAuth.resultCodeToString(HmacAuth.MISMATCH))
.to.eql('MISMATCH');

@@ -71,3 +102,3 @@ });

expect(validator.stringToSign(req, HEADERS)).to.eql(
expect(auth.stringToSign(req)).to.eql(
['POST',

@@ -85,12 +116,11 @@ payload.length.toString(),

'/foo/bar'
].join('\n'));
expect(
validator.requestSignature(req, payload, 'sha1', HEADERS, 'foobar'))
.to.eql('sha1 722UbRYfC6MnjtIxqEJMDPrW2mk=');
].join('\n') + '\n');
expect(auth.requestSignature(req, payload))
.to.eql('sha1 K4IrVDtMCRwwW8Oms0VyZWMjXHI=');
});
it('should correctly sign a GET request', function() {
it('should correctly sign a GET request with a complete URL', function() {
var httpOptions = {
method: 'GET',
url: '/foo/bar',
url: 'http://localhost/foo/bar?baz=quux%2Fxyzzy#plugh',
headers: {

@@ -104,3 +134,3 @@ 'Date': '2015-09-29',

expect(validator.stringToSign(req, HEADERS)).to.eql(
expect(auth.stringToSign(req)).to.eql(
['GET',

@@ -117,11 +147,40 @@ '',

'mbland',
'/foo/bar?baz=quux%2Fxyzzy#plugh'
].join('\n') + '\n');
expect(auth.requestSignature(req, undefined))
.to.eql('sha1 ih5Jce9nsltry63rR4ImNz2hdnk=');
});
it('should correctly sign a GET w/ multiple values for header', function() {
var httpOptions = {
method: 'GET',
url: '/foo/bar',
headers: {
'Date': '2015-09-29',
'Cookie': ['foo', 'bar', 'baz=quux'],
'Gap-Auth': 'mbland'
}
};
var req = httpMocks.createRequest(httpOptions);
expect(auth.stringToSign(req)).to.eql(
['GET',
'',
'',
'',
'2015-09-29',
'',
'',
'',
'',
'foo,bar,baz=quux',
'mbland',
'/foo/bar'
].join('\n'));
expect(
validator.requestSignature(req, undefined, 'sha1', HEADERS, 'foobar'))
.to.eql('sha1 JBQJcmSTteQyHZXFUA9glis9BIk=');
].join('\n') + '\n');
expect(auth.requestSignature(req, undefined))
.to.eql('sha1 JlRkes1X+qq3Bgc/GcRyLos+4aI=');
});
});
describe('validateRequest and middlewareValidator', function() {
describe('authenticateRequest and middlewareAuthenticator', function() {
var createRequest = function(headerSignature) {

@@ -143,27 +202,28 @@ var httpOptions = {

var validateRequest = function(request, secretKey) {
var validate = validator.middlewareValidator(HEADERS, secretKey);
validate(request, undefined, new Buffer(0), 'utf-8');
var authenticateRequest = function(request, secretKey) {
var authenticate = HmacAuth.middlewareAuthenticator(
secretKey, 'Gap-Signature', HEADERS);
authenticate(request, undefined, new Buffer(0), 'utf-8');
};
it('should throw ValidationError with NO_SIGNATURE', function() {
var f = function() { validateRequest(createRequest(), 'foobar'); };
expect(f).to.throw(validator.ValidationError, 'failed: NO_SIGNATURE');
it('should throw AuthenticationError with NO_SIGNATURE', function() {
var f = function() { authenticateRequest(createRequest(), 'foobar'); };
expect(f).to.throw(HmacAuth.AuthenticationError, 'failed: NO_SIGNATURE');
});
it('should throw ValidationError with INVALID_FORMAT', function() {
it('should throw AuthenticationError with INVALID_FORMAT', function() {
var badValue = 'should be algorithm and digest value';
var f = function() {
var request = createRequest(badValue);
validateRequest(request, 'foobar');
authenticateRequest(request, 'foobar');
};
expect(f).to.throw(
validator.ValidationError,
HmacAuth.AuthenticationError,
'failed: INVALID_FORMAT header: "' + badValue + '"');
});
it('should throw ValidationError with UNSUPPORTED_ALGORITHM', function() {
it('should throw AuthenticationError with UNSUPPORTED_ALGORITHM',
function() {
var request = createRequest();
var validSignature = validator.requestSignature(
request, null, 'sha1', HEADERS, 'foobar');
var validSignature = auth.requestSignature(request, null);
var components = validSignature.split(' ');

@@ -173,7 +233,7 @@ var signatureWithUnsupportedAlgorithm = 'unsupported ' + components[1];

var f = function() {
validateRequest(
authenticateRequest(
createRequest(signatureWithUnsupportedAlgorithm), 'foobar');
};
expect(f).to.throw(
validator.ValidationError,
HmacAuth.AuthenticationError,
'failed: UNSUPPORTED_ALGORITHM ' +

@@ -183,13 +243,11 @@ 'header: "' + signatureWithUnsupportedAlgorithm + '"');

it('should validate the request with MATCH', function() {
it('should authenticate the request with MATCH', function() {
var request = createRequest();
var expectedSignature = validator.requestSignature(
request, null, 'sha1', HEADERS, 'foobar');
request = createRequest(expectedSignature);
validateRequest(request, 'foobar');
var expectedSignature = auth.requestSignature(request, null);
auth.signRequest(request);
authenticateRequest(request, 'foobar');
// If we reach this point the result was a MATCH. Call
// validator.validateRequest() directly so we can inspect the values.
var results = validator.validateRequest(
request, undefined, HEADERS, 'foobar');
// auth.authenticateRequest() directly so we can inspect the values.
var results = auth.authenticateRequest(request, undefined);
var result = results[0];

@@ -199,3 +257,3 @@ var header = results[1];

expect(result).to.eql(validator.MATCH);
expect(result).to.eql(HmacAuth.MATCH);
expect(header).to.eql(expectedSignature);

@@ -205,19 +263,17 @@ expect(computed).to.eql(expectedSignature);

it('should throw ValidationError with MISMATCH', function() {
it('should throw AuthenticationError with MISMATCH', function() {
var request = createRequest();
var foobarSignature = validator.requestSignature(
request, null, 'sha1', HEADERS, 'foobar');
var barbazSignature = validator.requestSignature(
request, null, 'sha1', HEADERS, 'barbaz');
var barbazAuth = new HmacAuth('sha1', 'barbaz', 'Gap-Signature', HEADERS);
var f = function() {
validateRequest(createRequest(foobarSignature), 'barbaz');
auth.signRequest(request);
authenticateRequest(request, 'barbaz');
};
expect(f).to.throw(
validator.ValidationError,
HmacAuth.AuthenticationError,
'failed: MISMATCH ' +
'header: "' + foobarSignature + '" ' +
'computed: "' + barbazSignature + '"');
'header: "' + auth.requestSignature(request, null) + '" ' +
'computed: "' + barbazAuth.requestSignature(request, null) + '"');
});
});
});

Sorry, the diff of this file is not supported yet

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