code cleanup

- remove callbacks (functions are sync)
- update tests for removed callbacks
- minimize some code duplication
This commit is contained in:
Roman Shtylman 2012-05-31 23:40:34 -04:00
parent 4475c95ecb
commit cf96266a20
2 changed files with 126 additions and 374 deletions

View File

@ -1,16 +1,11 @@
var crypto = require('crypto'); var crypto = require('crypto');
var base32 = require('thirty-two');
/* /**
* Check a One Time Password based on a counter. * Check a One Time Password based on a counter.
* *
* First argument of callback is true if password check is successful, * @return {Object} null if failure, { delta: # } on success
* or false if check fails. * delta is the time step difference between the client and the server
*
* Second argument is the time step difference between the client and
* the server. This argument is only passed if the password check is
* successful.
* *
* Arguments: * Arguments:
* *
@ -35,48 +30,33 @@ var base32 = require('thirty-two');
* be user specific, and be incremented for each request. * be user specific, and be incremented for each request.
* *
*/ */
module.exports.checkHOTP = function(args, cb) { module.exports.checkHOTP = function(args) {
var hmac, var W = args.W || 50;
digest, var C = args.C || 0;
offset, h, v, p = 6, b, var P = args.P || '';
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))
// 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(i = C; i <= C+W; i++) { for(i = C; i <= C+W; ++i) {
if(this._calcHMAC(K,i) === P) { args.C = i;
if(this.getHOTP(args) === P) {
// We have found a matching code, trigger callback // We have found a matching code, trigger callback
// and pass offset // and pass offset
cb(true, i - C); return { delta: i - C };
return;
} }
} }
// If we get to here then no codes have matched, return false // If we get to here then no codes have matched, return false
cb(false); return false;
return;
}; };
/* /**
* Check a One Time Password based on a timer. * Check a One Time Password based on a timer.
* *
* First argument of callback is true if password check is successful, * @return {Object} null if failure, { delta: # } on success
* or false if check fails. * delta is the time step difference between the client and the server
*
* Second argument is the time step difference between the client and
* the server. This argument is only passed if the password check is
* successful.
* *
* Arguments: * Arguments:
* *
@ -103,60 +83,33 @@ module.exports.checkHOTP = function(args, cb) {
* Default - 30 * Default - 30
* *
*/ */
module.exports.checkTOTP = function(args, cb) { module.exports.checkTOTP = function(args) {
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;
var T = args.T || 30;
var _t = new Date().getTime();
// Time has been overwritten.
if(args._t) { if(args._t) {
// Time has been overwritten.
console.log('#####################################'); console.log('#####################################');
console.log('# NOTE: TOTP TIME VARIABLE HAS BEEN #'); console.log('# NOTE: TOTP TIME VARIABLE HAS BEEN #');
console.log('# OVERWRITTEN. THIS SHOULD ONLY BE #'); console.log('# OVERWRITTEN. THIS SHOULD ONLY BE #');
console.log('# USED FOR TEST PURPOSES. #'); console.log('# USED FOR TEST PURPOSES. #');
console.log('#####################################'); console.log('#####################################');
_t = args._t; _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 // Determine the value of the counter, C
// This is the number of time steps in seconds since T0 // 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 return module.exports.checkHOTP(args);
// 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;
}; };
/* /**
* 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: * Arguments:
* *
@ -168,28 +121,40 @@ module.exports.checkTOTP = function(args, cb) {
* be user specific, and be incremented for each request. * 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, var p = 6;
digest,
offset, h, v, p = 6, b,
i,
K = args.K || "",
C = args.C || 0,
// Create the byte array
var b = new Buffer(intToBytes(counter));
// Initiate the HMAC var hmac = crypto.createHmac('SHA1', new Buffer(key));
hmac = crypto.createHmac('SHA1', new Buffer(K))
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: * Arguments:
* *
@ -203,127 +168,37 @@ module.exports.getHOTP = function(args, cb) {
* Default - 30 * Default - 30
* *
*/ */
module.exports.getTOTP = function(args, cb) { module.exports.getTOTP = function(args) {
var hmac, var K = args.K || '';
digest, var T = args.T || 30;
offset, h, v, p = 6, b, var _t = new Date().getTime();;
C,i,
K = args.K || "",
T = args.T || 30,
_t;
// Time has been overwritten.
if(args._t) { if(args._t) {
// Time has been overwritten.
console.log('#####################################'); console.log('#####################################');
console.log('# NOTE: TOTP TIME VARIABLE HAS BEEN #'); console.log('# NOTE: TOTP TIME VARIABLE HAS BEEN #');
console.log('# OVERWRITTEN. THIS SHOULD ONLY BE #'); console.log('# OVERWRITTEN. THIS SHOULD ONLY BE #');
console.log('# USED FOR TEST PURPOSES. #'); console.log('# USED FOR TEST PURPOSES. #');
console.log('#####################################'); console.log('#####################################');
_t = args._t; _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 // Determine the value of the counter, C
// This is the number of time steps in seconds since T0 // 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);
}; };
/**
/* * convert an integer to a byte array
* Helper function to convert a string to a base32 encoded string * @param {Integer} num
* * @return {Array} bytes
* Arguments:
*
* str - String to encode
*
* Returns: Base 32 encoded string
*/ */
module.exports.encBase32 = function(str) { var intToBytes = function(num) {
return base32.encode(str); var bytes = [];
};
for(var i=7 ; i>=0 ; --i) {
/*
* 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--) {
bytes[i] = num & (255); bytes[i] = num & (255);
num = num >> 8; num = num >> 8;
} }
@ -332,18 +207,16 @@ module.exports._intToBytes = function(num) {
}; };
/* /**
* Private function to convert a hex value to a byte array * convert a hex value to a byte array
* * @param {String} hex string of hex to convert to a byte array
* Arguments * @return {Array} bytes
*
* hex - Hex value
*
* Returns - byte array
*/ */
module.exports._hexToBytes = function(hex) { var hexToBytes = function(hex) {
for(var bytes = [], c = 0; c < hex.length; c += 2) var bytes = [];
bytes.push(parseInt(hex.substr(c, 2), 16)); for(var c = 0; c < hex.length; c += 2) {
bytes.push(parseInt(hex.substr(c, 2), 16));
}
return bytes; return bytes;
}; };

View File

@ -1,9 +1,5 @@
/* var notp = require('..');
* Notp test suite
*/
var notp = require('../lib/notp');
/* /*
* Test HOTP. Uses test values from RFC 4226 * Test HOTP. Uses test values from RFC 4226
@ -48,40 +44,26 @@ var notp = require('../lib/notp');
* 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 n = 0, var args = {
args = { W : 0,
W : 0, K : '12345678901234567890'
K : '12345678901234567890' };
}, var HOTP = ['755224', '287082','359152', '969429', '338314', '254676', '287922', '162583', '399871', '520489'];
HOTP = ['755224', '287082','359152', '969429', '338314', '254676', '287922', '162583', '399871', '520489'];
// Check for failure // Check for failure
args.C = 0; args.C = 0;
args.P = 'WILLNOTPASS'; args.P = 'WILLNOTPASS';
notp.checkHOTP(args, assert.ok(!notp.checkHOTP(args), 'Should not pass');
function(ret, w) {
assert.eql(ret, false, 'Should not pass');
n++;
}
);
// Check for passes // Check for passes
for(i=0;i<HOTP.length;i++) { for(i=0;i<HOTP.length;i++) {
args.C = i; args.C = i;
args.P = HOTP[i]; args.P = HOTP[i];
notp.checkHOTP(args, var res = notp.checkHOTP(args);
function(ret, w) {
assert.eql(ret, true, 'Should pass');
assert.eql(w, 0, 'Should be in sync');
n++;
}
);
}
beforeExit(function() { assert.ok(res, 'Should pass');
assert.equal(HOTP.length + 1, n, 'All tests should have been run'); assert.eql(res.delta, 0, 'Should be in sync');
}); }
}; };
@ -91,55 +73,36 @@ 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.testTOTP = function(beforeExit, assert) { exports.testTOTP = function(beforeExit, assert) {
var n = 0, var args = {
args = { W : 0,
W : 0, K : '12345678901234567890'
K : '12345678901234567890' };
};
// Check for failure // Check for failure
args.T = 0; args.T = 0;
args.P = 'WILLNOTPASS'; args.P = 'WILLNOTPASS';
notp.checkTOTP(args, assert.ok(!notp.checkTOTP(args), 'Should not pass');
function(ret, w) {
assert.eql(ret, false, 'Should not pass');
n++;
}
);
// Check for test vector at 59s // Check for test vector at 59s
args._t = 59*1000; args._t = 59*1000;
args.P = '287082'; args.P = '287082';
notp.checkTOTP(args, var res = notp.checkTOTP(args);
function(ret, w) { assert.ok(res, 'Should pass');
assert.eql(ret, true, 'Should pass'); assert.eql(res.delta, 0, 'Should be in sync');
assert.eql(w, 0, 'Should be in sync');
n++;
}
);
// Check for test vector at 1234567890 // Check for test vector at 1234567890
args._t = 1234567890*1000; args._t = 1234567890*1000;
args.P = '005924'; args.P = '005924';
notp.checkTOTP(args, var res = notp.checkTOTP(args);
function(ret, w) { assert.ok(res, 'Should pass');
assert.eql(ret, true, 'Should pass'); assert.eql(res.delta, 0, 'Should be in sync');
assert.eql(w, 0, 'Should be in sync');
n++;
}
);
// Check for test vector at 1111111109 // Check for test vector at 1111111109
args._t = 1111111109*1000; args._t = 1111111109*1000;
args.P = '081804'; args.P = '081804';
notp.checkTOTP(args, var res = notp.checkTOTP(args);
function(ret, w) { assert.ok(res, 'Should pass');
assert.eql(ret, true, 'Should pass'); assert.eql(res.delta, 0, 'Should be in sync');
assert.eql(w, 0, 'Should be in sync');
n++;
}
);
// Check for test vector at 2000000000 // Check for test vector at 2000000000
args._t = 2000000000*1000; args._t = 2000000000*1000;
@ -151,10 +114,6 @@ exports.testTOTP = function(beforeExit, assert) {
n++; n++;
} }
); );
beforeExit(function() {
assert.equal(5, n, 'All tests should have been run');
});
}; };
@ -165,34 +124,19 @@ exports.testTOTP = function(beforeExit, assert) {
*/ */
exports.testHOTPOutOfSync = function(beforeExit, assert) { exports.testHOTPOutOfSync = function(beforeExit, assert) {
var n = 0, var args = {
args = { K : '12345678901234567890',
K : '12345678901234567890', P : '520489',
P : '520489', C : 1
C : 1 };
};
// Check that the test should fail for W < 8 // Check that the test should fail for W < 8
args.W = 7; args.W = 7;
notp.checkHOTP(args, assert.ok(!notp.checkHOTP(args), 'Should not pass for value of W < 8');
function(ret, w) {
assert.eql(ret, false, 'Should not pass for value of W < 8');
n++;
}
);
// Check that the test should pass for W >= 9 // Check that the test should pass for W >= 9
args.W = 8; args.W = 8;
notp.checkHOTP(args, assert.ok(notp.checkHOTP(args), 'Should pass for value of W >= 9');
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');
});
}; };
@ -202,34 +146,19 @@ exports.testHOTPOutOfSync = function(beforeExit, assert) {
*/ */
exports.testTOTPOutOfSync = function(beforeExit, assert) { exports.testTOTPOutOfSync = function(beforeExit, assert) {
var n = 0, var args = {
args = { K : '12345678901234567890',
K : '12345678901234567890', P : '279037',
P : '279037', _t : 1999999909*1000
_t : 1999999909*1000 };
};
// Check that the test should fail for W < 2 // Check that the test should fail for W < 2
args.W = 2; args.W = 2;
notp.checkTOTP(args, assert.ok(!notp.checkTOTP(args), 'Should not pass for value of W < 3');
function(ret, w) {
assert.eql(ret, false, 'Should not pass for value of W < 3');
n++;
}
);
// Check that the test should pass for W >= 3 // Check that the test should pass for W >= 3
args.W = 3; args.W = 3;
notp.checkTOTP(args, assert.ok(notp.checkTOTP(args), 'Should pass for value of W >= 3');
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');
});
}; };
@ -237,27 +166,18 @@ exports.testTOTPOutOfSync = function(beforeExit, assert) {
* Test getHOTP function. Uses same test values as for checkHOTP * Test getHOTP function. Uses same test values as for checkHOTP
*/ */
exports.testGetHOTP = function(beforeExit, assert) { exports.testGetHOTP = function(beforeExit, assert) {
var n = 0, var args = {
args = { W : 0,
W : 0, K : '12345678901234567890'
K : '12345678901234567890' };
},
HOTP = ['755224', '287082','359152', '969429', '338314', '254676', '287922', '162583', '399871', '520489']; var HOTP = ['755224', '287082','359152', '969429', '338314', '254676', '287922', '162583', '399871', '520489'];
// Check for passes // Check for passes
for(i=0;i<HOTP.length;i++) { for(i=0;i<HOTP.length;i++) {
args.C = i; args.C = i;
notp.getHOTP(args, assert.eql(notp.getHOTP(args), HOTP[i], 'HTOP value should be correct');
function(ret) {
assert.eql(ret, HOTP[i], 'HTOP value should be correct');
n++;
}
);
} }
beforeExit(function() {
assert.equal(HOTP.length, n, 'All tests should have been run');
});
}; };
@ -265,66 +185,25 @@ exports.testGetHOTP = function(beforeExit, assert) {
* Test getTOTP function. Uses same test values as for checkTOTP * Test getTOTP function. Uses same test values as for checkTOTP
*/ */
exports.testGetTOTP = function(beforeExit, assert) { exports.testGetTOTP = function(beforeExit, assert) {
var n = 0, var args = {
args = { W : 0,
W : 0, K : '12345678901234567890'
K : '12345678901234567890' };
};
// Check for test vector at 59s // Check for test vector at 59s
args._t = 59*1000; args._t = 59*1000;
notp.getTOTP(args, assert.eql(notp.getTOTP(args), '287082', 'TOTP values should match');
function(ret, w) {
assert.eql(ret, '287082', 'TOTP values should match');
n++;
}
);
// Check for test vector at 1234567890 // Check for test vector at 1234567890
args._t = 1234567890*1000; args._t = 1234567890*1000;
notp.getTOTP(args, assert.eql(notp.getTOTP(args), '005924', 'TOTP values should match');
function(ret, w) {
assert.eql(ret, '005924', 'TOTP values should match');
n++;
}
);
// Check for test vector at 1111111109 // Check for test vector at 1111111109
args._t = 1111111109*1000; args._t = 1111111109*1000;
notp.getTOTP(args, assert.eql(notp.getTOTP(args), '081804', 'TOTP values should match');
function(ret, w) {
assert.eql(ret, '081804', 'TOTP values should match');
n++;
}
);
// Check for test vector at 2000000000 // Check for test vector at 2000000000
args._t = 2000000000*1000; args._t = 2000000000*1000;
notp.getTOTP(args, assert.eql(notp.getTOTP(args), '279037', 'TOTP values should match');
function(ret, w) {
assert.eql(ret, '279037', 'TOTP values should match');
n++;
}
);
beforeExit(function() {
assert.equal(4, n, 'All tests should have been run');
});
}; };
/*
* Test encode to base32
*/
exports.testEncodeToBase32 = function(beforeExit, assert) {
assert.eql(notp.encBase32('12345678901234567890'), 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ');
};
/*
* Test decode base32
*/
exports.testDecodeFromBase32 = function(beforeExit, assert) {
assert.eql(notp.decBase32('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ'), '12345678901234567890');
};