diff --git a/.eslintignore b/.eslintignore index cbcaffc1..b5dc9cf0 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,6 +1,7 @@ dist build build-cli +coverage webpack.cli.js webpack.main.js webpack.renderer.js diff --git a/jslib/common/src/misc/nodeUtils.ts b/jslib/common/src/misc/nodeUtils.ts index 5b5ba27b..44cbec4b 100644 --- a/jslib/common/src/misc/nodeUtils.ts +++ b/jslib/common/src/misc/nodeUtils.ts @@ -29,6 +29,6 @@ export class NodeUtils { // https://stackoverflow.com/a/31394257 static bufferToArrayBuffer(buf: Buffer): ArrayBuffer { - return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); + return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer; } } diff --git a/jslib/common/src/misc/utils.ts b/jslib/common/src/misc/utils.ts index f65fe0d1..00aa62a1 100644 --- a/jslib/common/src/misc/utils.ts +++ b/jslib/common/src/misc/utils.ts @@ -1,11 +1,13 @@ /* eslint-disable no-useless-escape */ +import url from "url"; + import { I18nService } from "../abstractions/i18n.service"; import * as tldjs from "tldjs"; -const nodeURL = typeof window === "undefined" ? require("url") : null; +const nodeURL = typeof window === "undefined" ? url : null; -export class Utils { +class Utils { static inited = false; static isNode = false; static isBrowser = true; @@ -34,9 +36,11 @@ export class Utils { Utils.global = Utils.isNode && !Utils.isBrowser ? global : window; } - static fromB64ToArray(str: string): Uint8Array { + static fromB64ToArray(str: string): Uint8Array { if (Utils.isNode) { - return new Uint8Array(Buffer.from(str, "base64")); + const buffer = Buffer.from(str, "base64"); + + return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength) as Uint8Array; } else { const binaryString = window.atob(str); const bytes = new Uint8Array(binaryString.length); @@ -47,7 +51,7 @@ export class Utils { } } - static fromUrlB64ToArray(str: string): Uint8Array { + static fromUrlB64ToArray(str: string): Uint8Array { return Utils.fromB64ToArray(Utils.fromUrlB64ToB64(str)); } @@ -63,9 +67,11 @@ export class Utils { } } - static fromUtf8ToArray(str: string): Uint8Array { + static fromUtf8ToArray(str: string): Uint8Array { if (Utils.isNode) { - return new Uint8Array(Buffer.from(str, "utf8")); + const buffer = Buffer.from(str, "utf8"); + + return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength) as Uint8Array; } else { const strUtf8 = unescape(encodeURIComponent(str)); const arr = new Uint8Array(strUtf8.length); @@ -84,12 +90,12 @@ export class Utils { return arr; } - static fromBufferToB64(buffer: ArrayBuffer): string { + static fromBufferToB64(buffer: BufferSource): string { if (Utils.isNode) { - return Buffer.from(buffer).toString("base64"); + return Buffer.from(buffer as ArrayBuffer).toString("base64"); } else { let binary = ""; - const bytes = new Uint8Array(buffer); + const bytes = ArrayBuffer.isView(buffer) ? new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength) : new Uint8Array(buffer); for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); } @@ -97,7 +103,7 @@ export class Utils { } } - static fromBufferToUrlB64(buffer: ArrayBuffer): string { + static fromBufferToUrlB64(buffer: BufferSource): string { return Utils.fromB64toUrlB64(Utils.fromBufferToB64(buffer)); } @@ -247,7 +253,7 @@ export class Utils { const urlDomain = tldjs != null && tldjs.getDomain != null ? tldjs.getDomain(url.hostname) : null; return urlDomain != null ? urlDomain : url.hostname; - } catch (e) { + } catch { // Invalid domain, try another approach below. } } @@ -395,7 +401,7 @@ export class Utils { anchor.href = uriString; return anchor as any; } - } catch (e) { + } catch { // Ignore error } @@ -403,4 +409,6 @@ export class Utils { } } +export default Utils; + Utils.init(); diff --git a/jslib/common/src/models/domain/encString.ts b/jslib/common/src/models/domain/encString.ts index 87bd035a..13089412 100644 --- a/jslib/common/src/models/domain/encString.ts +++ b/jslib/common/src/models/domain/encString.ts @@ -1,6 +1,6 @@ import { CryptoService } from "../../abstractions/crypto.service"; import { EncryptionType } from "../../enums/encryptionType"; -import { Utils } from "../../misc/utils"; +import Utils from "../../misc/utils"; import { SymmetricCryptoKey } from "./symmetricCryptoKey"; @@ -53,7 +53,7 @@ export class EncString { try { this.encryptionType = parseInt(headerPieces[0], null); encPieces = headerPieces[1].split("|"); - } catch (e) { + } catch { return; } } else { @@ -114,7 +114,7 @@ export class EncString { key = await cryptoService.getOrgKey(orgId); } this.decryptedValue = await cryptoService.decryptToUtf8(this, key); - } catch (e) { + } catch { this.decryptedValue = "[error: cannot decrypt]"; } return this.decryptedValue; diff --git a/jslib/common/src/models/domain/symmetricCryptoKey.ts b/jslib/common/src/models/domain/symmetricCryptoKey.ts index a58dc6fd..c8cbfaf4 100644 --- a/jslib/common/src/models/domain/symmetricCryptoKey.ts +++ b/jslib/common/src/models/domain/symmetricCryptoKey.ts @@ -1,5 +1,5 @@ import { EncryptionType } from "../../enums/encryptionType"; -import { Utils } from "../../misc/utils"; +import Utils from "../../misc/utils"; export class SymmetricCryptoKey { key: ArrayBuffer; @@ -13,33 +13,35 @@ export class SymmetricCryptoKey { meta: any; - constructor(key: ArrayBuffer, encType?: EncryptionType) { + constructor(key: BufferSource, encType?: EncryptionType) { if (key == null) { throw new Error("Must provide key"); } + const keyBuffer = ArrayBuffer.isView(key) ? key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength) : key; + if (encType == null) { - if (key.byteLength === 32) { + if (keyBuffer.byteLength === 32) { encType = EncryptionType.AesCbc256_B64; - } else if (key.byteLength === 64) { + } else if (keyBuffer.byteLength === 64) { encType = EncryptionType.AesCbc256_HmacSha256_B64; } else { throw new Error("Unable to determine encType."); } } - this.key = key; + this.key = keyBuffer; this.encType = encType; - if (encType === EncryptionType.AesCbc256_B64 && key.byteLength === 32) { - this.encKey = key; + if (encType === EncryptionType.AesCbc256_B64 && keyBuffer.byteLength === 32) { + this.encKey = keyBuffer; this.macKey = null; - } else if (encType === EncryptionType.AesCbc128_HmacSha256_B64 && key.byteLength === 32) { - this.encKey = key.slice(0, 16); - this.macKey = key.slice(16, 32); - } else if (encType === EncryptionType.AesCbc256_HmacSha256_B64 && key.byteLength === 64) { - this.encKey = key.slice(0, 32); - this.macKey = key.slice(32, 64); + } else if (encType === EncryptionType.AesCbc128_HmacSha256_B64 && keyBuffer.byteLength === 32) { + this.encKey = keyBuffer.slice(0, 16); + this.macKey = keyBuffer.slice(16, 32); + } else if (encType === EncryptionType.AesCbc256_HmacSha256_B64 && keyBuffer.byteLength === 64) { + this.encKey = keyBuffer.slice(0, 32); + this.macKey = keyBuffer.slice(32, 64); } else { throw new Error("Unsupported encType/key length."); } diff --git a/jslib/common/src/models/request/identityToken/passwordTokenRequest.ts b/jslib/common/src/models/request/identityToken/passwordTokenRequest.ts index c40e053c..404462ad 100644 --- a/jslib/common/src/models/request/identityToken/passwordTokenRequest.ts +++ b/jslib/common/src/models/request/identityToken/passwordTokenRequest.ts @@ -1,5 +1,4 @@ import { ClientType } from "../../../enums/clientType"; -import { Utils } from "../../../misc/utils"; import { CaptchaProtectedRequest } from "../captchaProtectedRequest"; import { DeviceRequest } from "../deviceRequest"; diff --git a/jslib/common/src/services/crypto.service.ts b/jslib/common/src/services/crypto.service.ts index 28b13c5d..764c1c67 100644 --- a/jslib/common/src/services/crypto.service.ts +++ b/jslib/common/src/services/crypto.service.ts @@ -10,7 +10,7 @@ import { HashPurpose } from "../enums/hashPurpose"; import { KdfType } from "../enums/kdfType"; import { KeySuffixOptions } from "../enums/keySuffixOptions"; import { sequentialize } from "../misc/sequentialize"; -import { Utils } from "../misc/utils"; +import Utils from "../misc/utils"; import { EEFLongWordList } from "../misc/wordlist"; import { EncArrayBuffer } from "../models/domain/encArrayBuffer"; import { EncString } from "../models/domain/encString"; @@ -109,7 +109,7 @@ export class CryptoService implements CryptoServiceAbstraction { ): Promise { const key = await this.retrieveKeyFromStorage(keySuffix, userId); if (key != null) { - const symmetricKey = new SymmetricCryptoKey(Utils.fromB64ToArray(key).buffer); + const symmetricKey = new SymmetricCryptoKey(Utils.fromB64ToArray(key)); if (!(await this.validateKey(symmetricKey))) { this.logService.warning("Wrong key, throwing away stored key"); @@ -335,9 +335,11 @@ export class CryptoService implements CryptoServiceAbstraction { } async clearStoredKey(keySuffix: KeySuffixOptions) { - keySuffix === KeySuffixOptions.Auto - ? await this.stateService.setCryptoMasterKeyAuto(null) - : await this.stateService.setCryptoMasterKeyBiometric(null); + if (keySuffix === KeySuffixOptions.Auto) { + await this.stateService.setCryptoMasterKeyAuto(null); + } else { + await this.stateService.setCryptoMasterKeyBiometric(null); + } } async clearKeyHash(userId?: string): Promise { @@ -508,9 +510,9 @@ export class CryptoService implements CryptoServiceAbstraction { return Promise.resolve(null); } - let plainBuf: ArrayBuffer; + let plainBuf: BufferSource; if (typeof plainValue === "string") { - plainBuf = Utils.fromUtf8ToArray(plainValue).buffer; + plainBuf = Utils.fromUtf8ToArray(plainValue); } else { plainBuf = plainValue; } @@ -583,7 +585,8 @@ export class CryptoService implements CryptoServiceAbstraction { throw new Error("encPieces unavailable."); } - const data = Utils.fromB64ToArray(encPieces[0]).buffer; + const dataArray = Utils.fromB64ToArray(encPieces[0]); + const data = dataArray.buffer as ArrayBuffer; const privateKey = privateKeyValue ?? (await this.getPrivateKey()); if (privateKey == null) { throw new Error("No private key."); @@ -606,9 +609,12 @@ export class CryptoService implements CryptoServiceAbstraction { } async decryptToBytes(encString: EncString, key?: SymmetricCryptoKey): Promise { - const iv = Utils.fromB64ToArray(encString.iv).buffer; - const data = Utils.fromB64ToArray(encString.data).buffer; - const mac = encString.mac ? Utils.fromB64ToArray(encString.mac).buffer : null; + const ivArray = Utils.fromB64ToArray(encString.iv); + const iv = ivArray.buffer as ArrayBuffer; + const dataArray = Utils.fromB64ToArray(encString.data); + const data = dataArray.buffer as ArrayBuffer; + const macArray = encString.mac ? Utils.fromB64ToArray(encString.mac) : null; + const mac = macArray ? (macArray.buffer as ArrayBuffer) : null; const decipher = await this.aesDecryptToBytes(encString.encryptionType, data, iv, mac, key); if (decipher == null) { return null; @@ -665,9 +671,9 @@ export class CryptoService implements CryptoServiceAbstraction { return await this.aesDecryptToBytes( encType, - ctBytes.buffer, - ivBytes.buffer, - macBytes != null ? macBytes.buffer : null, + ctBytes.buffer as ArrayBuffer, + ivBytes.buffer as ArrayBuffer, + macBytes != null ? (macBytes.buffer as ArrayBuffer) : null, key, ); } @@ -717,7 +723,7 @@ export class CryptoService implements CryptoServiceAbstraction { const privateKey = await this.decryptToBytes(new EncString(encPrivateKey), encKey); await this.cryptoFunctionService.rsaExtractPublicKey(privateKey); - } catch (e) { + } catch { return false; } @@ -754,17 +760,24 @@ export class CryptoService implements CryptoServiceAbstraction { : await this.stateService.getCryptoMasterKeyBiometric({ userId: userId }); } - private async aesEncrypt(data: ArrayBuffer, key: SymmetricCryptoKey): Promise { + private async aesEncrypt(data: BufferSource, key: SymmetricCryptoKey): Promise { const obj = new EncryptedObject(); obj.key = await this.getKeyForEncryption(key); obj.iv = await this.cryptoFunctionService.randomBytes(16); - obj.data = await this.cryptoFunctionService.aesEncrypt(data, obj.iv, obj.key.encKey); + + const dataBuffer = ArrayBuffer.isView(data) + ? (data.byteOffset === 0 && data.byteLength === data.buffer.byteLength + ? data.buffer as ArrayBuffer + : data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer) + : data; + obj.data = await this.cryptoFunctionService.aesEncrypt(dataBuffer, obj.iv, obj.key.encKey); if (obj.key.macKey != null) { const macData = new Uint8Array(obj.iv.byteLength + obj.data.byteLength); macData.set(new Uint8Array(obj.iv), 0); macData.set(new Uint8Array(obj.data), obj.iv.byteLength); - obj.mac = await this.cryptoFunctionService.hmac(macData.buffer, obj.key.macKey, "sha256"); + + obj.mac = await this.cryptoFunctionService.hmac(macData.buffer as ArrayBuffer, obj.key.macKey, "sha256"); } return obj; @@ -830,7 +843,7 @@ export class CryptoService implements CryptoServiceAbstraction { macData.set(new Uint8Array(iv), 0); macData.set(new Uint8Array(data), iv.byteLength); const computedMac = await this.cryptoFunctionService.hmac( - macData.buffer, + macData.buffer as ArrayBuffer, theKey.macKey, "sha256", ); diff --git a/jslib/node/spec/services/nodeCryptoFunction.service.spec.ts b/jslib/node/spec/services/nodeCryptoFunction.service.spec.ts index 4272a74f..812bc92f 100644 --- a/jslib/node/spec/services/nodeCryptoFunction.service.spec.ts +++ b/jslib/node/spec/services/nodeCryptoFunction.service.spec.ts @@ -1,4 +1,4 @@ -import { Utils } from "@/jslib/common/src/misc/utils"; +import Utils from "@/jslib/common/src/misc/utils"; import { SymmetricCryptoKey } from "@/jslib/common/src/models/domain/symmetricCryptoKey"; import { NodeCryptoFunctionService } from "@/jslib/node/src/services/nodeCryptoFunction.service"; @@ -93,8 +93,9 @@ describe("NodeCrypto Function Service", () => { it("should fail with prk too small", async () => { const cryptoFunctionService = new NodeCryptoFunctionService(); + const prk = Utils.fromB64ToArray(prk16Byte); const f = cryptoFunctionService.hkdfExpand( - Utils.fromB64ToArray(prk16Byte), + prk.buffer as ArrayBuffer, "info", 32, "sha256", @@ -104,8 +105,9 @@ describe("NodeCrypto Function Service", () => { it("should fail with outputByteSize is too large", async () => { const cryptoFunctionService = new NodeCryptoFunctionService(); + const prk = Utils.fromB64ToArray(prk32Byte); const f = cryptoFunctionService.hkdfExpand( - Utils.fromB64ToArray(prk32Byte), + prk.buffer as ArrayBuffer, "info", 8161, "sha256", @@ -341,7 +343,8 @@ function testHkdf( utf8Key: string, unicodeKey: string, ) { - const ikm = Utils.fromB64ToArray("criAmKtfzxanbgea5/kelQ=="); + const ikmArray = Utils.fromB64ToArray("criAmKtfzxanbgea5/kelQ=="); + const ikm = ikmArray.buffer as ArrayBuffer; const regularSalt = "salt"; const utf8Salt = "üser_salt"; @@ -392,8 +395,9 @@ function testHkdfExpand( it("should create valid " + algorithm + " " + outputByteSize + " byte okm", async () => { const cryptoFunctionService = new NodeCryptoFunctionService(); + const prk = Utils.fromB64ToArray(b64prk); const okm = await cryptoFunctionService.hkdfExpand( - Utils.fromB64ToArray(b64prk), + prk.buffer as ArrayBuffer, info, outputByteSize, algorithm, diff --git a/jslib/node/src/services/nodeCryptoFunction.service.ts b/jslib/node/src/services/nodeCryptoFunction.service.ts index af9d570d..48d9e88f 100644 --- a/jslib/node/src/services/nodeCryptoFunction.service.ts +++ b/jslib/node/src/services/nodeCryptoFunction.service.ts @@ -3,7 +3,7 @@ import * as crypto from "crypto"; import * as forge from "node-forge"; import { CryptoFunctionService } from "@/jslib/common/src/abstractions/cryptoFunction.service"; -import { Utils } from "@/jslib/common/src/misc/utils"; +import Utils from "@/jslib/common/src/misc/utils"; import { DecryptParameters } from "@/jslib/common/src/models/domain/decryptParameters"; import { SymmetricCryptoKey } from "@/jslib/common/src/models/domain/symmetricCryptoKey"; @@ -215,7 +215,8 @@ export class NodeCryptoFunctionService implements CryptoFunctionService { const publicKeyAsn1 = forge.pki.publicKeyToAsn1(forgePublicKey); const publicKeyByteString = forge.asn1.toDer(publicKeyAsn1).data; const publicKeyArray = Utils.fromByteStringToArray(publicKeyByteString); - return Promise.resolve(publicKeyArray.buffer); + + return Promise.resolve(publicKeyArray.buffer as ArrayBuffer); } async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[ArrayBuffer, ArrayBuffer]> { @@ -241,7 +242,7 @@ export class NodeCryptoFunctionService implements CryptoFunctionService { const privateKeyByteString = forge.asn1.toDer(privateKeyPkcs8).getBytes(); const privateKey = Utils.fromByteStringToArray(privateKeyByteString); - resolve([publicKey.buffer, privateKey.buffer]); + resolve([publicKey.buffer as ArrayBuffer, privateKey.buffer as ArrayBuffer]); }, ); }); diff --git a/scripts/notarize.js b/scripts/notarize.js index 3085278d..cc51e576 100644 --- a/scripts/notarize.js +++ b/scripts/notarize.js @@ -1,6 +1,8 @@ /* eslint-disable @typescript-eslint/no-var-requires */ -require("dotenv").config(); -const { notarize } = require("@electron/notarize"); +import { notarize } from "@electron/notarize"; +import { config } from "dotenv"; + +config(); exports.default = async function notarizing(context) { const { electronPlatformName, appOutDir } = context; diff --git a/scripts/sign.js b/scripts/sign.js index 46d64aa7..985167e1 100644 --- a/scripts/sign.js +++ b/scripts/sign.js @@ -1,8 +1,10 @@ /* eslint-disable @typescript-eslint/no-var-requires, no-console */ +import { execSync } from "child_process"; + exports.default = async function (configuration) { if (parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 && configuration.path.slice(-4) == ".exe") { console.log(`[*] Signing file: ${configuration.path}`); - require("child_process").execSync( + execSync( `azuresigntool sign ` + `-kvu ${process.env.SIGNING_VAULT_URL} ` + `-kvi ${process.env.SIGNING_CLIENT_ID} ` + diff --git a/src/app/main.ts b/src/app/main.ts index 985222bb..da1cc2a3 100644 --- a/src/app/main.ts +++ b/src/app/main.ts @@ -3,8 +3,7 @@ import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; import { isDev } from "@/jslib/electron/src/utils"; -// tslint:disable-next-line -require("../scss/styles.scss"); +import "../scss/styles.scss"; import { AppModule } from "./app.module";