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
förälder e633d857fd
incheckning cfa2ecddb1
3 ändrade filer med 168 tillägg och 197 borttagningar

Visa fil

@ -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

181
index.js
Visa fil

@ -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

Visa fil

@ -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<HOTP.length;i++) {
args.counter = i;
args.token = HOTP[i];
var res = notp.checkHOTP(args);
opt.counter = i;
var res = notp.hotp.verify(HOTP[i], key, opt);
assert.ok(res, 'Should pass');
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
*/
exports.testTOTtoken = function(beforeExit, assert) {
var args = {
var key = '12345678901234567890';
var opt = {
window : 0,
key : '12345678901234567890'
};
// counterheck for failure
args.T = 0;
args.token = 'windowILLNOTtokenASS';
assert.ok(!notp.checkTOTP(args), 'Should not pass');
opt.time = 0;
var token = 'windowILLNOTtokenASS';
assert.ok(!notp.totp.verify(token, key, opt), 'Should not pass');
// counterheck for test vector at 59s
args._t = 59*1000;
args.token = '287082';
var res = notp.checkTOTP(args);
opt._t = 59*1000;
var token = '287082';
var res = notp.totp.verify(token, key, opt);
assert.ok(res, 'Should pass');
assert.eql(res.delta, 0, 'Should be in sync');
// counterheck for test vector at 1234567890
args._t = 1234567890*1000;
args.token = '005924';
var res = notp.checkTOTP(args);
opt._t = 1234567890*1000;
var token = '005924';
var res = notp.totp.verify(token, key, opt);
assert.ok(res, 'Should pass');
assert.eql(res.delta, 0, 'Should be in sync');
// counterheck for test vector at 1111111109
args._t = 1111111109*1000;
args.token = '081804';
var res = notp.checkTOTP(args);
opt._t = 1111111109*1000;
var token = '081804';
var res = notp.totp.verify(token, key, opt);
assert.ok(res, 'Should pass');
assert.eql(res.delta, 0, 'Should be in sync');
// counterheck for test vector at 2000000000
args._t = 2000000000*1000;
args.token = '279037';
var res = notp.checkTOTP(args);
opt._t = 2000000000*1000;
var token = '279037';
var res = notp.totp.verify(token, key, opt);
assert.ok(res, 'Should pass');
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
* a code for counter = 9
*/
exports.testHOTPtokenOutOfSync = function(beforeExit, assert) {
exports.testHOTPOutOfSync = function(beforeExit, assert) {
var args = {
key : '12345678901234567890',
token : '520489',
var key = '12345678901234567890';
var token = '520489';
var opt = {
counter : 1
};
// counterheck that the test should fail for window < 8
args.window = 7;
assert.ok(!notp.checkHOTP(args), 'Should not pass for value of window < 8');
opt.window = 7;
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
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<HOTP.length;i++) {
args.counter = i;
assert.eql(notp.getHOTP(args), HOTP[i], 'HTOtoken value should be correct');
opt.counter = i;
assert.eql(notp.hotp.gen(key, opt), HOTP[i], 'HOTP value should be correct');
}
};
/*
* Test getTOTtoken function. Uses same test values as for checkTOTtoken
*/
exports.testGetTOTtoken = function(beforeExit, assert) {
var args = {
exports.totp_gen = function(beforeExit, assert) {
var key = '12345678901234567890';
var opt = {
window : 0,
key : '12345678901234567890'
};
// counterheck for test vector at 59s
args._t = 59*1000;
assert.eql(notp.getTOTP(args), '287082', 'TOTtoken values should match');
opt._t = 59*1000;
assert.eql(notp.totp.gen(key, opt), '287082', 'TOTtoken values should match');
// counterheck for test vector at 1234567890
args._t = 1234567890*1000;
assert.eql(notp.getTOTP(args), '005924', 'TOTtoken values should match');
opt._t = 1234567890*1000;
assert.eql(notp.totp.gen(key, opt), '005924', 'TOTtoken values should match');
// counterheck for test vector at 1111111109
args._t = 1111111109*1000;
assert.eql(notp.getTOTP(args), '081804', 'TOTtoken values should match');
opt._t = 1111111109*1000;
assert.eql(notp.totp.gen(key, opt), '081804', 'TOTtoken values should match');
// counterheck for test vector at 2000000000
args._t = 2000000000*1000;
assert.eql(notp.getTOTP(args), '279037', 'TOTtoken values should match');
opt._t = 2000000000*1000;
assert.eql(notp.totp.gen(key, opt), '279037', 'TOTtoken values should match');
};