There is no need for single letter arguments, they just make reading the code more confusing. The spec uses them to avoid typing longer words all over the place but we don't have as much code to produce. - P -> token - W -> window - K -> key - C -> counter - T -> time
222 lines
6.0 KiB
JavaScript
222 lines
6.0 KiB
JavaScript
|
|
var crypto = require('crypto');
|
|
|
|
/**
|
|
* Check a One Time Password based on a counter.
|
|
*
|
|
* @return {Object} null if failure, { delta: # } on success
|
|
* delta is the time step difference between the client and the server
|
|
*
|
|
* 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.
|
|
*
|
|
* 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
|
|
* W and increment it for each password check, and also to adjust
|
|
* it accordingly in the case where the client and server become
|
|
* out of sync (second argument returns non zero).
|
|
* E.g. if W = 100, and C = 5, this function will check the psscode
|
|
* against all One Time Passcodes between 5 and 105.
|
|
*
|
|
* Default - 50
|
|
*
|
|
* counter - Counter value. This should be stored by the application, must
|
|
* be user specific, and be incremented for each request.
|
|
*
|
|
*/
|
|
module.exports.checkHOTP = function(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) {
|
|
// We have found a matching code, trigger callback
|
|
// and pass offset
|
|
return { delta: i - counter };
|
|
}
|
|
}
|
|
|
|
// If we get to here then no codes have matched, return false
|
|
return false;
|
|
};
|
|
|
|
|
|
/**
|
|
* Check a One Time Password based on a timer.
|
|
*
|
|
* @return {Object} null if failure, { delta: # } on success
|
|
* delta is the time step difference between the client and the server
|
|
*
|
|
* 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.
|
|
*
|
|
* 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
|
|
* W and increment it for each password check, and also to adjust
|
|
* it accordingly in the case where the client and server become
|
|
* out of sync (second argument returns non zero).
|
|
* E.g. if W = 5, and C = 1000, this function will check the psscode
|
|
* against all One Time Passcodes between 995 and 1005.
|
|
*
|
|
* Default - 6
|
|
*
|
|
* time - The time step of the counter. This must be the same for
|
|
* every request and is used to calculate C.
|
|
*
|
|
* Default - 30
|
|
*
|
|
*/
|
|
module.exports.checkTOTP = 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 module.exports.checkHOTP(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);
|
|
};
|
|
|
|
/**
|
|
* convert an integer to a byte array
|
|
* @param {Integer} num
|
|
* @return {Array} bytes
|
|
*/
|
|
var intToBytes = function(num) {
|
|
var bytes = [];
|
|
|
|
for(var i=7 ; i>=0 ; --i) {
|
|
bytes[i] = num & (255);
|
|
num = num >> 8;
|
|
}
|
|
|
|
return bytes;
|
|
};
|
|
|
|
|
|
/**
|
|
* convert a hex value to a byte array
|
|
* @param {String} hex string of hex to convert to a byte array
|
|
* @return {Array} bytes
|
|
*/
|
|
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;
|
|
};
|
|
|