From 8ab87fe8e972443755496c50290090243342b698 Mon Sep 17 00:00:00 2001 From: Savely Savianok <1986developer@gmail.com> Date: Wed, 26 Feb 2025 02:28:30 +0300 Subject: [PATCH 1/7] fix: fixed parse of issuer --- app.json | 2 +- package.json | 2 +- setting/utils/queryParser.js | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app.json b/app.json index 5085576..3050e55 100644 --- a/app.json +++ b/app.json @@ -6,7 +6,7 @@ "appType": "app", "version": { "code": 1, - "name": "1.1.1" + "name": "1.1.2" }, "icon": "icon.png", "vender": "zepp", diff --git a/package.json b/package.json index 864b752..f583b22 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "totpfit", - "version": "1.1.1", + "version": "1.1.2", "description": "Another 2FAuthenticator based on TOTP for Zepp Amazfit GTS 4", "main": "app.js", "author": "Lisoveliy", diff --git a/setting/utils/queryParser.js b/setting/utils/queryParser.js index c999285..0e6ba4a 100644 --- a/setting/utils/queryParser.js +++ b/setting/utils/queryParser.js @@ -23,6 +23,10 @@ export function getTOTPByLink(link) { issuer = issuer.replace("%20", " "); client = client.replace("%20", " "); + if(issuer == client){ + issuer = args[3].split("issuer=")[1]?.split("&")[0] + } + return new TOTP( secret, issuer, From ca342bf6e4e7934ab4dbe51e394161bdabd8db18 Mon Sep 17 00:00:00 2001 From: Savely Savianok <1986developer@gmail.com> Date: Wed, 26 Feb 2025 02:36:43 +0300 Subject: [PATCH 2/7] fix: fixed cleanup of issuer for "%20" --- setting/utils/queryParser.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setting/utils/queryParser.js b/setting/utils/queryParser.js index 0e6ba4a..ed1a61c 100644 --- a/setting/utils/queryParser.js +++ b/setting/utils/queryParser.js @@ -20,13 +20,13 @@ export function getTOTPByLink(link) { if (secret === undefined) throw new Error("Secret not defined"); - issuer = issuer.replace("%20", " "); - client = client.replace("%20", " "); - if(issuer == client){ issuer = args[3].split("issuer=")[1]?.split("&")[0] } + issuer = issuer.replace("%20", " "); + client = client.replace("%20", " "); + return new TOTP( secret, issuer, From ccfd422061bd58d49a869e36fcbe4dbbfb0bf46f Mon Sep 17 00:00:00 2001 From: Savely Savianok <1986developer@gmail.com> Date: Wed, 26 Feb 2025 02:46:16 +0300 Subject: [PATCH 3/7] fix: final fix of parsing issuer and client --- app.json | 2 +- package.json | 2 +- setting/utils/queryParser.js | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app.json b/app.json index 3050e55..12119f3 100644 --- a/app.json +++ b/app.json @@ -6,7 +6,7 @@ "appType": "app", "version": { "code": 1, - "name": "1.1.2" + "name": "1.1.3" }, "icon": "icon.png", "vender": "zepp", diff --git a/package.json b/package.json index f583b22..d9a0d69 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "totpfit", - "version": "1.1.2", + "version": "1.1.3", "description": "Another 2FAuthenticator based on TOTP for Zepp Amazfit GTS 4", "main": "app.js", "author": "Lisoveliy", diff --git a/setting/utils/queryParser.js b/setting/utils/queryParser.js index ed1a61c..985dcde 100644 --- a/setting/utils/queryParser.js +++ b/setting/utils/queryParser.js @@ -24,8 +24,8 @@ export function getTOTPByLink(link) { issuer = args[3].split("issuer=")[1]?.split("&")[0] } - issuer = issuer.replace("%20", " "); - client = client.replace("%20", " "); + issuer = decodeURIComponent(issuer); + client = decodeURIComponent(client); return new TOTP( secret, @@ -37,6 +37,7 @@ export function getTOTPByLink(link) { getHashType(algorithm) ); } catch (err) { + console.log(err) return null; } } From bd490d46426e032b651b0d25cda5d16c1771ebb5 Mon Sep 17 00:00:00 2001 From: Savely Savianok <1986developer@gmail.com> Date: Fri, 28 Feb 2025 23:32:54 +0300 Subject: [PATCH 4/7] feat: adding protobuf decoder for parse of google auth migrations --- lib/protobuf-decoder/hexUtils.js | 36 +++++++ lib/protobuf-decoder/intUtils.js | 27 +++++ lib/protobuf-decoder/protobufDecoder.js | 132 ++++++++++++++++++++++++ lib/protobuf-decoder/varintUtils.js | 24 +++++ lib/totp-quickjs/base32decoder.js | 20 ++++ setting/utils/queryParser.js | 35 +++++-- 6 files changed, 266 insertions(+), 8 deletions(-) create mode 100644 lib/protobuf-decoder/hexUtils.js create mode 100644 lib/protobuf-decoder/intUtils.js create mode 100644 lib/protobuf-decoder/protobufDecoder.js create mode 100644 lib/protobuf-decoder/varintUtils.js diff --git a/lib/protobuf-decoder/hexUtils.js b/lib/protobuf-decoder/hexUtils.js new file mode 100644 index 0000000..6f6ff52 --- /dev/null +++ b/lib/protobuf-decoder/hexUtils.js @@ -0,0 +1,36 @@ +import { Buffer } from 'buffer' + +export function parseInput(input) { + const normalizedInput = input.replace(/\s/g, ""); + const normalizedHexInput = normalizedInput.replace(/0x/g, "").toLowerCase(); + if (isHex(normalizedHexInput)) { + return Buffer.from(normalizedHexInput, "hex"); + } else { + return Buffer.from(normalizedInput, "base64"); + } +} + +export function isHex(string) { + let result = true; + for (const char of string) { + if (!((char >= "a" && char <= "f") || (char >= "0" && char <= "9"))) { + result = false; + } + } + return result; +} + +export function bufferLeToBeHex(buffer) { + let output = ""; + for (const v of buffer) { + const hex = v.toString(16); + if (hex.length === 1) { + output = "0" + hex + output; + } else { + output = hex + output; + } + } + return output; +} + +export const bufferToPrettyHex = b => [...b].map(c => c.toString(16).padStart(2, '0')).join(' '); diff --git a/lib/protobuf-decoder/intUtils.js b/lib/protobuf-decoder/intUtils.js new file mode 100644 index 0000000..3e16ea4 --- /dev/null +++ b/lib/protobuf-decoder/intUtils.js @@ -0,0 +1,27 @@ +import JSBI from "jsbi"; + +export function interpretAsSignedType(n) { + // see https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/wire_format_lite.h#L857-L876 + // however, this is a simpler equivalent formula + const isEven = JSBI.equal(JSBI.bitwiseAnd(n, JSBI.BigInt(1)), JSBI.BigInt(0)); + if (isEven) { + return JSBI.divide(n, JSBI.BigInt(2)); + } else { + return JSBI.multiply( + JSBI.BigInt(-1), + JSBI.divide(JSBI.add(n, JSBI.BigInt(1)), JSBI.BigInt(2)) + ); + } +} + +export function interpretAsTwosComplement(n, bits) { + const isTwosComplement = JSBI.equal( + JSBI.signedRightShift(n, JSBI.BigInt(bits - 1)), + JSBI.BigInt(1) + ); + if (isTwosComplement) { + return JSBI.subtract(n, JSBI.leftShift(JSBI.BigInt(1), JSBI.BigInt(bits))); + } else { + return n; + } +} diff --git a/lib/protobuf-decoder/protobufDecoder.js b/lib/protobuf-decoder/protobufDecoder.js new file mode 100644 index 0000000..f9a11af --- /dev/null +++ b/lib/protobuf-decoder/protobufDecoder.js @@ -0,0 +1,132 @@ +import { decodeVarint } from "./varintUtils"; + +class BufferReader { + constructor(buffer) { + this.buffer = buffer; + this.offset = 0; + } + + readVarInt() { + const result = decodeVarint(this.buffer, this.offset); + this.offset += result.length; + + return result.value; + } + + readBuffer(length) { + this.checkByte(length); + const result = this.buffer.slice(this.offset, this.offset + length); + this.offset += length; + + return result; + } + + // gRPC has some additional header - remove it + trySkipGrpcHeader() { + const backupOffset = this.offset; + + if (this.buffer[this.offset] === 0 && this.leftBytes() >= 5) { + this.offset++; + const length = this.buffer.readInt32BE(this.offset); + this.offset += 4; + + if (length > this.leftBytes()) { + // Something is wrong, revert + this.offset = backupOffset; + } + } + } + + leftBytes() { + return this.buffer.length - this.offset; + } + + checkByte(length) { + const bytesAvailable = this.leftBytes(); + if (length > bytesAvailable) { + throw new Error( + "Not enough bytes left. Requested: " + + length + + " left: " + + bytesAvailable + ); + } + } + + checkpoint() { + this.savedOffset = this.offset; + } + + resetToCheckpoint() { + this.offset = this.savedOffset; + } +} + +export const TYPES = { + VARINT: 0, + FIXED64: 1, + LENDELIM: 2, + FIXED32: 5 +}; + +export function decodeProto(buffer) { + const reader = new BufferReader(buffer); + const parts = []; + + reader.trySkipGrpcHeader(); + + try { + while (reader.leftBytes() > 0) { + reader.checkpoint(); + + const byteRange = [reader.offset]; + const indexType = parseInt(reader.readVarInt().toString()); + const type = indexType & 0b111; + const index = indexType >> 3; + + let value; + if (type === TYPES.VARINT) { + value = reader.readVarInt().toString(); + } else if (type === TYPES.LENDELIM) { + const length = parseInt(reader.readVarInt().toString()); + value = reader.readBuffer(length); + } else if (type === TYPES.FIXED32) { + value = reader.readBuffer(4); + } else if (type === TYPES.FIXED64) { + value = reader.readBuffer(8); + } else { + throw new Error("Unknown type: " + type); + } + byteRange.push(reader.offset); + + parts.push({ + byteRange, + index, + type, + value + }); + } + } catch (err) { + reader.resetToCheckpoint(); + } + + return { + parts, + leftOver: reader.readBuffer(reader.leftBytes()) + }; +} + +export function typeToString(type, subType) { + switch (type) { + case TYPES.VARINT: + return "varint"; + case TYPES.LENDELIM: + return subType || "len_delim"; + case TYPES.FIXED32: + return "fixed32"; + case TYPES.FIXED64: + return "fixed64"; + default: + return "unknown"; + } +} diff --git a/lib/protobuf-decoder/varintUtils.js b/lib/protobuf-decoder/varintUtils.js new file mode 100644 index 0000000..ceae6f5 --- /dev/null +++ b/lib/protobuf-decoder/varintUtils.js @@ -0,0 +1,24 @@ +"use bigint" +export function decodeVarint(buffer, offset) { + let res = BigInt(0); + let shift = 0; + let byte = 0; + + do { + if (offset >= buffer.length) { + throw new RangeError("Index out of bound decoding varint"); + } + + byte = buffer[offset++]; + + const multiplier = exponentiate(BigInt(2), BigInt(shift)); + const thisByteValue = multiply(BigInt(byte & 0x7f), multiplier); + shift += 7; + res = add(res, thisByteValue); + } while (byte >= 0x80); + + return { + value: res, + length: shift / 7 + }; +} diff --git a/lib/totp-quickjs/base32decoder.js b/lib/totp-quickjs/base32decoder.js index b74d251..7c0e514 100644 --- a/lib/totp-quickjs/base32decoder.js +++ b/lib/totp-quickjs/base32decoder.js @@ -23,4 +23,24 @@ function leftpad(str, len, pad) { (str = new Array(len + 1 - str.length).join(pad) + str), str ); +} + +export function base64decode(base64String) { + var sliceSize = 1024; + var byteCharacters = window.atob(base64String); + var bytesLength = byteCharacters.length; + var slicesCount = Math.ceil(bytesLength / sliceSize); + var byteArrays = new Array(slicesCount); + + for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) { + var begin = sliceIndex * sliceSize; + var end = Math.min(begin + sliceSize, bytesLength); + + var bytes = new Array(end - begin); + for (var offset = begin, i = 0; offset < end; ++i, ++offset) { + bytes[i] = byteCharacters[offset].charCodeAt(0); + } + byteArrays[sliceIndex] = new Uint8Array(bytes); + } + return byteArrays } \ No newline at end of file diff --git a/setting/utils/queryParser.js b/setting/utils/queryParser.js index 985dcde..8bb2365 100644 --- a/setting/utils/queryParser.js +++ b/setting/utils/queryParser.js @@ -1,10 +1,29 @@ +import { decodeProto } from "../../lib/protobuf-decoder/protobufDecoder"; import { TOTP } from "../../lib/totp-quickjs"; +import { base64decode } from "../../lib/totp-quickjs/base32decoder"; -const otpScheme = "otpauth:/"; +const otpauthScheme = "otpauth:/"; +const googleMigrationScheme = "otpauth-migration:/"; export function getTOTPByLink(link) { + if(link.includes(otpauthScheme)) + return getByOtpauthScheme(link) + if(link.includes(googleMigrationScheme)) + return getByGoogleMigrationScheme(link) + + return null; +} + +function getHashType(algorithm) { + if (algorithm == "SHA1") return "SHA-1"; + if (algorithm == "SHA256") return "SHA-256"; + if (algorithm == "SHA512") return "SHA-512"; + else return "SHA-1"; +} + +function getByOtpauthScheme(link){ try { - let args = link.split("/", otpScheme.length); + let args = link.split("/", otpauthScheme.length); let type = args[2]; //Returns 'hotp' or 'totp' let issuer = args[3].split(":")[0]?.split("?")[0]; //Returns issuer let client = @@ -42,9 +61,9 @@ export function getTOTPByLink(link) { } } -function getHashType(algorithm) { - if (algorithm == "SHA1") return "SHA-1"; - if (algorithm == "SHA256") return "SHA-256"; - if (algorithm == "SHA512") return "SHA-512"; - else return "SHA-1"; -} +function getByGoogleMigrationScheme(link){ + console.log("Hello") + let data = link.split("data=")[1]; //Returns secret + let decodedProto = decodeProto(base64decode(data)); + console.log(decodedProto) +} \ No newline at end of file From ba4b8cc29edaec094f225966f21455ce7b840441 Mon Sep 17 00:00:00 2001 From: Savely Savianok <1986developer@gmail.com> Date: Sat, 1 Mar 2025 22:58:48 +0300 Subject: [PATCH 5/7] feat(staging): adding protobuf decoder (part 2) --- lib/protobuf-decoder/protobufDecoder.js | 2 +- lib/totp-quickjs/base32decoder.js | 38 +++++++++++++++---------- setting/utils/queryParser.js | 7 +++-- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/lib/protobuf-decoder/protobufDecoder.js b/lib/protobuf-decoder/protobufDecoder.js index f9a11af..558f1cb 100644 --- a/lib/protobuf-decoder/protobufDecoder.js +++ b/lib/protobuf-decoder/protobufDecoder.js @@ -1,6 +1,6 @@ import { decodeVarint } from "./varintUtils"; -class BufferReader { +export class BufferReader { constructor(buffer) { this.buffer = buffer; this.offset = 0; diff --git a/lib/totp-quickjs/base32decoder.js b/lib/totp-quickjs/base32decoder.js index 7c0e514..821b33c 100644 --- a/lib/totp-quickjs/base32decoder.js +++ b/lib/totp-quickjs/base32decoder.js @@ -25,22 +25,30 @@ function leftpad(str, len, pad) { ); } -export function base64decode(base64String) { - var sliceSize = 1024; - var byteCharacters = window.atob(base64String); - var bytesLength = byteCharacters.length; - var slicesCount = Math.ceil(bytesLength / sliceSize); - var byteArrays = new Array(slicesCount); +export function base64decode(base64) { + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + let result = []; + let i = 0, j = 0; + let b1, b2, b3, b4; - for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) { - var begin = sliceIndex * sliceSize; - var end = Math.min(begin + sliceSize, bytesLength); + while (i < base64.length) { + b1 = chars.indexOf(base64.charAt(i++)); + b2 = chars.indexOf(base64.charAt(i++)); + b3 = chars.indexOf(base64.charAt(i++)); + b4 = chars.indexOf(base64.charAt(i++)); - var bytes = new Array(end - begin); - for (var offset = begin, i = 0; offset < end; ++i, ++offset) { - bytes[i] = byteCharacters[offset].charCodeAt(0); - } - byteArrays[sliceIndex] = new Uint8Array(bytes); + if (b1 === -1 || b2 === -1) break; + + result[j++] = (b1 << 2) | (b2 >> 4); + + if (b3 !== -1) { + result[j++] = ((b2 & 15) << 4) | (b3 >> 2); } - return byteArrays + + if (b4 !== -1) { + result[j++] = ((b3 & 3) << 6) | b4; + } + } + + return result.slice(0, j); } \ No newline at end of file diff --git a/setting/utils/queryParser.js b/setting/utils/queryParser.js index 8bb2365..2bd7953 100644 --- a/setting/utils/queryParser.js +++ b/setting/utils/queryParser.js @@ -64,6 +64,9 @@ function getByOtpauthScheme(link){ function getByGoogleMigrationScheme(link){ console.log("Hello") let data = link.split("data=")[1]; //Returns secret - let decodedProto = decodeProto(base64decode(data)); - console.log(decodedProto) + data = decodeURIComponent(data); + console.log(data) + let decode = base64decode(data); + console.log(decode) + console.log(decodeProto(decode)); } \ No newline at end of file From fb6ece773cd709b4e4e0fd9b57d8d3425ea04b31 Mon Sep 17 00:00:00 2001 From: Savely Savianok <1986developer@gmail.com> Date: Mon, 17 Mar 2025 13:37:12 +0300 Subject: [PATCH 6/7] feat: ported protobuf decoder --- lib/protobuf-decoder/intUtils.js | 27 ------------------------- lib/protobuf-decoder/protobufDecoder.js | 2 +- lib/protobuf-decoder/varintUtils.js | 10 ++++----- setting/index.js | 1 - setting/utils/queryParser.js | 7 ++++--- 5 files changed, 10 insertions(+), 37 deletions(-) delete mode 100644 lib/protobuf-decoder/intUtils.js diff --git a/lib/protobuf-decoder/intUtils.js b/lib/protobuf-decoder/intUtils.js deleted file mode 100644 index 3e16ea4..0000000 --- a/lib/protobuf-decoder/intUtils.js +++ /dev/null @@ -1,27 +0,0 @@ -import JSBI from "jsbi"; - -export function interpretAsSignedType(n) { - // see https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/wire_format_lite.h#L857-L876 - // however, this is a simpler equivalent formula - const isEven = JSBI.equal(JSBI.bitwiseAnd(n, JSBI.BigInt(1)), JSBI.BigInt(0)); - if (isEven) { - return JSBI.divide(n, JSBI.BigInt(2)); - } else { - return JSBI.multiply( - JSBI.BigInt(-1), - JSBI.divide(JSBI.add(n, JSBI.BigInt(1)), JSBI.BigInt(2)) - ); - } -} - -export function interpretAsTwosComplement(n, bits) { - const isTwosComplement = JSBI.equal( - JSBI.signedRightShift(n, JSBI.BigInt(bits - 1)), - JSBI.BigInt(1) - ); - if (isTwosComplement) { - return JSBI.subtract(n, JSBI.leftShift(JSBI.BigInt(1), JSBI.BigInt(bits))); - } else { - return n; - } -} diff --git a/lib/protobuf-decoder/protobufDecoder.js b/lib/protobuf-decoder/protobufDecoder.js index 558f1cb..f99a573 100644 --- a/lib/protobuf-decoder/protobufDecoder.js +++ b/lib/protobuf-decoder/protobufDecoder.js @@ -72,7 +72,6 @@ export const TYPES = { export function decodeProto(buffer) { const reader = new BufferReader(buffer); const parts = []; - reader.trySkipGrpcHeader(); try { @@ -108,6 +107,7 @@ export function decodeProto(buffer) { } } catch (err) { reader.resetToCheckpoint(); + console.log(err); } return { diff --git a/lib/protobuf-decoder/varintUtils.js b/lib/protobuf-decoder/varintUtils.js index ceae6f5..fbd69a2 100644 --- a/lib/protobuf-decoder/varintUtils.js +++ b/lib/protobuf-decoder/varintUtils.js @@ -1,6 +1,5 @@ -"use bigint" export function decodeVarint(buffer, offset) { - let res = BigInt(0); + let res = this.BigInt(0); let shift = 0; let byte = 0; @@ -10,11 +9,12 @@ export function decodeVarint(buffer, offset) { } byte = buffer[offset++]; + console.log(this) - const multiplier = exponentiate(BigInt(2), BigInt(shift)); - const thisByteValue = multiply(BigInt(byte & 0x7f), multiplier); + const multiplier = this.BigInt(2) ** this.BigInt(shift); + const thisByteValue = this.BigInt(byte & 0x7f) * multiplier; shift += 7; - res = add(res, thisByteValue); + res = res + thisByteValue; } while (byte >= 0x80); return { diff --git a/setting/index.js b/setting/index.js index d9276b2..e73daf1 100644 --- a/setting/index.js +++ b/setting/index.js @@ -5,7 +5,6 @@ let _props = null; AppSettingsPage({ build(props) { _props = props; - const storage = JSON.parse( props.settingsStorage.getItem("TOTPs") ?? "[]" ); diff --git a/setting/utils/queryParser.js b/setting/utils/queryParser.js index 2bd7953..9bb52f0 100644 --- a/setting/utils/queryParser.js +++ b/setting/utils/queryParser.js @@ -62,11 +62,12 @@ function getByOtpauthScheme(link){ } function getByGoogleMigrationScheme(link){ - console.log("Hello") - let data = link.split("data=")[1]; //Returns secret + + let data = link.split("data=")[1]; //Returns base64 encoded data data = decodeURIComponent(data); console.log(data) let decode = base64decode(data); console.log(decode) - console.log(decodeProto(decode)); + let proto = decodeProto(decode); + console.log(proto); } \ No newline at end of file From 21c7646c715318972878e6d0178e400c4944c465 Mon Sep 17 00:00:00 2001 From: Savely Savianok <1986developer@gmail.com> Date: Mon, 17 Mar 2025 15:30:03 +0300 Subject: [PATCH 7/7] feat: added google migration support (BETA) v.1.2.0 --- README.md | 15 ++++++-- docs/assets/image.png | Bin 0 -> 27369 bytes docs/assets/image2.png | Bin 0 -> 18910 bytes lib/protobuf-decoder/varintUtils.js | 1 - lib/totp-quickjs/base32decoder.js | 26 ++++++++++++++ page/index.js | 4 ++- setting/index.js | 14 ++++++-- setting/utils/queryParser.js | 52 ++++++++++++++++++++++++---- 8 files changed, 99 insertions(+), 13 deletions(-) create mode 100644 docs/assets/image.png create mode 100644 docs/assets/image2.png diff --git a/README.md b/README.md index 25f7017..e958a1d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,17 @@ # TOTPFIT ### Another 2FAuthenticator based on TOTP for Zepp Amazfit GTS 4 -Features: +![alt text](docs/assets/image2.png) + +### Features: - Supports of otpauth links with parameters "issuer", "algorithm", "digits", "period" -- Addition/Edition/Deletion of TOTPs from mobile settings app \ No newline at end of file +- Addition/Edition/Deletion of TOTPs from mobile settings app + +### Google Migration Support: +- Support of google migration links formated: ```otpauth-migration://offline?data=...``` (BETA) + +### Screenshots: + +![alt text](docs/assets/image2.png) + +![alt text](docs/assets/image.png) \ No newline at end of file diff --git a/docs/assets/image.png b/docs/assets/image.png new file mode 100644 index 0000000000000000000000000000000000000000..69a46edf3d5008220dc78061a025fde8a2066cab GIT binary patch literal 27369 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�*6kHlREO2Shc7$kui>RV!^0^?(&j zBe5Y2pLAWIX48VL37aiMjUn+KBvq^<|seAJqA$=^^0+N~Z}Ww7)r zV_JyodW{;Id0nApvvAWXyN*u^#`_11<6Jj38yp&hP+QB##QBmbwo<aEd587hgF~)CyDc|Lw=yRjteek(7!wA^P^oG3d90RyK zn8s#2x7#KUl47oQs_{&50tvjA^yYcQ{r?n|zcqeL5Gq#nSTXQE0K2zl%n`#YuK>FJ zXJu%cMmOd&J)9*d3vWR-;MRqCEmp%i^$a&s%hK9(!LpyKzvCp`d0JN>!Qm+j-NK>L zl{KPmU~l!GqHF6+!K2ksT`JS;Px#lOYD%i1VEBmmCMkw|>X|7<6rTTg(~l&9XE-}8 z$iwol0B6VUvY&^!km1b9Y>up}WuaH$N8rM%JJM-^5zgTFitEGQ;PrCjAD0ao8(lj% zZjD+he8Dl{qwsQLNC2C1<(~V^m_FAOrVTEm=^~7ovUW~dBQL+O?6VWwM%s8?QB&esTT%`|B(_f6s|7{Qba+k;h$XiN>ZNejjz+Bo`+(4HmY^nky=| z#O5E-%Kf`$Q}>nqYd0+~)ybCzE<6t6!d!T)A;Ws@;TMztjG-HTrUVNfQfg+OIaTe1 zk%Gj6MQlqtBm^XZU~d#!|=b%-?xD!z{c~7hw(N(b|>2y&#eD{`#Nyb&5sE+lg^+2|N8vo z@X0f@v;J3^|N5}7q7qm)+SmRP+53O@_luyjLx6jB)aEa9)_Oks|3}|Xdb5F30>?J) z$<_H=CK9Xrb&x&UPi6-l{{tpWJHL)Bd!5L#EKFBstq)^^YE!{pVj@3ftQ_ z=l8S6zQK0ql?uK$Z!)|Qn*MwXsKO|cPCVwHF+sfQc>8t!W9uE$e(Emm(n$?+1CDqf zIpkcG@uTEAj~2Tm-x-CHn}w{#aR(TRqIQZuzxe6k+1ni^y`a%ouSwcm0@4M>v8FS( zWyv+aOgJt(W2H${^m*OYxrPj&vtdFG#K<3LDDtXWu6fH(cxT2m;4;tW%i8g8^pEB)|h)`cmFz{cJ#=g8%D(E?IC&i4Sv>~98KYfI-kwBd2^5@2I> z$>p3=8hqv^eL#CYdoC7qDp{%l^zpNaTHF1Aqx3UYeNP4s7aLkf39`6y0vYi>j zKC>!5d$ee`d9o02>z}Y(e^jeelf#q+SAf1QnQz5cW9Zo7C@{rJ2)Gp1X;sJbwD6@H z6c|n|&RhX(5!)8rSiP)EXDLhLm5V=*7k2_fuY@D#)6wI&KPi6!C#eL@czc2p(!N(W5eA!#S m{nL9@;55m`4=xw~vmfT#_37Z02p!;=Obni`elF{r5}E)K%*)yU literal 0 HcmV?d00001 diff --git a/lib/protobuf-decoder/varintUtils.js b/lib/protobuf-decoder/varintUtils.js index fbd69a2..6b9b6b4 100644 --- a/lib/protobuf-decoder/varintUtils.js +++ b/lib/protobuf-decoder/varintUtils.js @@ -9,7 +9,6 @@ export function decodeVarint(buffer, offset) { } byte = buffer[offset++]; - console.log(this) const multiplier = this.BigInt(2) ** this.BigInt(shift); const thisByteValue = this.BigInt(byte & 0x7f) * multiplier; diff --git a/lib/totp-quickjs/base32decoder.js b/lib/totp-quickjs/base32decoder.js index 821b33c..784e2b1 100644 --- a/lib/totp-quickjs/base32decoder.js +++ b/lib/totp-quickjs/base32decoder.js @@ -25,6 +25,32 @@ function leftpad(str, len, pad) { ); } +export function encode(bytes) { + const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; + let bits = 0; + let value = 0; + let output = ''; + + for (let i = 0; i < bytes.length; i++) { + value = (value << 8) | bytes[i]; + bits += 8; + + while (bits >= 5) { + output += alphabet[(value >>> (bits - 5)) & 0x1F]; + bits -= 5; + } + } + + if (bits > 0) { + output += alphabet[(value << (5 - bits)) & 0x1F]; + } + + const paddingLength = (8 - (output.length % 8)) % 8; + output += '='.repeat(paddingLength); + + return output; +} + export function base64decode(base64) { const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; let result = []; diff --git a/page/index.js b/page/index.js index 776ff07..c60a457 100644 --- a/page/index.js +++ b/page/index.js @@ -6,7 +6,6 @@ import { LocalStorage } from "@zos/storage"; const app = getApp(); let waitForFetch = true; -let localStorage = new LocalStorage(); Page( BasePage({ @@ -14,6 +13,8 @@ Page( this.getTOTPData() .then((x) => { app._options.globalData.TOTPS = JSON.parse(x) ?? []; + + let localStorage = new LocalStorage(); localStorage.setItem( "TOTPs", JSON.stringify(app._options.globalData.TOTPS) @@ -22,6 +23,7 @@ Page( }) .catch((x) => { console.log(`Init failed: ${x}`); + let localStorage = new LocalStorage(); app._options.globalData.TOTPS = JSON.parse( localStorage.getItem("TOTPs", null) ?? [] ); diff --git a/setting/index.js b/setting/index.js index e73daf1..03e27a1 100644 --- a/setting/index.js +++ b/setting/index.js @@ -13,12 +13,16 @@ AppSettingsPage({ placeholder: "otpauth://", label: "Add new OTP Link", onChange: (changes) => { - var link = getTOTPByLink(changes); + let link = getTOTPByLink(changes); if (link == null) { console.log("link is invalid"); return; } - storage.push(link); + + if(Array.isArray(link)) + storage.push(...link); + else storage.push(link); + updateStorage(storage); }, labelStyle: { @@ -80,7 +84,11 @@ function GetTOTPList(storage) { label: "Change OTP link", onChange: (changes) => { try { - storage[elementId] = getTOTPByLink(changes); + let link = getTOTPByLink(changes); + if(Array.isArray(link)) + return; + + storage[elementId] = link; updateStorage(storage); } catch (err) { console.log(err); diff --git a/setting/utils/queryParser.js b/setting/utils/queryParser.js index 9bb52f0..1a27e22 100644 --- a/setting/utils/queryParser.js +++ b/setting/utils/queryParser.js @@ -1,6 +1,6 @@ -import { decodeProto } from "../../lib/protobuf-decoder/protobufDecoder"; +import { decodeProto, TYPES } from "../../lib/protobuf-decoder/protobufDecoder"; import { TOTP } from "../../lib/totp-quickjs"; -import { base64decode } from "../../lib/totp-quickjs/base32decoder"; +import { base64decode, encode } from "../../lib/totp-quickjs/base32decoder"; const otpauthScheme = "otpauth:/"; const googleMigrationScheme = "otpauth-migration:/"; @@ -40,7 +40,7 @@ function getByOtpauthScheme(link){ if (secret === undefined) throw new Error("Secret not defined"); if(issuer == client){ - issuer = args[3].split("issuer=")[1]?.split("&")[0] + issuer = args[3].split("issuer=")[1]?.split("&")[0]; } issuer = decodeURIComponent(issuer); @@ -65,9 +65,49 @@ function getByGoogleMigrationScheme(link){ let data = link.split("data=")[1]; //Returns base64 encoded data data = decodeURIComponent(data); - console.log(data) let decode = base64decode(data); - console.log(decode) let proto = decodeProto(decode); - console.log(proto); + + let protoTotps = []; + + proto.parts.forEach(part => { + if(part.type == TYPES.LENDELIM){ + protoTotps.push(decodeProto(part.value)); + } + }); + + let totps = []; + protoTotps.forEach(x => { + let type = x.parts.filter(x => x.index == 6)[0]; //find type of OTP + if(type.value !== '2'){ + console.log("ERR: it's a not TOTP record") + return; + } + let secret = x.parts.filter(x => x.index == 1)[0].value; + secret = encode(secret); + + let name = bytesToString(x.parts.filter(x => x.index == 2)[0].value); + let issuer = bytesToString(x.parts.filter(x => x.index == 3)[0].value); + + totps.push(new TOTP( + secret, + issuer, + name, + 6, + 30, + 0, + "SHA-1" + )); + }); + + return totps; + +} + +function bytesToString(bytes) { + let str = ''; + for (let i = 0; i < bytes.length; i++) { + str += String.fromCharCode(bytes[i]); + } + return str; } \ No newline at end of file