mirror of https://github.com/svaarala/duktape.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
229 lines
7.2 KiB
229 lines
7.2 KiB
11 years ago
|
/*
|
||
|
* Standalone TOTP implementation in Ecmascript.
|
||
|
*
|
||
|
* http://tools.ietf.org/html/rfc6238
|
||
|
* http://tools.ietf.org/html/rfc4226
|
||
|
* http://tools.ietf.org/html/rfc3174
|
||
|
* http://tools.ietf.org/html/rfc2104
|
||
|
* http://tools.ietf.org/html/rfc3548
|
||
|
*
|
||
|
* ">>> 0" is used to coerce result to unsigned 32 bits.
|
||
|
*
|
||
|
* Exercises bit arithmetic.
|
||
|
*/
|
||
|
|
||
|
var TOTP = {};
|
||
|
TOTP.nybbles = "0123456789abcdef";
|
||
|
TOTP.eightByteDivisors = [ 72057594037927936, 281474976710656, 1099511627776,
|
||
|
4294967296, 16777216, 65536, 256, 1 ];
|
||
|
TOTP.fiveByteDivisors = [ 4294967296, 16777216, 65536, 256, 1 ];
|
||
|
TOTP.stringToBytes = function (val) {
|
||
|
var i, n, res;
|
||
|
for (i = 0, n = val.length, res = []; i < n; i++) {
|
||
|
res.push(val.charCodeAt(i) & 0xff);
|
||
|
}
|
||
|
return res;
|
||
|
};
|
||
|
TOTP.bytesToString = function (val) {
|
||
|
return String.fromCharCode.apply(String, val);
|
||
|
};
|
||
|
TOTP.hexEncode = function (val) {
|
||
|
var i, n, t, res, nybbles = TOTP.nybbles;
|
||
|
for (i = 0, n = val.length, res = ''; i < n; i++) {
|
||
|
t = (val.charCodeAt(i) & 0xff);
|
||
|
res += nybbles[t >> 4] + nybbles[t & 0x0f];
|
||
|
}
|
||
|
return res;
|
||
|
};
|
||
|
TOTP.base32Decode = function (val) {
|
||
|
// The authenticators given are not direct inputs to TOTP, but are base-32 encoded.
|
||
|
// http://stackoverflow.com/questions/8529265/google-authenticator-implementation-in-python
|
||
|
// https://docs.python.org/3/library/base64.html#base64.b32decode
|
||
|
var i, j, n, x, t, npad, res, nstrip;
|
||
|
while ((val.length % 8) !== 0) { val += '='; } // tolerate missing pad
|
||
|
for (i = 0, n = val.length, t = 0, npad = 0, res = ''; i < n; i++) {
|
||
|
x = val.charCodeAt(i);
|
||
|
if (x >= 0x41 && x <= 0x5a) { t = t * 32 + (x - 0x41); }
|
||
|
else if (x >= 0x61 && x <= 0x7a) { t = t * 32 + (x - 0x61); }
|
||
|
else if (x >= 0x32 && x <= 0x37) { t = t * 32 + (x - 0x32 + 26); }
|
||
|
else if (x == 0x3d /* = */) { t = t * 32; npad++; }
|
||
|
else { throw new Error('invalid base32'); }
|
||
|
if ((i % 8) == 7) {
|
||
|
TOTP.fiveByteDivisors.forEach(function (divisor) {
|
||
|
res += String.fromCharCode((t / divisor) & 0xff);
|
||
|
});
|
||
|
t = 0;
|
||
|
}
|
||
|
}
|
||
|
nstrip = [ 0, 1, undefined, 2, 3, undefined, 4][npad];
|
||
|
if (nstrip === undefined) { throw new Error('invalid padding'); }
|
||
|
return res.substring(0, res.length - nstrip);
|
||
|
};
|
||
|
TOTP.sha1Raw = function (words) {
|
||
|
var w0_idx, w;
|
||
|
var h0, h1, h2, h3, h4, a, b, c, d, e;
|
||
|
var t, temp;
|
||
|
var Kval = [ 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 ];
|
||
|
var res;
|
||
|
|
||
|
function S(x, n) { return ((x << n) | (x >>> (32 - n))) >>> 0; }
|
||
|
function K(t) { return Kval[Math.floor(t / 20)]; }
|
||
|
function f(t, b, c, d) {
|
||
|
if (t < 20) { return ((b & c) | ((~b) & d)) >>> 0; }
|
||
|
else if (t < 40) { return (b ^ c ^ d) >>> 0; }
|
||
|
else if (t < 60) { return ((b & c) | (b & d) | (c & d)) >>> 0; }
|
||
|
else { return (b ^ c ^ d) >>> 0; }
|
||
|
}
|
||
|
|
||
|
// 'words' is an array of 32-bit values, padded to a multiple of 16 entries
|
||
|
if ((words.length % 16) !== 0) {
|
||
|
throw new Error('words length must be a multiple of 16');
|
||
|
}
|
||
|
|
||
|
h0 = 0x67452301; h1 = 0xefcdab89; h2 = 0x98badcfe; h3 = 0x10325476; h4 = 0xc3d2e1f0;
|
||
|
|
||
|
for (w0_idx = 0; w0_idx < words.length; w0_idx += 16) {
|
||
|
for (w = [], t = 0; t < 16; t++) { w[t] = words[w0_idx + t]; }
|
||
|
for (t = 16; t < 80; t++) {
|
||
|
w[t] = S((w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16]) >>> 0, 1);
|
||
|
}
|
||
|
a = h0; b = h1; c = h2; d = h3; e = h4;
|
||
|
for (t = 0; t < 80; t++) {
|
||
|
temp = (S(a, 5) + f(t, b, c, d) + e + w[t] + K(t)) >>> 0;
|
||
|
e = d; d = c; c = S(b, 30); b = a; a = temp;
|
||
|
}
|
||
|
h0 = (h0 + a) >>> 0; h1 = (h1 + b) >>> 0; h2 = (h2 + c) >>> 0;
|
||
|
h3 = (h3 + d) >>> 0; h4 = (h4 + e) >>> 0;
|
||
|
}
|
||
|
|
||
|
res = ''; [ h0, h1, h2, h3, h4 ].forEach(function (v) {
|
||
|
for (var i = 24; i >= 0; i -= 8) { res += String.fromCharCode((v >> i) & 0xff); }
|
||
|
});
|
||
|
return res;
|
||
|
};
|
||
|
TOTP.sha1 = function (str) {
|
||
|
var bytes, words, i, n;
|
||
|
var bitlen = str.length * 8; // assume representable with 53 bits
|
||
|
bytes = TOTP.stringToBytes(str);
|
||
|
bytes.push(0x80);
|
||
|
while (bytes.length % 64 !== 56) { bytes.push(0x00); }
|
||
|
TOTP.eightByteDivisors.forEach(function (divisor) {
|
||
|
bytes.push((bitlen / divisor) & 0xff);
|
||
|
});
|
||
|
//print('sha1 bytes', TOTP.hexEncode(TOTP.bytesToString(bytes)));
|
||
|
for (i = 0, n = bytes.length, words = []; i < n; i += 4) {
|
||
|
words[i / 4] = ((bytes[i] << 24) | (bytes[i + 1] << 16) |
|
||
|
(bytes[i + 2] << 8) | bytes[i + 3]) >>> 0;
|
||
|
}
|
||
|
//words.forEach(function (v) { print((v >>> 0).toString(16)); });
|
||
|
return TOTP.sha1Raw(words);
|
||
|
};
|
||
|
TOTP.hmacSha1 = function (key, text) {
|
||
|
var L = 20, B = 64;
|
||
|
var i, n, t1, t2;
|
||
|
if (key.length > B) { throw new Error('key too long (key hashing not supported)'); }
|
||
|
for (i = 0, n = key.length, t1 = '', t2 = ''; i < B; i++) {
|
||
|
t1 += String.fromCharCode(((key.charCodeAt(i) || 0) ^ 0x36) & 0xff);
|
||
|
t2 += String.fromCharCode(((key.charCodeAt(i) || 0) ^ 0x5c) & 0xff);
|
||
|
}
|
||
|
return TOTP.sha1(t2 + TOTP.sha1(t1 + text));
|
||
|
};
|
||
|
TOTP.HOTP = function (K, C, digits) {
|
||
|
var Cstr = '';
|
||
|
var HS, off, i, t, res;
|
||
|
TOTP.eightByteDivisors.forEach(function (divisor) {
|
||
|
Cstr += String.fromCharCode((C / divisor) & 0xff);
|
||
|
});
|
||
|
HS = TOTP.hmacSha1(K, Cstr);
|
||
|
off = HS.charCodeAt(19) & 0x0f;
|
||
|
for (i = off, t = 0; i < off + 4; i++) {
|
||
|
t = (t << 8) + (HS.charCodeAt(i) & 0xff);
|
||
|
}
|
||
|
t &= 0x7fffffff;
|
||
|
res = '';
|
||
|
while (digits-- > 0) { res = Math.floor(t % 10) + res; t = Math.floor(t / 10); }
|
||
|
return res;
|
||
|
};
|
||
|
TOTP.TOTP = function (K, time, digits) {
|
||
|
// Assume T0 = 0 (Unix epoch), X = 30 seconds, time is in milliseconds
|
||
|
var T = Math.floor(time / 30e3);
|
||
|
return TOTP.HOTP(K, T, digits);
|
||
|
};
|
||
|
|
||
|
/*===
|
||
|
sha1
|
||
|
da39a3ee5e6b4b0d3255bfef95601890afd80709
|
||
|
8843d7f92416211de9ebb963ff4ce28125932878
|
||
|
hmac-sha1
|
||
|
effcdf6ae5eb2fa2d27416d5f184df9c259a7c79
|
||
|
hotp
|
||
|
755224
|
||
|
287082
|
||
|
359152
|
||
|
969429
|
||
|
338314
|
||
|
254676
|
||
|
287922
|
||
|
162583
|
||
|
399871
|
||
|
520489
|
||
|
totp
|
||
|
94287082
|
||
|
07081804
|
||
|
14050471
|
||
|
65353130
|
||
|
base32
|
||
|
f
|
||
|
f
|
||
|
fo
|
||
|
fo
|
||
|
foo
|
||
|
foo
|
||
|
foo
|
||
|
foo
|
||
|
foo b
|
||
|
foo bar
|
||
|
foo bar
|
||
|
===*/
|
||
|
|
||
|
function test() {
|
||
|
var i;
|
||
|
|
||
|
print('sha1');
|
||
|
print(TOTP.hexEncode(TOTP.sha1('')));
|
||
|
print(TOTP.hexEncode(TOTP.sha1('foobar')));
|
||
|
|
||
|
print('hmac-sha1');
|
||
|
print(TOTP.hexEncode(TOTP.hmacSha1('Jefe', 'what do ya want for nothing?')));
|
||
|
|
||
|
print('hotp');
|
||
|
for (i = 0; i < 10; i++) {
|
||
|
print(TOTP.HOTP('12345678901234567890', i, 6));
|
||
|
}
|
||
|
|
||
|
print('totp');
|
||
|
print(TOTP.TOTP('12345678901234567890', 59e3, 8));
|
||
|
print(TOTP.TOTP('12345678901234567890', 1111111109e3, 8));
|
||
|
print(TOTP.TOTP('12345678901234567890', 1111111111e3, 8));
|
||
|
print(TOTP.TOTP('12345678901234567890', 20000000000e3, 8));
|
||
|
|
||
|
print('base32');
|
||
|
print(TOTP.base32Decode('MY======'));
|
||
|
print(TOTP.base32Decode('MY'));
|
||
|
print(TOTP.base32Decode('MZXQ===='));
|
||
|
print(TOTP.base32Decode('MZXQ'));
|
||
|
print(TOTP.base32Decode('MZXW6==='));
|
||
|
print(TOTP.base32Decode('MZXW6'));
|
||
|
print(TOTP.base32Decode('MZXW6IA='));
|
||
|
print(TOTP.base32Decode('MZXW6IA'));
|
||
|
print(TOTP.base32Decode('MZXW6IDC'));
|
||
|
print(TOTP.base32Decode('MZXW6IDCMFZA===='));
|
||
|
print(TOTP.base32Decode('MZXW6IDCMFZA'));
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
test();
|
||
|
} catch (e) {
|
||
|
print(e);
|
||
|
}
|