rework api

make two subobjects `hotp` and `totp` with gen and verify methods
This commit is contained in:
Roman Shtylman 2012-06-01 00:33:55 -04:00
parent e633d857fd
commit cfa2ecddb1
3 changed files with 168 additions and 197 deletions

View File

@ -14,15 +14,13 @@ IMPORTANT: The NOTP library accepts ASCII strings as keys, but the Google Authen
```javascript ```javascript
var notp = require('notp'); var notp = require('notp');
var args = {};
//.... some initial login code, that receives the TOTP / HTOP //.... some initial login code, that receives the TOTP / HTOP
// token from the user // token from the user
args.key = 'TOTP key for user... could be stored in DB'; var key = 'TOTP key for user... could be stored in DB';
args.token = 'User supplied TOTP value'; var token = 'User supplied TOTP value';
// Check TOTP is correct // Check TOTP is correct
var login = notp.checkTOTP(args); var login = notp.totp.verify(token, key);
// invalid token // invalid token
if (!login) { if (!login) {
@ -34,7 +32,7 @@ console.log('Token valid, sync value is %s', login.delta);
``` ```
# API # API
##notp.checkHOTP(args) ##hotp.verify(token, key, opt)
Check a One Time Password based on a counter. Check a One Time Password based on a counter.
@ -47,12 +45,8 @@ console.log('Token valid, sync value is %s', login.delta);
Arguments: 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 window - The allowable margin for the counter. The function will check
W codes in the future against the provided passcode. Note, W codes in the future against the provided passcode. Note,
it is the calling applications responsibility to keep track of 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** **Example**
```javascript ```javascript
var opt = { var key = 'USER SPECIFIC KEY', // Should be ASCII string
key : 'USER SPECIFIC KEY', // Should be ASCII string var token = 'USER SUPPLIED PASSCODE'
token : 'USER SUPPLIED PASSCODE'
};
var res = notp.checkHOTP(opt); var res = notp.hotp.verify(token, key, opt);
// not valid // not valid
if (!res) { if (!res) {
@ -86,7 +78,7 @@ if (!res) {
console.log('valid, counter is out of sync by %d steps', res.delta); 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. 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: Arguments:
args opt
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.
window - The allowable margin for the counter. The function will check window - The allowable margin for the counter. The function will check
W codes either side of the provided counter. Note, W codes either side of the provided counter. Note,
it is the calling applications responsibility to keep track of 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** **Example**
```javascript ```javascript
var opt = { var key = 'USER SPECIFIC KEY', // Should be ASCII string
key : 'USER SPECIFIC KEY', // Should be ASCII string var token = 'USER SUPPLIED PASSCODE'
token : 'USER SUPPLIED PASSCODE'
};
var res = notp.checkTOTP(opt); var res = notp.totp.verify(token, key, opt);
// not valid // not valid
if (!res) { if (!res) {
@ -141,7 +126,7 @@ if (!res) {
console.log('valid, counter is out of sync by %d steps', res.delta); 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 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: Arguments:
args opt
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 counter - Counter value. This should be stored by the application, must
be user specific, and be incremented for each request. be user specific, and be incremented for each request.
**Example** **Example**
```javascript ```javascript
var token = notp.getHOTP({ var token = notp.hotp.gen(key, {
key : 'USER SPECIFIC KEY', // Should be ASCII string counter : 5 // COUNTER VALUE
token : 5 // COUNTER VALUE
}); });
``` ```
##notp.getTOTP(args, err, cb) ##totp.gen(key, opt)
Generate a time based One Time Password Generate a time based One Time Password
@ -173,10 +154,7 @@ var token = notp.getHOTP({
Arguments: Arguments:
args opt
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 time - The time step of the counter. This must be the same for
every request and is used to calculate C. every request and is used to calculate C.
@ -185,9 +163,7 @@ var token = notp.getHOTP({
**Example** **Example**
```javascript ```javascript
var token = notp.getTOTP({ var token = notp.totp.gen(key);
key : 'USER SPECIFIC KEY' // Should be ASCII string
});
``` ```
## License ## License

181
index.js
View File

@ -1,6 +1,52 @@
var crypto = require('crypto'); 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. * 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. * 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 window = opt.window || 50;
var counter = opt.counter || 0; var counter = opt.counter || 0;
var token = opt.token || '';
// Now loop through from C to C + W to determine if there is // Now loop through from C to C + W to determine if there is
// a correct code // a correct code
for(var i = counter; i <= counter + window; ++i) { for(var i = counter; i <= counter + window; ++i) {
opt.counter = i; opt.counter = i;
if(this.getHOTP(opt) === token) { if(this.gen(key, opt) === token) {
// We have found a matching code, trigger callback // We have found a matching code, trigger callback
// and pass offset // and pass offset
return { delta: i - counter }; return { delta: i - counter };
@ -51,6 +95,45 @@ module.exports.checkHOTP = function(opt) {
return false; 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. * Check a One Time Password based on a timer.
@ -83,8 +166,7 @@ module.exports.checkHOTP = function(opt) {
* Default - 30 * Default - 30
* *
*/ */
module.exports.checkTOTP = function(opt) { totp.verify = function(token, key, opt) {
var time = opt.time || 30; var time = opt.time || 30;
var _t = new Date().getTime(); 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 // This is the number of time steps in seconds since T0
opt.counter = Math.floor((_t / 1000) / time); opt.counter = Math.floor((_t / 1000) / time);
return module.exports.checkHOTP(opt); return hotp.verify(token, key, opt);
}; };
module.exports.hotp = hotp;
/** module.exports.totp = totp;
* 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);
};
/** /**
* convert an integer to a byte array * convert an integer to a byte array

View File

@ -44,22 +44,20 @@ var notp = require('..');
* see http://tools.ietf.org/html/rfc4226 * see http://tools.ietf.org/html/rfc4226
*/ */
exports.testHOTP = function(beforeExit, assert) { exports.testHOTP = function(beforeExit, assert) {
var args = { var key = '12345678901234567890';
var opt = {
window : 0, window : 0,
key : '12345678901234567890'
}; };
var HOTP = ['755224', '287082','359152', '969429', '338314', '254676', '287922', '162583', '399871', '520489']; var HOTP = ['755224', '287082','359152', '969429', '338314', '254676', '287922', '162583', '399871', '520489'];
// counterheck for failure // counterheck for failure
args.counter = 0; opt.counter = 0;
args.token = 'windowILLNOTtokenASS'; assert.ok(!notp.hotp.verify('WILL NOT PASS', key, opt), 'Should not pass');
assert.ok(!notp.checkHOTP(args), 'Should not pass');
// counterheck for passes // counterheck for passes
for(i=0;i<HOTP.length;i++) { for(i=0;i<HOTP.length;i++) {
args.counter = i; opt.counter = i;
args.token = HOTP[i]; var res = notp.hotp.verify(HOTP[i], key, opt);
var res = notp.checkHOTP(args);
assert.ok(res, 'Should pass'); assert.ok(res, 'Should pass');
assert.eql(res.delta, 0, 'Should be in sync'); assert.eql(res.delta, 0, 'Should be in sync');
@ -73,41 +71,41 @@ exports.testHOTP = function(beforeExit, assert) {
* see http://tools.ietf.org/id/draft-mraihi-totp-timebased-06.txt * see http://tools.ietf.org/id/draft-mraihi-totp-timebased-06.txt
*/ */
exports.testTOTtoken = function(beforeExit, assert) { exports.testTOTtoken = function(beforeExit, assert) {
var args = { var key = '12345678901234567890';
var opt = {
window : 0, window : 0,
key : '12345678901234567890'
}; };
// counterheck for failure // counterheck for failure
args.T = 0; opt.time = 0;
args.token = 'windowILLNOTtokenASS'; var token = 'windowILLNOTtokenASS';
assert.ok(!notp.checkTOTP(args), 'Should not pass'); assert.ok(!notp.totp.verify(token, key, opt), 'Should not pass');
// counterheck for test vector at 59s // counterheck for test vector at 59s
args._t = 59*1000; opt._t = 59*1000;
args.token = '287082'; var token = '287082';
var res = notp.checkTOTP(args); var res = notp.totp.verify(token, key, opt);
assert.ok(res, 'Should pass'); assert.ok(res, 'Should pass');
assert.eql(res.delta, 0, 'Should be in sync'); assert.eql(res.delta, 0, 'Should be in sync');
// counterheck for test vector at 1234567890 // counterheck for test vector at 1234567890
args._t = 1234567890*1000; opt._t = 1234567890*1000;
args.token = '005924'; var token = '005924';
var res = notp.checkTOTP(args); var res = notp.totp.verify(token, key, opt);
assert.ok(res, 'Should pass'); assert.ok(res, 'Should pass');
assert.eql(res.delta, 0, 'Should be in sync'); assert.eql(res.delta, 0, 'Should be in sync');
// counterheck for test vector at 1111111109 // counterheck for test vector at 1111111109
args._t = 1111111109*1000; opt._t = 1111111109*1000;
args.token = '081804'; var token = '081804';
var res = notp.checkTOTP(args); var res = notp.totp.verify(token, key, opt);
assert.ok(res, 'Should pass'); assert.ok(res, 'Should pass');
assert.eql(res.delta, 0, 'Should be in sync'); assert.eql(res.delta, 0, 'Should be in sync');
// counterheck for test vector at 2000000000 // counterheck for test vector at 2000000000
args._t = 2000000000*1000; opt._t = 2000000000*1000;
args.token = '279037'; var token = '279037';
var res = notp.checkTOTP(args); var res = notp.totp.verify(token, key, opt);
assert.ok(res, 'Should pass'); assert.ok(res, 'Should pass');
assert.eql(res.delta, 0, 'Should be in sync'); assert.eql(res.delta, 0, 'Should be in sync');
}; };
@ -118,21 +116,22 @@ exports.testTOTtoken = function(beforeExit, assert) {
* windowe are going to use a value of counter = 1 and test against * windowe are going to use a value of counter = 1 and test against
* a code for counter = 9 * a code for counter = 9
*/ */
exports.testHOTPtokenOutOfSync = function(beforeExit, assert) { exports.testHOTPOutOfSync = function(beforeExit, assert) {
var args = { var key = '12345678901234567890';
key : '12345678901234567890', var token = '520489';
token : '520489',
var opt = {
counter : 1 counter : 1
}; };
// counterheck that the test should fail for window < 8 // counterheck that the test should fail for window < 8
args.window = 7; opt.window = 7;
assert.ok(!notp.checkHOTP(args), 'Should not pass for value of window < 8'); assert.ok(!notp.hotp.verify(token, key, opt), 'Should not pass for value of window < 8');
// counterheck that the test should pass for window >= 9 // counterheck that the test should pass for window >= 9
args.window = 8; opt.window = 8;
assert.ok(notp.checkHOTP(args), 'Should pass for value of window >= 9'); 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 * counterheck for codes that are out of sync
* windowe are going to use a value of T = 1999999909 (91s behind 2000000000) * 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 = { var key = '12345678901234567890';
key : '12345678901234567890', var token = '279037';
token : '279037',
var opt = {
_t : 1999999909*1000 _t : 1999999909*1000
}; };
// counterheck that the test should fail for window < 2 // counterheck that the test should fail for window < 2
args.window = 2; opt.window = 2;
assert.ok(!notp.checkTOTP(args), 'Should not pass for value of window < 3'); 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 // counterheck that the test should pass for window >= 3
args.window = 3; opt.window = 3;
assert.ok(notp.checkTOTP(args), 'Should pass for value of window >= 3'); assert.ok(notp.totp.verify(token, key, opt), 'Should pass for value of window >= 3');
}; };
/* exports.hotp_gen = function(beforeExit, assert) {
* Test getHOTtoken function. Uses same test values as for checkHOTtoken var key = '12345678901234567890';
*/ var opt = {
exports.testGetHOTtoken = function(beforeExit, assert) {
var args = {
window : 0, window : 0,
key : '12345678901234567890'
}; };
var HOTP = ['755224', '287082','359152', '969429', '338314', '254676', '287922', '162583', '399871', '520489']; var HOTP = ['755224', '287082','359152', '969429', '338314', '254676', '287922', '162583', '399871', '520489'];
// counterheck for passes // counterheck for passes
for(i=0;i<HOTP.length;i++) { for(i=0;i<HOTP.length;i++) {
args.counter = i; opt.counter = i;
assert.eql(notp.getHOTP(args), HOTP[i], 'HTOtoken value should be correct'); assert.eql(notp.hotp.gen(key, opt), HOTP[i], 'HOTP value should be correct');
} }
}; };
/* exports.totp_gen = function(beforeExit, assert) {
* Test getTOTtoken function. Uses same test values as for checkTOTtoken var key = '12345678901234567890';
*/ var opt = {
exports.testGetTOTtoken = function(beforeExit, assert) {
var args = {
window : 0, window : 0,
key : '12345678901234567890'
}; };
// counterheck for test vector at 59s // counterheck for test vector at 59s
args._t = 59*1000; opt._t = 59*1000;
assert.eql(notp.getTOTP(args), '287082', 'TOTtoken values should match'); assert.eql(notp.totp.gen(key, opt), '287082', 'TOTtoken values should match');
// counterheck for test vector at 1234567890 // counterheck for test vector at 1234567890
args._t = 1234567890*1000; opt._t = 1234567890*1000;
assert.eql(notp.getTOTP(args), '005924', 'TOTtoken values should match'); assert.eql(notp.totp.gen(key, opt), '005924', 'TOTtoken values should match');
// counterheck for test vector at 1111111109 // counterheck for test vector at 1111111109
args._t = 1111111109*1000; opt._t = 1111111109*1000;
assert.eql(notp.getTOTP(args), '081804', 'TOTtoken values should match'); assert.eql(notp.totp.gen(key, opt), '081804', 'TOTtoken values should match');
// counterheck for test vector at 2000000000 // counterheck for test vector at 2000000000
args._t = 2000000000*1000; opt._t = 2000000000*1000;
assert.eql(notp.getTOTP(args), '279037', 'TOTtoken values should match'); assert.eql(notp.totp.gen(key, opt), '279037', 'TOTtoken values should match');
}; };