From cf96266a20f05074987c48c13748bf13c3309720 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Thu, 31 May 2012 23:40:34 -0400 Subject: [PATCH] code cleanup - remove callbacks (functions are sync) - update tests for removed callbacks - minimize some code duplication --- lib/notp.js | 271 ++++++++++++++------------------------------------- test/notp.js | 229 ++++++++++--------------------------------- 2 files changed, 126 insertions(+), 374 deletions(-) diff --git a/lib/notp.js b/lib/notp.js index 3a27690..9cb69e2 100644 --- a/lib/notp.js +++ b/lib/notp.js @@ -1,16 +1,11 @@ var crypto = require('crypto'); -var base32 = require('thirty-two'); -/* +/** * Check a One Time Password based on a counter. * - * First argument of callback is true if password check is successful, - * or false if check fails. - * - * Second argument is the time step difference between the client and - * the server. This argument is only passed if the password check is - * successful. + * @return {Object} null if failure, { delta: # } on success + * delta is the time step difference between the client and the server * * Arguments: * @@ -35,48 +30,33 @@ var base32 = require('thirty-two'); * be user specific, and be incremented for each request. * */ -module.exports.checkHOTP = function(args, cb) { +module.exports.checkHOTP = function(args) { - var hmac, - digest, - offset, h, v, p = 6, b, - i, - K = args.K || "", - W = args.W || 50, - C = args.C || 0, - P = args.P || ""; - - - // Initiate the HMAC - hmac = crypto.createHmac('SHA1', new Buffer(K)) + var W = args.W || 50; + var C = args.C || 0; + var P = args.P || ''; // Now loop through from C to C + W to determine if there is // a correct code - for(i = C; i <= C+W; i++) { - if(this._calcHMAC(K,i) === P) { + for(i = C; i <= C+W; ++i) { + args.C = i; + if(this.getHOTP(args) === P) { // We have found a matching code, trigger callback // and pass offset - cb(true, i - C); - return; + return { delta: i - C }; } } // If we get to here then no codes have matched, return false - cb(false); - return; - + return false; }; -/* +/** * Check a One Time Password based on a timer. * - * First argument of callback is true if password check is successful, - * or false if check fails. - * - * Second argument is the time step difference between the client and - * the server. This argument is only passed if the password check is - * successful. + * @return {Object} null if failure, { delta: # } on success + * delta is the time step difference between the client and the server * * Arguments: * @@ -103,60 +83,33 @@ module.exports.checkHOTP = function(args, cb) { * Default - 30 * */ -module.exports.checkTOTP = function(args, cb) { - - var hmac, - digest, - offset, h, v, p = 6, b, - C,i, - K = args.K || "", - W = args.W || 6, - T = args.T || 30, - P = args.P || "", - _t; +module.exports.checkTOTP = function(args) { + var T = args.T || 30; + var _t = new Date().getTime(); + // Time has been overwritten. if(args._t) { - // Time has been overwritten. console.log('#####################################'); console.log('# NOTE: TOTP TIME VARIABLE HAS BEEN #'); console.log('# OVERWRITTEN. THIS SHOULD ONLY BE #'); console.log('# USED FOR TEST PURPOSES. #'); console.log('#####################################'); _t = args._t; - } else { - _t = new Date().getTime(); } - // Initiate the HMAC - hmac = crypto.createHmac('SHA1', new Buffer(K)) - // Determine the value of the counter, C // This is the number of time steps in seconds since T0 - C = Math.floor((_t / 1000) / T); + args.C = Math.floor((_t / 1000) / T); - // Now loop through from C - W to C + W and check to see - // if we have a valid code in that time line - for(i = C-W; i <= C+W; i++) { - - if(this._calcHMAC(K,i) === P) { - // We have found a matching code, trigger callback - // and pass offset - cb(true, i-C); - return; - } - } - - // If we get to here then no codes have matched, return false - cb(false); - return; + return module.exports.checkHOTP(args); }; -/* - * Gennerate a counter based One Time Password +/** + * Generate a counter based One Time Password * - * First argument of callback is the value of the One Time Password + * @return {String} the one time password * * Arguments: * @@ -168,28 +121,40 @@ module.exports.checkTOTP = function(args, cb) { * be user specific, and be incremented for each request. * */ -module.exports.getHOTP = function(args, cb) { +module.exports.getHOTP = function(args) { + var key = args.K || ''; + var counter = args.C || 0; - var hmac, - digest, - offset, h, v, p = 6, b, - i, - K = args.K || "", - C = args.C || 0, + var p = 6; + // Create the byte array + var b = new Buffer(intToBytes(counter)); - // Initiate the HMAC - hmac = crypto.createHmac('SHA1', new Buffer(K)) + var hmac = crypto.createHmac('SHA1', new Buffer(key)); - cb(this._calcHMAC(K,C)); + // Update the HMAC witht he byte array + var digest = hmac.update(b).digest('hex'); + // Get byte array + var h = hexToBytes(digest); + + // Truncate + var offset = h[19] & 0xf; + var v = (h[offset] & 0x7f) << 24 | + (h[offset + 1] & 0xff) << 16 | + (h[offset + 2] & 0xff) << 8 | + (h[offset + 3] & 0xff); + + v = v + ''; + + return v.substr(v.length - p, p); }; -/* - * Gennerate a time based One Time Password +/** + * Generate a time based One Time Password * - * First argument of callback is the value of the One Time Password + * @return {String} the one time password * * Arguments: * @@ -203,127 +168,37 @@ module.exports.getHOTP = function(args, cb) { * Default - 30 * */ -module.exports.getTOTP = function(args, cb) { - var hmac, - digest, - offset, h, v, p = 6, b, - C,i, - K = args.K || "", - T = args.T || 30, - _t; - +module.exports.getTOTP = function(args) { + var K = args.K || ''; + var T = args.T || 30; + var _t = new Date().getTime();; + // Time has been overwritten. if(args._t) { - // Time has been overwritten. console.log('#####################################'); console.log('# NOTE: TOTP TIME VARIABLE HAS BEEN #'); console.log('# OVERWRITTEN. THIS SHOULD ONLY BE #'); console.log('# USED FOR TEST PURPOSES. #'); console.log('#####################################'); _t = args._t; - } else { - _t = new Date().getTime(); } - // Initiate the HMAC - hmac = crypto.createHmac('SHA1', new Buffer(K)) - // Determine the value of the counter, C // This is the number of time steps in seconds since T0 - C = Math.floor((_t / 1000) / T); + args.C = Math.floor((_t / 1000) / T); - cb(this._calcHMAC(K,C)); + return this.getHOTP(args); }; - -/* - * Helper function to convert a string to a base32 encoded string - * - * Arguments: - * - * str - String to encode - * - * Returns: Base 32 encoded string +/** + * convert an integer to a byte array + * @param {Integer} num + * @return {Array} bytes */ -module.exports.encBase32 = function(str) { - return base32.encode(str); -}; +var intToBytes = function(num) { + var bytes = []; - -/* - * Helper function to convert a base32 encoded string to an ascii string - * - * Arguments: - * - * b32 - String to decode - * - * Returns: ASCII string - */ -module.exports.decBase32 = function(b32) { - return base32.decode(b32); -}; - - -/****************************************************************** - * NOTE: Any functions below this line are private and therefore * - * may change without providing backwards compatibility with * - * previous versions. You should not call the functions below * - * directly. * -*******************************************************************/ - - -/* - * Private functon to calculate an HMAC. - * - * Arguments - * - * K - Key value - * C - Counter value - * - * Returns - truncated HMAC - */ -module.exports._calcHMAC = function(K, C) { - - var hmac = crypto.createHmac('SHA1', new Buffer(K)), - digest, - offset, h, v, p = 6, b; - - // Create the byte array - b = new Buffer(this._intToBytes(C)), - - // Update the HMAC witht he byte array - hmac.update(b); - - // Diget the HMAC - digest = hmac.digest('hex'); - - // Get byte array - h = this._hexToBytes(digest); - - // Truncate - offset = h[19] & 0xf; - v = (h[offset] & 0x7f) << 24 | (h[offset + 1] & 0xff) << 16 | (h[offset + 2] & 0xff) << 8 | (h[offset + 3] & 0xff); - v = "" + v; - v = v.substr(v.length - p, p); - - return v; -}; - - -/* - * Private function to convert an integer to a byte array - * - * Arguments - * - * num - Integer - * - * Returns - byte array - */ -module.exports._intToBytes = function(num) { - var bytes = [], - i; - - for(i=7;i>=0;i--) { + for(var i=7 ; i>=0 ; --i) { bytes[i] = num & (255); num = num >> 8; } @@ -332,18 +207,16 @@ module.exports._intToBytes = function(num) { }; -/* - * Private function to convert a hex value to a byte array - * - * Arguments - * - * hex - Hex value - * - * Returns - byte array +/** + * convert a hex value to a byte array + * @param {String} hex string of hex to convert to a byte array + * @return {Array} bytes */ -module.exports._hexToBytes = function(hex) { - for(var bytes = [], c = 0; c < hex.length; c += 2) - bytes.push(parseInt(hex.substr(c, 2), 16)); +var hexToBytes = function(hex) { + var bytes = []; + for(var c = 0; c < hex.length; c += 2) { + bytes.push(parseInt(hex.substr(c, 2), 16)); + } return bytes; }; diff --git a/test/notp.js b/test/notp.js index 896f08b..4656a70 100644 --- a/test/notp.js +++ b/test/notp.js @@ -1,9 +1,5 @@ -/* - * Notp test suite - */ - -var notp = require('../lib/notp'); +var notp = require('..'); /* * Test HOTP. Uses test values from RFC 4226 @@ -48,40 +44,26 @@ var notp = require('../lib/notp'); * see http://tools.ietf.org/html/rfc4226 */ exports.testHOTP = function(beforeExit, assert) { - var n = 0, - args = { - W : 0, - K : '12345678901234567890' - }, - HOTP = ['755224', '287082','359152', '969429', '338314', '254676', '287922', '162583', '399871', '520489']; - + var args = { + W : 0, + K : '12345678901234567890' + }; + var HOTP = ['755224', '287082','359152', '969429', '338314', '254676', '287922', '162583', '399871', '520489']; // Check for failure args.C = 0; args.P = 'WILLNOTPASS'; - notp.checkHOTP(args, - function(ret, w) { - assert.eql(ret, false, 'Should not pass'); - n++; - } - ); + assert.ok(!notp.checkHOTP(args), 'Should not pass'); // Check for passes for(i=0;i= 9 args.W = 8; - notp.checkHOTP(args, - function(ret, w) { - assert.eql(ret, true, 'Should pass for value of W >= 9'); - n++; - } - ); - - beforeExit(function() { - assert.equal(2, n, 'All tests should have been run'); - }); + assert.ok(notp.checkHOTP(args), 'Should pass for value of W >= 9'); }; @@ -202,34 +146,19 @@ exports.testHOTPOutOfSync = function(beforeExit, assert) { */ exports.testTOTPOutOfSync = function(beforeExit, assert) { - var n = 0, - args = { - K : '12345678901234567890', - P : '279037', - _t : 1999999909*1000 - }; + var args = { + K : '12345678901234567890', + P : '279037', + _t : 1999999909*1000 + }; // Check that the test should fail for W < 2 args.W = 2; - notp.checkTOTP(args, - function(ret, w) { - assert.eql(ret, false, 'Should not pass for value of W < 3'); - n++; - } - ); + assert.ok(!notp.checkTOTP(args), 'Should not pass for value of W < 3'); // Check that the test should pass for W >= 3 args.W = 3; - notp.checkTOTP(args, - function(ret, w) { - assert.eql(ret, true, 'Should pass for value of W >= 3'); - n++; - } - ); - - beforeExit(function() { - assert.equal(2, n, 'All tests should have been run'); - }); + assert.ok(notp.checkTOTP(args), 'Should pass for value of W >= 3'); }; @@ -237,27 +166,18 @@ exports.testTOTPOutOfSync = function(beforeExit, assert) { * Test getHOTP function. Uses same test values as for checkHOTP */ exports.testGetHOTP = function(beforeExit, assert) { - var n = 0, - args = { - W : 0, - K : '12345678901234567890' - }, - HOTP = ['755224', '287082','359152', '969429', '338314', '254676', '287922', '162583', '399871', '520489']; + var args = { + W : 0, + K : '12345678901234567890' + }; + + var HOTP = ['755224', '287082','359152', '969429', '338314', '254676', '287922', '162583', '399871', '520489']; // Check for passes for(i=0;i