diff --git a/lib/base32/LICENSE.txt b/lib/base32/LICENSE.txt deleted file mode 100644 index 1e3aadd..0000000 --- a/lib/base32/LICENSE.txt +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2023, Chris Umbel, Lisoveliy - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/lib/base32/README.md b/lib/base32/README.md deleted file mode 100644 index 1242794..0000000 --- a/lib/base32/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# base32 - -Implementation of RFC 3548 Base32 encoding/decoding for zepp quickjs. - -## Usage - import {encode, decode} from 'base32/index.js' - base32.encode('node'); - // output: NZXWIZI= - base32.decode('NZXWIZI='); - //output: node diff --git a/lib/base32/index.js b/lib/base32/index.js deleted file mode 100644 index fb23939..0000000 --- a/lib/base32/index.js +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright (c) 2023, Chris Umbel, Lisoveliy - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -export { encode, decode } from './lib/base32/base32.js' \ No newline at end of file diff --git a/lib/base32/lib/base32/base32.js b/lib/base32/lib/base32/base32.js deleted file mode 100644 index 514f7c6..0000000 --- a/lib/base32/lib/base32/base32.js +++ /dev/null @@ -1,128 +0,0 @@ -/* -Copyright (c) 2023, Chris Umbel, Lisoveliy - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -'use strict'; - -var charTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; -var byteTable = [ - 0xff, 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, - 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, - 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, - 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, - 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff -]; - -function quintetCount(buff) { - var quintets = Math.floor(buff.length / 5); - return buff.length % 5 === 0 ? quintets : quintets + 1; -} - -export function encode(plain) { - if (!Buffer.isBuffer(plain)) { - plain = new Buffer(plain); - } - var i = 0; - var j = 0; - var shiftIndex = 0; - var digit = 0; - var encoded = new Buffer(quintetCount(plain) * 8); - - /* byte by byte isn't as pretty as quintet by quintet but tests a bit - faster. will have to revisit. */ - while (i < plain.length) { - var current = plain[i]; - - if (shiftIndex > 3) { - digit = current & (0xff >> shiftIndex); - shiftIndex = (shiftIndex + 5) % 8; - digit = (digit << shiftIndex) | ((i + 1 < plain.length) ? - plain[i + 1] : 0) >> (8 - shiftIndex); - i++; - } else { - digit = (current >> (8 - (shiftIndex + 5))) & 0x1f; - shiftIndex = (shiftIndex + 5) % 8; - if (shiftIndex === 0) i++; - } - - encoded[j] = charTable.charCodeAt(digit); - j++; - } - - for (i = j; i < encoded.length; i++) { - encoded[i] = 0x3d; //'='.charCodeAt(0) - } - - return encoded; -}; - -export function decode(encoded) { - var shiftIndex = 0; - var plainDigit = 0; - var plainChar; - var plainPos = 0; - if (!Buffer.isBuffer(encoded)) { - encoded = new Buffer(encoded); - } - var decoded = new Buffer(Math.ceil(encoded.length * 5 / 8)); - - /* byte by byte isn't as pretty as octet by octet but tests a bit - faster. will have to revisit. */ - for (var i = 0; i < encoded.length; i++) { - if (encoded[i] === 0x3d) { //'=' - break; - } - - var encodedByte = encoded[i] - 0x30; - - if (encodedByte < byteTable.length) { - plainDigit = byteTable[encodedByte]; - - if (shiftIndex <= 3) { - shiftIndex = (shiftIndex + 5) % 8; - - if (shiftIndex === 0) { - plainChar |= plainDigit; - decoded[plainPos] = plainChar; - plainPos++; - plainChar = 0; - } else { - plainChar |= 0xff & (plainDigit << (8 - shiftIndex)); - } - } else { - shiftIndex = (shiftIndex + 5) % 8; - plainChar |= 0xff & (plainDigit >>> shiftIndex); - decoded[plainPos] = plainChar; - plainPos++; - - plainChar = 0xff & (plainDigit << (8 - shiftIndex)); - } - } else { - throw new Error('Invalid input - it is not base32 encoded string'); - } - } - - return decoded.slice(0, plainPos); -}; diff --git a/lib/base32/package.json b/lib/base32/package.json deleted file mode 100644 index 9e3ede1..0000000 --- a/lib/base32/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "base32", - "description": "Implementation RFC 3548 Base32 encoding/decoding for node.", - "version": "1.0.2-zepp", - "author": "Chris Umbel ", - "keywords": ["base32", "encoding"], - "main": "index.js", - "repository": { - "type": "git", - "url": "git://github.com/chrisumbel/thirty-two.git" - } -} diff --git a/lib/totp-quickjs/OTPGenerator.js b/lib/totp-quickjs/OTPGenerator.js new file mode 100644 index 0000000..50f4867 --- /dev/null +++ b/lib/totp-quickjs/OTPGenerator.js @@ -0,0 +1,54 @@ +import { decode } from "./base32decoder.js"; +import jsSHA from "jssha"; +"use bigint" +/** + * get HOTP based on counter + * @param {BigInt} counter BigInt counter of HOTP + * @param {string} secret base32 encoded string + * @param {number} [digits=6] number of digits in OTP token + * @param {string} [hashType='SHA-1'] type of hash (more in jsSHA documentation) + * @returns HOTP string + */ +export function getHOTP(counter, secret, digits = 6, hashType = 'SHA-1'){ + + //Stage 1: Prepare data + const rawDataCounter = new DataView(new ArrayBuffer(8)) + rawDataCounter.setUint32(0, counter >> 32) + rawDataCounter.setUint32(4, counter) + const bCounter = new Uint8Array(rawDataCounter.buffer) + + const bSecret = new Uint8Array(decode(secret).match(/.{1,2}/g).map(chunk => parseInt(chunk, 16))); //confirmed + + //Stage 2: Hash data + const jssha = new jsSHA(hashType, 'UINT8ARRAY') + jssha.setHMACKey(bSecret, 'UINT8ARRAY') + jssha.update(bCounter) + const hmacResult = jssha.getHMAC('UINT8ARRAY') //confirmed + + //Stage 3: Dynamic truncate + const offsetB = hmacResult[19] & 0xf; + const P = hmacResult.slice(offsetB, offsetB + 4) + P[0] = P[0] & 0x7f; + + //Stage 4: Format string + let res = (new DataView(P.buffer).getInt32(0) % Math.pow(10, digits)).toString() + while(res.length < digits) + res = '0' + res; + return res; +} + +/** + * get OTP based on current time + * @param {string} secret base32 encoded string + * @param {number} [digits=6] digits in OTP + * @param {number} [time=Date.now()] time for counter (default unix time epoch) + * @param {number} [fetchTime=30] period of token in seconds + * @param {number} [timeOffset=0] time offset for token in seconds + * @param {string} [hashType='SHA-1'] type of hash (more in jsSHA documentation) + * @returns TOTP string + */ +export function getTOTP(secret, digits = 6, time = Date.now(), fetchTime = 30, timeOffset = 0, hashType = 'SHA-1') +{ + const unixTime = Math.round((time / 1000 + timeOffset) / fetchTime) + return getHOTP(BigInt(unixTime), secret, digits) +} \ No newline at end of file diff --git a/lib/totp-quickjs/base32decoder.js b/lib/totp-quickjs/base32decoder.js new file mode 100644 index 0000000..b74d251 --- /dev/null +++ b/lib/totp-quickjs/base32decoder.js @@ -0,0 +1,26 @@ +export function decode(base32) { + for ( + var base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", + bits = "", + hex = "", + i = 0; + i < base32.length; + i++ + ) { + var val = base32chars.indexOf(base32.charAt(i).toUpperCase()); + bits += leftpad(val.toString(2), 5, "0"); + } + for (i = 0; i + 4 <= bits.length; i += 4) { + var chunk = bits.substr(i, 4); + hex += parseInt(chunk, 2).toString(16); + } + return hex; +} + +function leftpad(str, len, pad) { + return ( + len + 1 >= str.length && + (str = new Array(len + 1 - str.length).join(pad) + str), + str + ); +} \ No newline at end of file diff --git a/lib/totp-quickjs/index.js b/lib/totp-quickjs/index.js new file mode 100644 index 0000000..501c298 --- /dev/null +++ b/lib/totp-quickjs/index.js @@ -0,0 +1,61 @@ +import { getHOTP } from "./OTPGenerator.js" +"use bigint" +/** + * TOTP instance + */ +export class TOTP{ + /** + * + * @param {string} secret base32 encoded string + * @param {string} issuer issuer of TOTP + * @param {number} [digits=6] number of digits in OTP token + * @param {number} [fetchTime=30] period of token in seconds + * @param {number} [timeOffset=0] time offset for token in seconds + * @param {string} [hashType='SHA-1'] type of hash (more in jsSHA documentation) + */ + constructor(secret, + issuer, + digits = 6, + fetchTime = 30, + timeOffset = 0, + hashType = 'SHA-1') + { + this.secret = secret + this.issuer = issuer + this.digits = digits + this.fetchTime = fetchTime + this.timeOffset = timeOffset + this.hashType = hashType + } + /** + * + * @param {number} time time for counter (default unix time epoch) + * @returns OTP instance + */ + getOTP(time = Date.now()){ + const unixTime = (time / 1000 + this.timeOffset) / this.fetchTime + const otp = getHOTP(Math.floor(unixTime), this.secret, this.digits) + const expireTime = time + + (this.fetchTime - + (time / 1000 + this.timeOffset) % + this.fetchTime) * 1000 + + return new OTP(otp, expireTime) + } +} + +/** + * Class for TOTP.getOTP result + */ +export class OTP{ + /** + * + * @param {string} otp OTP string + * @param {number} expireTime time in seconds to reset OTP + */ + constructor(otp, expireTime) + { + this.otp = otp + this.expireTime = expireTime + } +} \ No newline at end of file diff --git a/lib/totp.js/index.js b/lib/totp.js/index.js deleted file mode 100644 index 7dcf2e0..0000000 --- a/lib/totp.js/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { HOTP } from './lib/hotp.js' -export { TOTP } from './lib/totp.js' diff --git a/lib/totp.js/lib/hotp.js b/lib/totp.js/lib/hotp.js deleted file mode 100644 index 129ec74..0000000 --- a/lib/totp.js/lib/hotp.js +++ /dev/null @@ -1,72 +0,0 @@ -import { encode, decode } from '../../base32/index.js' -import jsSHA from 'jssha' - -export class HOTP { - - /** - * - * @param {string} key secret key - * @param {*} digit lenth of otp code - */ - constructor(key, digit = 6) { - this.key = key; - this.digit = digit; - } - static encode(data){ - return encode(data) - } - /** - * generate secret key - * @param {int} len - */ - static randomKey(len = 16) { - const str = Math.random().toString(36); - return encode(str).toString().substr(0, len); - } - - /** - * generate a OTP base on HMAC-SHA-1 - * @param {int} movingFactor counter - */ - genOTP(movingFactor) { - const hmacSha = new jsSHA('SHA-1', 'BYTES'); - hmacSha.setHMACKey(decode(this.key).toString(), 'BYTES'); - - const factorByte = this._factor2ByteText(movingFactor); - hmacSha.update(factorByte); - - const hmac_result = hmacSha.getHMAC('BYTES'); - return this._truncat(hmac_result); - } - - /** - * verify a OPT code - * @param {string} opt opt code - * @param {int} movingFactor counter - */ - verify(opt, movingFactor) { - return opt === this.genOTP(movingFactor); - } - - _truncat(hmac_result) { - const offset = hmac_result[19].charCodeAt() & 0xf; - const bin_code = (hmac_result[offset].charCodeAt() & 0x7f) << 24 - | (hmac_result[offset + 1].charCodeAt() & 0xff) << 16 - | (hmac_result[offset + 2].charCodeAt() & 0xff) << 8 - | (hmac_result[offset + 3].charCodeAt() & 0xff); - let otp = (bin_code % 10 ** this.digit).toString(); - while (otp.length < this.digit) { - otp = '0' + otp; - } - return otp; - } - - _factor2ByteText(movingFactor) { - const text = new Array(8); - for (let i = text.length - 1; i >= 0; i--) { - text[i] = String.fromCharCode(movingFactor & 0xFF); - movingFactor >>= 8; - } - return text.join(''); - } -} \ No newline at end of file diff --git a/lib/totp.js/lib/totp.js b/lib/totp.js/lib/totp.js deleted file mode 100644 index 172414e..0000000 --- a/lib/totp.js/lib/totp.js +++ /dev/null @@ -1,16 +0,0 @@ -import { HOTP } from "./hotp.js"; - -export class TOTP extends HOTP { - constructor(key) { - super(key) - } - - genOTP(timeStep = 30, t0 = 0) { - const T = Math.floor((Date.now() / 1000 - t0) / timeStep); - return super.genOTP(T); - } - - verify(otp, timeStep = 30, t0 = 0) { - return otp === this.genOTP(timeStep, t0); - } -} \ No newline at end of file diff --git a/lib/totp.js/package.json b/lib/totp.js/package.json deleted file mode 100644 index a915c48..0000000 --- a/lib/totp.js/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "totp.js", - "version": "1.0.1-zepp", - "description": "Two-factor authentication implementation in pure javascript. One-time password generator (HOTP/TOTP) with support for Google Authenticator. ", - "main": "index.js", - "scripts": { - "test": "./node_modules/.bin/mocha" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/wuyanxin/totp.js.git" - }, - "keywords": [ - "otp", - "hotp", - "totp", - "Two-factor authentication", - "Google authenticator" - ], - "author": "wuyanxin", - "license": "MIT", - "bugs": { - "url": "https://github.com/wuyanxin/totp.js/issues" - }, - "homepage": "https://github.com/wuyanxin/totp.js#readme", - "dependencies": { - "jssha": "^2.3.1" - }, - "devDependencies": { - "mocha": "^5.0.4" - } -} diff --git a/page/index.js b/page/index.js index c68f940..6a6615a 100644 --- a/page/index.js +++ b/page/index.js @@ -33,8 +33,9 @@ Page({ const buttonHeight = height / 4; const margin = 10; let totpHeight = margin; - + for(let i = 0; i < buffer.length; i++){ + console.log(buffer[i]) createWidget(widget.FILL_RECT, { x: width / 2 - buttonWidth / 2, y: totpHeight, @@ -53,7 +54,7 @@ Page({ align_h: align.CENTER_H, align_v: align.CENTER_V, text_style: text_style.NONE, - text: buffer[i].name + text: (buffer[i].expireTime - Date.now()) / 1000 }) createWidget(widget.TEXT, { x: 0, @@ -65,7 +66,7 @@ Page({ align_h: align.CENTER_H, align_v: align.CENTER_V, text_style: text_style.NONE, - text: buffer[i].data + text: buffer[i].otp }) totpHeight += margin + buttonHeight; diff --git a/page/totplogic/totps.js b/page/totplogic/totps.js index 2fd3aaf..1aca2be 100644 --- a/page/totplogic/totps.js +++ b/page/totplogic/totps.js @@ -1,4 +1,4 @@ -import { TOTP } from "../../lib/totp.js/lib/totp"; +import { TOTP } from "../../lib/totp-quickjs"; export class TOTPBuffer{ constructor(){ @@ -6,15 +6,6 @@ export class TOTPBuffer{ } getTOTPs(){ - return [new TOTPData("Contabo-Customer-Control-Panel-11755808: my.contabo.com", new TOTP(TOTP.encode('UU5WIIWKDNAHUNNL')).genOTP()), - new TOTPData("OTPData", new TOTP(TOTP.encode('LCHJZO23LT3Z2QYNYAYAJXH5HFDZ5YI2')).genOTP()), - new TOTPData("test", new TOTP(TOTP.encode('GAXHMZDEGJVG64LP')).genOTP())] - } -} - -export class TOTPData{ - constructor(name, data){ - this.name = name; - this.data = data; + return [new TOTP('JBSWY3DPEHPK3PXP').getOTP()] } } \ No newline at end of file