diff --git a/Readme.md b/Readme.md index cd7a824..55ecf50 100644 --- a/Readme.md +++ b/Readme.md @@ -14,15 +14,13 @@ IMPORTANT: The NOTP library accepts ASCII strings as keys, but the Google Authen ```javascript var notp = require('notp'); -var args = {}; - //.... some initial login code, that receives the TOTP / HTOP // token from the user -args.key = 'TOTP key for user... could be stored in DB'; -args.token = 'User supplied TOTP value'; +var key = 'TOTP key for user... could be stored in DB'; +var token = 'User supplied TOTP value'; // Check TOTP is correct -var login = notp.checkTOTP(args); +var login = notp.totp.verify(token, key); // invalid token if (!login) { @@ -34,7 +32,7 @@ console.log('Token valid, sync value is %s', login.delta); ``` # API -##notp.checkHOTP(args) +##hotp.verify(token, key, opt) Check a One Time Password based on a counter. @@ -47,12 +45,8 @@ console.log('Token valid, sync value is %s', login.delta); Arguments: - args - key - Key for the one time password. This should be unique and secret for - every user as it is the seed used to calculate the HMAC - - token - Passcode to validate. + opt window - The allowable margin for the counter. The function will check W codes in the future against the provided passcode. Note, it is the calling applications responsibility to keep track of @@ -71,12 +65,10 @@ console.log('Token valid, sync value is %s', login.delta); **Example** ```javascript -var opt = { - key : 'USER SPECIFIC KEY', // Should be ASCII string - token : 'USER SUPPLIED PASSCODE' -}; +var key = 'USER SPECIFIC KEY', // Should be ASCII string +var token = 'USER SUPPLIED PASSCODE' -var res = notp.checkHOTP(opt); +var res = notp.hotp.verify(token, key, opt); // not valid if (!res) { @@ -86,7 +78,7 @@ if (!res) { console.log('valid, counter is out of sync by %d steps', res.delta); ``` -##notp.checkTOTP(args, err, cb) +##totp.verify(token, key, opt) Check a One Time Password based on a timer. @@ -100,12 +92,7 @@ console.log('valid, counter is out of sync by %d steps', res.delta); Arguments: - args - key - Key for the one time password. This should be unique and secret for - every user as it is the seed used to calculate the HMAC - - token - Passcode to validate. - + opt window - The allowable margin for the counter. The function will check W codes either side of the provided counter. Note, it is the calling applications responsibility to keep track of @@ -126,12 +113,10 @@ console.log('valid, counter is out of sync by %d steps', res.delta); **Example** ```javascript -var opt = { - key : 'USER SPECIFIC KEY', // Should be ASCII string - token : 'USER SUPPLIED PASSCODE' -}; +var key = 'USER SPECIFIC KEY', // Should be ASCII string +var token = 'USER SUPPLIED PASSCODE' -var res = notp.checkTOTP(opt); +var res = notp.totp.verify(token, key, opt); // not valid if (!res) { @@ -141,7 +126,7 @@ if (!res) { console.log('valid, counter is out of sync by %d steps', res.delta); ``` -##notp.getHOTP(args, err, cb) +##hotp.gen(key, opt) Generate a counter based One Time Password @@ -149,23 +134,19 @@ console.log('valid, counter is out of sync by %d steps', res.delta); Arguments: - args - key - Key for the one time password. This should be unique and secret for - every user as it is the seed used to calculate the HMAC - + opt counter - Counter value. This should be stored by the application, must be user specific, and be incremented for each request. **Example** ```javascript -var token = notp.getHOTP({ - key : 'USER SPECIFIC KEY', // Should be ASCII string - token : 5 // COUNTER VALUE +var token = notp.hotp.gen(key, { + counter : 5 // COUNTER VALUE }); ``` -##notp.getTOTP(args, err, cb) +##totp.gen(key, opt) Generate a time based One Time Password @@ -173,10 +154,7 @@ var token = notp.getHOTP({ Arguments: - args - key - Key for the one time password. This should be unique and secret for - every user as it is the seed used to calculate the HMAC - + opt time - The time step of the counter. This must be the same for every request and is used to calculate C. @@ -185,9 +163,7 @@ var token = notp.getHOTP({ **Example** ```javascript -var token = notp.getTOTP({ - key : 'USER SPECIFIC KEY' // Should be ASCII string -}); +var token = notp.totp.gen(key); ``` ## License diff --git a/index.js b/index.js index 73ff375..a3ad53d 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,52 @@ var crypto = require('crypto'); +var hotp = {}; + +/** + * Generate a counter based One Time Password + * + * @return {String} the one time password + * + * Arguments: + * + * args + * key - Key for the one time password. This should be unique and secret for + * every user as it is the seed used to calculate the HMAC + * + * counter - Counter value. This should be stored by the application, must + * be user specific, and be incremented for each request. + * + */ +hotp.gen = function(key, opt) { + var key = key || ''; + var counter = opt.counter || 0; + + var p = 6; + + // Create the byte array + var b = new Buffer(intToBytes(counter)); + + var hmac = crypto.createHmac('SHA1', new Buffer(key)); + + // 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); +}; + /** * Check a One Time Password based on a counter. * @@ -30,17 +76,15 @@ var crypto = require('crypto'); * be user specific, and be incremented for each request. * */ -module.exports.checkHOTP = function(opt) { - +hotp.verify = function(token, key, opt) { var window = opt.window || 50; var counter = opt.counter || 0; - var token = opt.token || ''; // Now loop through from C to C + W to determine if there is // a correct code for(var i = counter; i <= counter + window; ++i) { opt.counter = i; - if(this.getHOTP(opt) === token) { + if(this.gen(key, opt) === token) { // We have found a matching code, trigger callback // and pass offset return { delta: i - counter }; @@ -51,6 +95,45 @@ module.exports.checkHOTP = function(opt) { return false; }; +var totp = {}; + +/** + * Generate a time based One Time Password + * + * @return {String} the one time password + * + * Arguments: + * + * args + * key - Key for the one time password. This should be unique and secret for + * every user as it is the seed used to calculate the HMAC + * + * time - The time step of the counter. This must be the same for + * every request and is used to calculat C. + * + * Default - 30 + * + */ +totp.gen = function(key, opt) { + var time = opt.time || 30; + var _t = new Date().getTime();; + + // Time has been overwritten. + if(opt._t) { + 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 = opt._t; + } + + // Determine the value of the counter, C + // This is the number of time steps in seconds since T0 + opt.counter = Math.floor((_t / 1000) / time); + + return hotp.gen(key, opt); +}; /** * Check a One Time Password based on a timer. @@ -83,8 +166,7 @@ module.exports.checkHOTP = function(opt) { * Default - 30 * */ -module.exports.checkTOTP = function(opt) { - +totp.verify = function(token, key, opt) { var time = opt.time || 30; var _t = new Date().getTime(); @@ -102,92 +184,11 @@ module.exports.checkTOTP = function(opt) { // This is the number of time steps in seconds since T0 opt.counter = Math.floor((_t / 1000) / time); - return module.exports.checkHOTP(opt); + return hotp.verify(token, key, opt); }; - -/** - * Generate a counter based One Time Password - * - * @return {String} the one time password - * - * Arguments: - * - * args - * key - Key for the one time password. This should be unique and secret for - * every user as it is the seed used to calculate the HMAC - * - * counter - Counter value. This should be stored by the application, must - * be user specific, and be incremented for each request. - * - */ -module.exports.getHOTP = function(opt) { - var key = opt.key || ''; - var counter = opt.counter || 0; - - var p = 6; - - // Create the byte array - var b = new Buffer(intToBytes(counter)); - - var hmac = crypto.createHmac('SHA1', new Buffer(key)); - - // 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); -}; - - -/** - * Generate a time based One Time Password - * - * @return {String} the one time password - * - * Arguments: - * - * args - * key - Key for the one time password. This should be unique and secret for - * every user as it is the seed used to calculate the HMAC - * - * time - The time step of the counter. This must be the same for - * every request and is used to calculat C. - * - * Default - 30 - * - */ -module.exports.getTOTP = function(opt) { - var time = opt.time || 30; - var _t = new Date().getTime();; - - // Time has been overwritten. - if(opt._t) { - 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 = opt._t; - } - - // Determine the value of the counter, C - // This is the number of time steps in seconds since T0 - opt.counter = Math.floor((_t / 1000) / time); - - return this.getHOTP(opt); -}; +module.exports.hotp = hotp; +module.exports.totp = totp; /** * convert an integer to a byte array diff --git a/test/notp.js b/test/notp.js index 7931c5a..8843ab2 100644 --- a/test/notp.js +++ b/test/notp.js @@ -44,22 +44,20 @@ var notp = require('..'); * see http://tools.ietf.org/html/rfc4226 */ exports.testHOTP = function(beforeExit, assert) { - var args = { + var key = '12345678901234567890'; + var opt = { window : 0, - key : '12345678901234567890' }; var HOTP = ['755224', '287082','359152', '969429', '338314', '254676', '287922', '162583', '399871', '520489']; // counterheck for failure - args.counter = 0; - args.token = 'windowILLNOTtokenASS'; - assert.ok(!notp.checkHOTP(args), 'Should not pass'); + opt.counter = 0; + assert.ok(!notp.hotp.verify('WILL NOT PASS', key, opt), 'Should not pass'); // counterheck for passes for(i=0;i= 9 - args.window = 8; - assert.ok(notp.checkHOTP(args), 'Should pass for value of window >= 9'); + opt.window = 8; + assert.ok(notp.hotp.verify(token, key, opt), 'Should pass for value of window >= 9'); }; @@ -140,66 +139,61 @@ exports.testHOTPtokenOutOfSync = function(beforeExit, assert) { * counterheck for codes that are out of sync * windowe are going to use a value of T = 1999999909 (91s behind 2000000000) */ -exports.testTOTtokenOutOfSync = function(beforeExit, assert) { +exports.testTOTPOutOfSync = function(beforeExit, assert) { - var args = { - key : '12345678901234567890', - token : '279037', + var key = '12345678901234567890'; + var token = '279037'; + + var opt = { _t : 1999999909*1000 }; // counterheck that the test should fail for window < 2 - args.window = 2; - assert.ok(!notp.checkTOTP(args), 'Should not pass for value of window < 3'); + opt.window = 2; + assert.ok(!notp.totp.verify(token, key, opt), 'Should not pass for value of window < 3'); // counterheck that the test should pass for window >= 3 - args.window = 3; - assert.ok(notp.checkTOTP(args), 'Should pass for value of window >= 3'); + opt.window = 3; + assert.ok(notp.totp.verify(token, key, opt), 'Should pass for value of window >= 3'); }; -/* - * Test getHOTtoken function. Uses same test values as for checkHOTtoken - */ -exports.testGetHOTtoken = function(beforeExit, assert) { - var args = { +exports.hotp_gen = function(beforeExit, assert) { + var key = '12345678901234567890'; + var opt = { window : 0, - key : '12345678901234567890' }; var HOTP = ['755224', '287082','359152', '969429', '338314', '254676', '287922', '162583', '399871', '520489']; // counterheck for passes for(i=0;i