From 635b2382e7976e6f366a2e62ee405d116dd91625 Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Thu, 29 Apr 2021 23:36:35 +0400 Subject: [PATCH] Fix 2FA again Changed SHA from lib to browser's crypto --- babel.config.js | 12 +++++++++-- jest.setup.js | 8 +++++-- package-lock.json | 6 ++++++ package.json | 1 + src/helpers/bytes.ts | 4 +++- src/lib/crypto/crypto_methods.ts | 20 ++++++++++------- src/lib/crypto/crypto_utils.ts | 37 +++++++++++++++++++++----------- src/lib/crypto/srp.ts | 8 +++---- src/lib/mtproto/authorizer.ts | 2 +- src/lib/mtproto/networker.ts | 2 +- src/lib/storage.ts | 14 ++++++------ src/tests/crypto_methods.test.ts | 32 ++++++++++++++++++++++++--- 12 files changed, 103 insertions(+), 43 deletions(-) diff --git a/babel.config.js b/babel.config.js index 16a61f65..a5719b3c 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,4 +1,4 @@ -const config2 = { +/* const config2 = { "presets": [ "@babel/preset-typescript", @@ -38,4 +38,12 @@ const config3 = { ] }; -module.exports = config2; \ No newline at end of file +module.exports = config2; */ + +module.exports = { + presets: [ + ['@babel/preset-env', {targets: {node: 'current'}}], + '@babel/preset-typescript', + ]/* , + plugins: ["@babel/plugin-syntax-dynamic-import"] */ +}; diff --git a/jest.setup.js b/jest.setup.js index 142a1b9c..a66d947a 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -1,3 +1,7 @@ -import { Crypto } from "@peculiar/webcrypto"; +const webCrypto = require('@peculiar/webcrypto'); +const textEncoding = require('text-encoding'); -window.crypto = new Crypto(); \ No newline at end of file +window.crypto = new webCrypto.Crypto(); +window.TextEncoder = textEncoding.TextEncoder; + +const a = 1; diff --git a/package-lock.json b/package-lock.json index 8c048db4..f3a06233 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18422,6 +18422,12 @@ } } }, + "text-encoding": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.7.0.tgz", + "integrity": "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==", + "dev": true + }, "throat": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", diff --git a/package.json b/package.json index 1b4a470f..b84f1908 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "serviceworker-webpack-plugin": "^1.0.1", "style-loader": "^1.3.0", "terser-webpack-plugin": "^3.1.0", + "text-encoding": "^0.7.0", "ts-jest": "^24.3.0", "ts-loader": "^6.2.2", "typescript": "^3.9.7", diff --git a/src/helpers/bytes.ts b/src/helpers/bytes.ts index 25549cf7..094eaac7 100644 --- a/src/helpers/bytes.ts +++ b/src/helpers/bytes.ts @@ -112,9 +112,11 @@ export function convertToArrayBuffer(bytes: any | ArrayBuffer | Uint8Array) { return bytesToArrayBuffer(bytes); } -export function convertToUint8Array(bytes: Uint8Array | number[]): Uint8Array { +export function convertToUint8Array(bytes: Uint8Array | ArrayBuffer | number[] | string): Uint8Array { if((bytes as Uint8Array).buffer !== undefined) { return bytes as Uint8Array; + } else if(typeof(bytes) === 'string') { + return new TextEncoder().encode(bytes); } return new Uint8Array(bytes); diff --git a/src/lib/crypto/crypto_methods.ts b/src/lib/crypto/crypto_methods.ts index 4d4a0525..327d2d79 100644 --- a/src/lib/crypto/crypto_methods.ts +++ b/src/lib/crypto/crypto_methods.ts @@ -1,15 +1,15 @@ import { convertToArrayBuffer } from "../../helpers/bytes"; import type { InputCheckPasswordSRP } from "../../layer"; -import type { aesEncryptSync } from "./crypto_utils"; +import { aesEncryptSync, aesDecryptSync, sha256HashSync, sha1HashSync, bytesModPow } from "./crypto_utils"; export default abstract class CryptoWorkerMethods { abstract performTaskWorker(task: string, ...args: any[]): Promise; - public sha1Hash(bytes: number[] | ArrayBuffer | Uint8Array): Promise { + public sha1Hash(bytes: Parameters[0]): Promise { return this.performTaskWorker('sha1-hash', bytes); } - public sha256Hash(bytes: any) { + public sha256Hash(bytes: Parameters[0]) { return this.performTaskWorker('sha256-hash', bytes); } @@ -17,12 +17,16 @@ export default abstract class CryptoWorkerMethods { return this.performTaskWorker('pbkdf2', buffer, salt, iterations); } - public aesEncrypt(bytes: any, keyBytes: any, ivBytes: any) { + public aesEncrypt(bytes: Parameters[0], + keyBytes: Parameters[1], + ivBytes: Parameters[2]) { return this.performTaskWorker>('aes-encrypt', convertToArrayBuffer(bytes), convertToArrayBuffer(keyBytes), convertToArrayBuffer(ivBytes)); } - public aesDecrypt(encryptedBytes: any, keyBytes: any, ivBytes: any): Promise { + public aesDecrypt(encryptedBytes: Parameters[0], + keyBytes: Parameters[1], + ivBytes: Parameters[2]): Promise { return this.performTaskWorker('aes-decrypt', encryptedBytes, keyBytes, ivBytes) .then(bytes => convertToArrayBuffer(bytes)); @@ -36,8 +40,8 @@ export default abstract class CryptoWorkerMethods { return this.performTaskWorker<[number[], number[], number]>('factorize', [...bytes]); } - public modPow(x: any, y: any, m: any) { - return this.performTaskWorker('mod-pow', x, y, m); + public modPow(x: Parameters[0], y: Parameters[1], m: Parameters[2]) { + return this.performTaskWorker>('mod-pow', x, y, m); } public gzipUncompress(bytes: ArrayBuffer, toString?: boolean) { @@ -47,4 +51,4 @@ export default abstract class CryptoWorkerMethods { public computeSRP(password: string, state: any, isNew = false): Promise { return this.performTaskWorker('computeSRP', password, state, isNew); } -} \ No newline at end of file +} diff --git a/src/lib/crypto/crypto_utils.ts b/src/lib/crypto/crypto_utils.ts index bdcf0e28..73d9ec75 100644 --- a/src/lib/crypto/crypto_utils.ts +++ b/src/lib/crypto/crypto_utils.ts @@ -9,8 +9,8 @@ * https://github.com/zhukov/webogram/blob/master/LICENSE */ -import sha1 from '@cryptography/sha1'; -import sha256 from '@cryptography/sha256'; +//import sha1 from '@cryptography/sha1'; +//import sha256 from '@cryptography/sha256'; import {IGE} from '@cryptography/aes'; // @ts-ignore @@ -21,9 +21,11 @@ import {str2bigInt, bpe, equalsInt, greater, divide_, one, bigInt2str, powMod, bigInt2bytes} from '../../vendor/leemon';//from 'leemon'; import { addPadding } from '../mtproto/bin_utils'; -import { bytesToWordss, bytesFromWordss, bytesToHex, bytesFromHex } from '../../helpers/bytes'; +import { bytesToWordss, bytesFromWordss, bytesToHex, bytesFromHex, convertToUint8Array } from '../../helpers/bytes'; import { nextRandomInt } from '../../helpers/random'; +const subtle = typeof(window) !== 'undefined' && 'crypto' in window ? window.crypto.subtle : self.crypto.subtle; + export function longToBytes(sLong: string): Array { /* let perf = performance.now(); for(let i = 0; i < 1000000; ++i) { @@ -45,8 +47,11 @@ export function longToBytes(sLong: string): Array { return bytes; } -export function sha1HashSync(bytes: number[] | ArrayBuffer | Uint8Array) { - //console.trace(dT(), 'SHA-1 hash start', bytes); +export function sha1HashSync(bytes: Uint8Array | ArrayBuffer | string) { + return subtle.digest('SHA-1', convertToUint8Array(bytes)).then(b => { + return new Uint8Array(b); + }); + /* //console.trace(dT(), 'SHA-1 hash start', bytes); const hashBytes: number[] = []; @@ -58,18 +63,27 @@ export function sha1HashSync(bytes: number[] | ArrayBuffer | Uint8Array) { //console.log(dT(), 'SHA-1 hash finish', hashBytes, bytesToHex(hashBytes)); - return new Uint8Array(hashBytes); + return new Uint8Array(hashBytes); */ } export function sha256HashSync(bytes: Uint8Array | ArrayBuffer | string) { - //console.log(dT(), 'SHA-256 hash start'); + return subtle.digest('SHA-256', convertToUint8Array(bytes)).then(b => { + //console.log('legacy', performance.now() - perfS); + return new Uint8Array(b); + }); + /* //console.log('SHA-256 hash start'); + + let perfS = performance.now(); + - let words = typeof(bytes) === 'string' ? bytes : bytesToWordss(bytes); + let perfD = performance.now(); + let words = typeof(bytes) === 'string' ? bytes : bytesToWordss(bytes as any); let hash = sha256(words); + console.log('darutkin', performance.now() - perfD); - //console.log(dT(), 'SHA-256 hash finish', hash); + //console.log('SHA-256 hash finish', hash, sha256(words, 'hex')); - return bytesFromWordss(hash); + return bytesFromWordss(hash); */ } export function aesEncryptSync(bytes: ArrayBuffer, keyBytes: ArrayBuffer, ivBytes: ArrayBuffer) { @@ -114,7 +128,6 @@ export function rsaEncrypt(publicKey: {modulus: string, exponent: string}, bytes } export async function hash_pbkdf2(/* hasher: 'string', */buffer: any, salt: any, iterations: number) { - let subtle = typeof(window) !== 'undefined' && 'crypto' in window ? window.crypto.subtle : self.crypto.subtle; // @ts-ignore let importKey = await subtle.importKey( "raw", //only "raw" is allowed @@ -252,7 +265,7 @@ export function pqPrimeLeemon(what: any) { return [bigInt2bytes(P), bigInt2bytes(Q), it]; } -export function bytesModPow(x: any, y: any, m: any) { +export function bytesModPow(x: number[] | Uint8Array, y: number[] | Uint8Array, m: number[] | Uint8Array) { try { var xBigInt = str2bigInt(bytesToHex(x), 16); var yBigInt = str2bigInt(bytesToHex(y), 16); diff --git a/src/lib/crypto/srp.ts b/src/lib/crypto/srp.ts index e0755def..2b3561a7 100644 --- a/src/lib/crypto/srp.ts +++ b/src/lib/crypto/srp.ts @@ -12,10 +12,8 @@ const log = logger('SRP', LogTypes.Error); //MOUNT_CLASS_TO && Object.assign(MOUNT_CLASS_TO, {str2bigInt, bigInt2str, int2bigInt}); export async function makePasswordHash(password: string, client_salt: Uint8Array, server_salt: Uint8Array): Promise { - let clientSaltString = ''; - for(let i = 0; i < client_salt.length; i++) clientSaltString += String.fromCharCode(client_salt[i]); - - let buffer: any = await CryptoWorker.sha256Hash(clientSaltString + password + clientSaltString); + // ! look into crypto_methods.test.ts + let buffer: any = await CryptoWorker.sha256Hash(bufferConcats(client_salt, new TextEncoder().encode(password), client_salt)); //log('encoded 1', bytesToHex(new Uint8Array(buffer))); buffer = bufferConcats(server_salt, buffer, server_salt); @@ -71,7 +69,7 @@ export async function computeSRP(password: string, state: AccountPassword, isNew //log('computed pw_hash:', pw_hash, x, bytesToHex(new Uint8Array(pw_hash))); const padArray = function(arr: any[], len: number, fill = 0) { - return Array(len).fill(fill).concat(arr).slice(-len); + return new Uint8Array(Array(len).fill(fill).concat(arr).slice(-len)); }; const pForHash = padArray(bytesFromHex(bigInt2str(p, 16)), 256); diff --git a/src/lib/mtproto/authorizer.ts b/src/lib/mtproto/authorizer.ts index 44ab6598..76596e04 100644 --- a/src/lib/mtproto/authorizer.ts +++ b/src/lib/mtproto/authorizer.ts @@ -527,7 +527,7 @@ export class Authorizer { } //var authKeyHash = sha1BytesSync(authKey), - let authKeyHash = await CryptoWorker.sha1Hash(authKey), + let authKeyHash = await CryptoWorker.sha1Hash(new Uint8Array(authKey)), authKeyAux = authKeyHash.slice(0, 8), authKeyId = authKeyHash.slice(-8); diff --git a/src/lib/mtproto/networker.ts b/src/lib/mtproto/networker.ts index 34cac3f6..579f85df 100644 --- a/src/lib/mtproto/networker.ts +++ b/src/lib/mtproto/networker.ts @@ -986,7 +986,7 @@ export default class MTPNetworker { }; } - public getDecryptedMessage(msgKey: Uint8Array | number[], encryptedData: Uint8Array | number[]): Promise { + public getDecryptedMessage(msgKey: Uint8Array, encryptedData: Uint8Array): Promise { // this.log('get decrypted start') return this.getAesKeyIv(msgKey, false).then((keyIv) => { // this.log('after msg key iv') diff --git a/src/lib/storage.ts b/src/lib/storage.ts index ac33c8a1..77c0a746 100644 --- a/src/lib/storage.ts +++ b/src/lib/storage.ts @@ -27,7 +27,7 @@ export default class AppStorage/* Storage ex private keysToSet: Set = new Set(); private saveThrottled: () => void; - private saveResolve: () => void; + private saveDeferred = deferredPromise(); constructor(storageOptions: Omit & {stores?: DatabaseStore[], storeName: DatabaseStoreName}) { this.storage = new IDBStorage(storageOptions); @@ -35,6 +35,9 @@ export default class AppStorage/* Storage ex AppStorage.STORAGES.push(this); this.saveThrottled = throttle(async() => { + const deferred = this.saveDeferred; + this.saveDeferred = deferredPromise(); + if(this.keysToSet.size) { const keys = Array.from(this.keysToSet.values()) as string[]; this.keysToSet.clear(); @@ -51,10 +54,7 @@ export default class AppStorage/* Storage ex } } - if(this.saveResolve) { - this.saveResolve(); - this.saveResolve = undefined; - } + deferred.resolve(); }, 16, false); this.getThrottled = throttle(async() => { @@ -151,9 +151,7 @@ export default class AppStorage/* Storage ex } } - return new Promise((resolve) => { - this.saveResolve = resolve; - }); + return this.saveDeferred; } public async delete(key: keyof Storage, saveLocal = false) { diff --git a/src/tests/crypto_methods.test.ts b/src/tests/crypto_methods.test.ts index 92b28d31..d072556c 100644 --- a/src/tests/crypto_methods.test.ts +++ b/src/tests/crypto_methods.test.ts @@ -1,4 +1,4 @@ -import { bytesFromArrayBuffer, bytesFromHex } from '../helpers/bytes'; +import { bytesFromArrayBuffer, bytesFromHex, bytesToHex } from '../helpers/bytes'; import CryptoWorker from '../lib/crypto/cryptoworker'; test('factorize', () => { @@ -11,7 +11,8 @@ test('factorize', () => { }); test('sha1', () => { - CryptoWorker.sha1Hash(bytesFromHex('ec5ac983081eeb1da706316227000000044af6cfb1000000046995dd57000000d55105998729349339eb322d86ec13bc0884f6ba0449d8ecbad0ef574837422579a11a88591796cdcc4c05690da0652462489286450179a635924bcc0ab83848')) + const bytes = new Uint8Array(bytesFromHex('ec5ac983081eeb1da706316227000000044af6cfb1000000046995dd57000000d55105998729349339eb322d86ec13bc0884f6ba0449d8ecbad0ef574837422579a11a88591796cdcc4c05690da0652462489286450179a635924bcc0ab83848')); + CryptoWorker.sha1Hash(bytes) .then(buffer => { //console.log(bytesFromArrayBuffer(buffer)); @@ -26,9 +27,34 @@ test('sha1', () => { }); test('sha256', () => { - CryptoWorker.sha256Hash(new Uint8Array([112, 20, 211, 20, 106, 249, 203, 252, 39, 107, 106, 194, 63, 60, 13, 130, 51, 78, 107, 6, 110, 156, 214, 65, 205, 10, 30, 150, 79, 10, 145, 194, 232, 240, 127, 55, 146, 103, 248, 227, 160, 172, 30, 153, 122, 189, 110, 162, 33, 86, 174, 117])).then(bytes => { + CryptoWorker.sha256Hash(new Uint8Array([112, 20, 211, 20, 106, 249, 203, 252, 39, 107, 106, 194, 63, 60, 13, 130, 51, 78, 107, 6, 110, 156, 214, 65, 205, 10, 30, 150, 79, 10, 145, 194, 232, 240, 127, 55, 146, 103, 248, 227, 160, 172, 30, 153, 122, 189, 110, 162, 33, 86, 174, 117])) + .then(bytes => { expect(bytes).toEqual(new Uint8Array([158, 59, 39, 247, 130, 244, 235, 160, 16, 249, 34, 114, 67, 171, 203, 208, 187, 72, 217, 106, 253, 62, 195, 242, 52, 118, 99, 72, 221, 29, 203, 95])); }); + + const client_salt = new Uint8Array([58, 45, 208, 42, 210, 96, 229, 224, 220, 241, 61, 180, 91, 93, 132, 127, 29, 81, 244, 35, 114, 240, 134, 109, 60, 129, 157, 117, 214, 173, 161, 93, 61, 215, 199, 129, 184, 20, 247, 52]); + + // ! ! ! ! ! ! ! ! ! ! THIS IS WRONG WAY TO ENCODE AND CONCAT THEM AFTER ! ! ! ! ! ! ! ! ! ! ! ! ! + /* let clientSaltString = ''; + for(let i = 0; i < client_salt.length; i++) clientSaltString += String.fromCharCode(client_salt[i]); */ + + const payload = [ + ['£', 'b4fe151e413445357b1c0935e7cf04a429492ebd23dc62bfadb2f898c431c1fd'], + ['haha', '090b235e9eb8f197f2dd927937222c570396d971222d9009a9189e2b6cc0a2c1'], + ['😂😘❤️😍😊😁👁👍🏿', 'f3cd34d2345934e10d95d01c7eae9040a6f3c4e20a02a392078b762d876ece8a'], + ['$', '09fc96082d34c2dfc1295d92073b5ea1dc8ef8da95f14dfded011ffb96d3e54b'], + //[clientSaltString + '😂😘❤️😍😊😁👁👍🏿' + clientSaltString, 'c2ac294f00e8ac4db6b94099f2014d763315cb2127b1e1ea178cfc3f302680d0'], + [new Uint8Array(Array.from(client_salt).concat(Array.from(new TextEncoder().encode('😂😘❤️😍😊😁👁👍🏿')), Array.from(client_salt))), 'f11950fb40baf391b06a57e7490c8ad4d99ec0c1516c2bc7e529895296616ea7'] + ]; + + payload.forEach(pair => { + //const uint8 = new TextEncoder().encode(pair[0]); + //CryptoWorker.sha256Hash(new Uint8Array(pair[0].split('').map(c => c.charCodeAt(0)))).then(bytes => { + CryptoWorker.sha256Hash(pair[0]).then(bytes => { + const hex = bytesToHex(bytes); + expect(hex).toEqual(pair[1]); + }); + }); }); test('pbkdf2', () => {