223 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			223 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 
 | |
| 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.
 | |
|  *
 | |
|  * @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.
 | |
|  *
 | |
|  */
 | |
| hotp.verify = function(token, key, opt) {
 | |
| 	var window = opt.window || 50;
 | |
| 	var counter = opt.counter || 0;
 | |
| 
 | |
| 	// 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.gen(key, 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;
 | |
| };
 | |
| 
 | |
| 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.
 | |
|  *
 | |
|  * @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
 | |
|  *
 | |
|  */
 | |
| totp.verify = function(token, 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.verify(token, key, opt);
 | |
| };
 | |
| 
 | |
| module.exports.hotp = hotp;
 | |
| module.exports.totp = totp;
 | |
| 
 | |
| /**
 | |
|  * 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;
 | |
| };
 | |
| 
 |