Browse Source

Fix 2FA again

Changed SHA from lib to browser's crypto
master
Eduard Kuzmenko 3 years ago
parent
commit
635b2382e7
  1. 12
      babel.config.js
  2. 8
      jest.setup.js
  3. 6
      package-lock.json
  4. 1
      package.json
  5. 4
      src/helpers/bytes.ts
  6. 20
      src/lib/crypto/crypto_methods.ts
  7. 37
      src/lib/crypto/crypto_utils.ts
  8. 8
      src/lib/crypto/srp.ts
  9. 2
      src/lib/mtproto/authorizer.ts
  10. 2
      src/lib/mtproto/networker.ts
  11. 14
      src/lib/storage.ts
  12. 32
      src/tests/crypto_methods.test.ts

12
babel.config.js

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
const config2 = {
/* const config2 = {
"presets": [
"@babel/preset-typescript",
@ -38,4 +38,12 @@ const config3 = { @@ -38,4 +38,12 @@ const config3 = {
]
};
module.exports = config2;
module.exports = config2; */
module.exports = {
presets: [
['@babel/preset-env', {targets: {node: 'current'}}],
'@babel/preset-typescript',
]/* ,
plugins: ["@babel/plugin-syntax-dynamic-import"] */
};

8
jest.setup.js

@ -1,3 +1,7 @@ @@ -1,3 +1,7 @@
import { Crypto } from "@peculiar/webcrypto";
const webCrypto = require('@peculiar/webcrypto');
const textEncoding = require('text-encoding');
window.crypto = new Crypto();
window.crypto = new webCrypto.Crypto();
window.TextEncoder = textEncoding.TextEncoder;
const a = 1;

6
package-lock.json generated

@ -18422,6 +18422,12 @@ @@ -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",

1
package.json

@ -71,6 +71,7 @@ @@ -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",

4
src/helpers/bytes.ts

@ -112,9 +112,11 @@ export function convertToArrayBuffer(bytes: any | ArrayBuffer | Uint8Array) { @@ -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);

20
src/lib/crypto/crypto_methods.ts

@ -1,15 +1,15 @@ @@ -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<T>(task: string, ...args: any[]): Promise<T>;
public sha1Hash(bytes: number[] | ArrayBuffer | Uint8Array): Promise<Uint8Array> {
public sha1Hash(bytes: Parameters<typeof sha1HashSync>[0]): Promise<Uint8Array> {
return this.performTaskWorker<Uint8Array>('sha1-hash', bytes);
}
public sha256Hash(bytes: any) {
public sha256Hash(bytes: Parameters<typeof sha256HashSync>[0]) {
return this.performTaskWorker<number[]>('sha256-hash', bytes);
}
@ -17,12 +17,16 @@ export default abstract class CryptoWorkerMethods { @@ -17,12 +17,16 @@ export default abstract class CryptoWorkerMethods {
return this.performTaskWorker<ArrayBuffer>('pbkdf2', buffer, salt, iterations);
}
public aesEncrypt(bytes: any, keyBytes: any, ivBytes: any) {
public aesEncrypt(bytes: Parameters<typeof aesEncryptSync>[0],
keyBytes: Parameters<typeof aesEncryptSync>[1],
ivBytes: Parameters<typeof aesEncryptSync>[2]) {
return this.performTaskWorker<ReturnType<typeof aesEncryptSync>>('aes-encrypt', convertToArrayBuffer(bytes),
convertToArrayBuffer(keyBytes), convertToArrayBuffer(ivBytes));
}
public aesDecrypt(encryptedBytes: any, keyBytes: any, ivBytes: any): Promise<ArrayBuffer> {
public aesDecrypt(encryptedBytes: Parameters<typeof aesDecryptSync>[0],
keyBytes: Parameters<typeof aesDecryptSync>[1],
ivBytes: Parameters<typeof aesDecryptSync>[2]): Promise<ArrayBuffer> {
return this.performTaskWorker<ArrayBuffer>('aes-decrypt',
encryptedBytes, keyBytes, ivBytes)
.then(bytes => convertToArrayBuffer(bytes));
@ -36,8 +40,8 @@ export default abstract class CryptoWorkerMethods { @@ -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<number[]>('mod-pow', x, y, m);
public modPow(x: Parameters<typeof bytesModPow>[0], y: Parameters<typeof bytesModPow>[1], m: Parameters<typeof bytesModPow>[2]) {
return this.performTaskWorker<ReturnType<typeof bytesModPow>>('mod-pow', x, y, m);
}
public gzipUncompress<T>(bytes: ArrayBuffer, toString?: boolean) {
@ -47,4 +51,4 @@ export default abstract class CryptoWorkerMethods { @@ -47,4 +51,4 @@ export default abstract class CryptoWorkerMethods {
public computeSRP(password: string, state: any, isNew = false): Promise<InputCheckPasswordSRP> {
return this.performTaskWorker('computeSRP', password, state, isNew);
}
}
}

37
src/lib/crypto/crypto_utils.ts

@ -9,8 +9,8 @@ @@ -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, @@ -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<number> {
/* let perf = performance.now();
for(let i = 0; i < 1000000; ++i) {
@ -45,8 +47,11 @@ export function longToBytes(sLong: string): Array<number> { @@ -45,8 +47,11 @@ export function longToBytes(sLong: string): Array<number> {
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) { @@ -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 @@ -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) { @@ -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);

8
src/lib/crypto/srp.ts

@ -12,10 +12,8 @@ const log = logger('SRP', LogTypes.Error); @@ -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<number[]> {
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 @@ -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);

2
src/lib/mtproto/authorizer.ts

@ -527,7 +527,7 @@ export class Authorizer { @@ -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);

2
src/lib/mtproto/networker.ts

@ -986,7 +986,7 @@ export default class MTPNetworker { @@ -986,7 +986,7 @@ export default class MTPNetworker {
};
}
public getDecryptedMessage(msgKey: Uint8Array | number[], encryptedData: Uint8Array | number[]): Promise<ArrayBuffer> {
public getDecryptedMessage(msgKey: Uint8Array, encryptedData: Uint8Array): Promise<ArrayBuffer> {
// this.log('get decrypted start')
return this.getAesKeyIv(msgKey, false).then((keyIv) => {
// this.log('after msg key iv')

14
src/lib/storage.ts

@ -27,7 +27,7 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex @@ -27,7 +27,7 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex
private keysToSet: Set<keyof Storage> = new Set();
private saveThrottled: () => void;
private saveResolve: () => void;
private saveDeferred = deferredPromise<void>();
constructor(storageOptions: Omit<IDBOptions, 'storeName' | 'stores'> & {stores?: DatabaseStore[], storeName: DatabaseStoreName}) {
this.storage = new IDBStorage(storageOptions);
@ -35,6 +35,9 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex @@ -35,6 +35,9 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex
AppStorage.STORAGES.push(this);
this.saveThrottled = throttle(async() => {
const deferred = this.saveDeferred;
this.saveDeferred = deferredPromise<void>();
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 extends Record<string, any>/* Storage ex @@ -51,10 +54,7 @@ export default class AppStorage<Storage extends Record<string, any>/* 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 extends Record<string, any>/* Storage ex @@ -151,9 +151,7 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex
}
}
return new Promise<void>((resolve) => {
this.saveResolve = resolve;
});
return this.saveDeferred;
}
public async delete(key: keyof Storage, saveLocal = false) {

32
src/tests/crypto_methods.test.ts

@ -1,4 +1,4 @@ @@ -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', () => { @@ -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', () => { @@ -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', () => {

Loading…
Cancel
Save