From 8b8acc15d2709e346df95a122df033a008ccecfe Mon Sep 17 00:00:00 2001 From: Thomas Rittson Date: Thu, 24 Mar 2022 17:22:05 +1000 Subject: [PATCH] WIP start building out CryptoWorkerService --- .../src/abstractions/cryptoWorker.service.ts | 7 ++ .../src/abstractions/platformUtils.service.ts | 1 + common/src/abstractions/webWorker.service.ts | 5 - common/src/misc/utils.ts | 4 +- common/src/services/cipher.service.ts | 48 +++++--- common/src/services/cryptoWorker.service.ts | 106 ++++++++++++++++++ common/src/services/webWorker.service.ts | 56 --------- common/src/workers/crypto.worker.ts | 2 - 8 files changed, 147 insertions(+), 82 deletions(-) create mode 100644 common/src/abstractions/cryptoWorker.service.ts delete mode 100644 common/src/abstractions/webWorker.service.ts create mode 100644 common/src/services/cryptoWorker.service.ts delete mode 100644 common/src/services/webWorker.service.ts diff --git a/common/src/abstractions/cryptoWorker.service.ts b/common/src/abstractions/cryptoWorker.service.ts new file mode 100644 index 00000000..ca4e46a1 --- /dev/null +++ b/common/src/abstractions/cryptoWorker.service.ts @@ -0,0 +1,7 @@ +import { CipherData } from '../models/data/cipherData'; +import { CipherView } from '../models/view/cipherView'; + +export abstract class CryptoWorkerService { + decryptCiphers: (cipherData: CipherData[]) => Promise; + terminateAll: () => Promise; +} diff --git a/common/src/abstractions/platformUtils.service.ts b/common/src/abstractions/platformUtils.service.ts index dded8171..1e09247f 100644 --- a/common/src/abstractions/platformUtils.service.ts +++ b/common/src/abstractions/platformUtils.service.ts @@ -24,6 +24,7 @@ export abstract class PlatformUtilsService { getApplicationVersion: () => Promise; supportsWebAuthn: (win: Window) => boolean; supportsDuo: () => boolean; + supportsWorkers: () => boolean; showToast: ( type: "error" | "success" | "warning" | "info", title: string, diff --git a/common/src/abstractions/webWorker.service.ts b/common/src/abstractions/webWorker.service.ts deleted file mode 100644 index 64805b9f..00000000 --- a/common/src/abstractions/webWorker.service.ts +++ /dev/null @@ -1,5 +0,0 @@ -export abstract class WebWorkerService { - create: (name: string) => Worker; - terminate: (name: string) => Promise; - terminateAll: () => Promise; -} diff --git a/common/src/misc/utils.ts b/common/src/misc/utils.ts index 1d96e082..07f63b6a 100644 --- a/common/src/misc/utils.ts +++ b/common/src/misc/utils.ts @@ -11,6 +11,7 @@ export class Utils { static isBrowser = true; static isMobileBrowser = false; static isAppleMobileBrowser = false; + static isWorker = false; static global: any = null; static tldEndingRegex = /.*\.(com|net|org|edu|uk|gov|ca|de|jp|fr|au|ru|ch|io|es|us|co|xyz|info|ly|mil)$/; @@ -31,7 +32,8 @@ export class Utils { Utils.isBrowser = typeof window !== "undefined"; Utils.isMobileBrowser = Utils.isBrowser && this.isMobile(window); Utils.isAppleMobileBrowser = Utils.isBrowser && this.isAppleMobile(window); - Utils.global = Utils.isNode && !Utils.isBrowser ? global : window; + Utils.isWorker = !Utils.isBrowser && (self as any).WorkerGlobalScope != null; + Utils.global = Utils.isNode && !Utils.isBrowser ? global : (Utils.isWorker ? self : window); } static fromB64ToArray(str: string): Uint8Array { diff --git a/common/src/services/cipher.service.ts b/common/src/services/cipher.service.ts index 2cbf5296..4ebe3f36 100644 --- a/common/src/services/cipher.service.ts +++ b/common/src/services/cipher.service.ts @@ -4,10 +4,11 @@ import { CryptoService } from "../abstractions/crypto.service"; import { FileUploadService } from "../abstractions/fileUpload.service"; import { I18nService } from "../abstractions/i18n.service"; import { LogService } from "../abstractions/log.service"; +import { PlatformUtilsService } from '../abstractions/platformUtils.service'; import { SearchService } from "../abstractions/search.service"; import { SettingsService } from "../abstractions/settings.service"; import { StateService } from "../abstractions/state.service"; -import { WebWorkerService } from '../abstractions/webWorker.service'; +import { CryptoWorkerService } from '../abstractions/cryptoWorker.service'; import { CipherType } from "../enums/cipherType"; import { FieldType } from "../enums/fieldType"; import { UriMatchType } from "../enums/uriMatchType"; @@ -49,6 +50,8 @@ const DomainMatchBlacklist = new Map>([ ["google.com", new Set(["script.google.com"])], ]); +const workerThreshold = 0; // Testing only, should be 250 + export class CipherService implements CipherServiceAbstraction { private sortedCiphersCache: SortedCiphersCache = new SortedCiphersCache( this.sortCiphersByLastUsed @@ -63,7 +66,8 @@ export class CipherService implements CipherServiceAbstraction { private searchService: () => SearchService, private logService: LogService, private stateService: StateService, - private webWorkerService: WebWorkerService + private cryptoWorkerService: CryptoWorkerService, + private platformUtilsService: PlatformUtilsService ) {} async getDecryptedCipherCache(): Promise { @@ -326,25 +330,33 @@ export class CipherService implements CipherServiceAbstraction { @sequentialize(() => "getAllDecrypted") async getAllDecrypted(): Promise { - - // TEST - this.webWorkerService.create('test'); - - const userId = await this.stateService.getUserId(); - if ((await this.getDecryptedCipherCache()) != null) { - if ( - this.searchService != null && - (this.searchService().indexedEntityId ?? userId) !== userId - ) { - await this.searchService().indexCiphers(userId, await this.getDecryptedCipherCache()); + // testing only, remove if statement in prod + if (false) { + const userId = await this.stateService.getUserId(); + if ((await this.getDecryptedCipherCache()) != null) { + if ( + this.searchService != null && + (this.searchService().indexedEntityId ?? userId) !== userId + ) { + await this.searchService().indexCiphers(userId, await this.getDecryptedCipherCache()); + } + return await this.getDecryptedCipherCache(); + } + + const decCiphers: CipherView[] = []; + const hasKey = await this.cryptoService.hasKey(); + if (!hasKey) { + throw new Error("No key."); } - return await this.getDecryptedCipherCache(); } - const decCiphers: CipherView[] = []; - const hasKey = await this.cryptoService.hasKey(); - if (!hasKey) { - throw new Error("No key."); + const cipherData = await this.stateService.getEncryptedCiphers(); + const supportsWorkers = this.platformUtilsService.supportsWorkers(); + if (supportsWorkers && Object.values(cipherData).length > workerThreshold) { + // Test + // this.cryptoWorkerService.create('test'); + // Do stuff + // return this.cryptoWorkerService. } const promises: any[] = []; diff --git a/common/src/services/cryptoWorker.service.ts b/common/src/services/cryptoWorker.service.ts new file mode 100644 index 00000000..66708f48 --- /dev/null +++ b/common/src/services/cryptoWorker.service.ts @@ -0,0 +1,106 @@ +import { CryptoService } from '../abstractions/crypto.service'; +import { CryptoWorkerService as CryptoWorkerServiceAbstraction } from '../abstractions/cryptoWorker.service'; +import { StateService } from '../abstractions/state.service'; +import { CipherData } from '../models/data/cipherData'; +import { Cipher } from '../models/domain/cipher'; +import { CipherView } from '../models/view/cipherView'; + +interface EncryptionKeys { + key: string, + encKey: string, + orgKeys: string, + privateKey: string +} + +export class CryptoWorkerService implements CryptoWorkerServiceAbstraction { + private currentWorkers: Worker[] = []; + + constructor( + private cryptoService: CryptoService, + private stateService: StateService, + ) { } + + // Callers should be sequentialized to prevent duplicate concurrent calls + // Consider whether we should utilize existing workers instead of creating new ones + async decryptCiphers(cipherData: CipherData[]): Promise { + if (cipherData == null || cipherData.length === 0) { + return null; + } + + const key = (await this.cryptoService.getKey()).keyB64; + const encKey = (await this.stateService.getEncryptedCryptoSymmetricKey()); + const orgKeys = (await this.stateService.getEncryptedOrganizationKeys()); + const privateKey = (await this.stateService.getEncryptedPrivateKey()); + const encryptionKeys: EncryptionKeys = { + key: key, + encKey: encKey, + orgKeys: orgKeys, + privateKey: privateKey + } + + const message = { + command: 'decryptCiphers', + ciphers: JSON.stringify(cipherData), + keys: JSON.stringify(encryptionKeys), + } + + return new Promise((resolve, reject) => { + const worker = this.createWorker(); + + worker.addEventListener('message', response => { + this.terminate(worker); + resolve(this.processDecryptedCiphers(response)) + }); + + worker.postMessage(message); + }) + } + + private async processDecryptedCiphers(response: any): Promise { + // TODO: transform from serialized data to CipherViews + return null; + } + + private createWorker() { + const worker = new Worker(new URL('../workers/crypto.worker.ts', import.meta.url)); + this.currentWorkers.push(worker); + return worker; + } + + terminateAll(): Promise { + if (this.currentWorkers.length === 0) { + return; + } + + const terminateResponses = this.currentWorkers.map((worker: Worker) => this.terminate(worker)); + return Promise.all(terminateResponses); + } + + private terminate(worker: Worker) { + return new Promise((resolve, reject) => { + const killWorker = () => { + worker.terminate(); + const index = this.currentWorkers.indexOf(worker); + if (index > -1) { + this.currentWorkers.splice(index); + } + clearTimeout(terminateWorkerTimeout); + resolve(); + } + + worker.addEventListener('message', event => { + if (event.data.command === 'clearCacheResponse') { + killWorker(); + } + }); + + worker.postMessage({ + command: 'clearCacheRequest', + }); + + const terminateWorkerTimeout = setTimeout(() => { + killWorker(); + }, 250); + }); + } +} diff --git a/common/src/services/webWorker.service.ts b/common/src/services/webWorker.service.ts deleted file mode 100644 index c85d5381..00000000 --- a/common/src/services/webWorker.service.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { WebWorkerService as WebWorkerServiceAbstraction } from '../abstractions/webWorker.service'; - -export class WebWorkerService implements WebWorkerServiceAbstraction { - constructor() { - console.log('service up') - } - workers = new Map(); - - create(name: string) { - - if (!this.workers.has(name)) { - // const worker = new Worker(new URL('../workers/crypto.worker.ts', import.meta.url)); - // worker.addEventListener('message', (e) => console.log(e)); - // this.workers.set(name, worker); - } - return this.workers.get(name); - } - - terminate(name: string): Promise { - const worker = this.workers.get(name); - if (worker == null) { - return; - } - - worker.postMessage({ - type: 'clearCacheRequest', - }); - return new Promise((resolve, reject) => { - const terminateWorkerTimeout = setTimeout(() => { - worker.terminate(); - this.workers.delete(name); - resolve(); - }, 250); - - worker.addEventListener('message', event => { - if (event.data.type === 'clearCacheResponse') { - worker.terminate(); - this.workers.delete(name); - clearTimeout(terminateWorkerTimeout); - resolve(); - } - }); - }); - } - - terminateAll(): Promise { - const promises: Promise[] = []; - this.workers.forEach((worker, name) => { - promises.push(this.terminate(name)); - }); - if (promises.length === 0) { - return; - } - return Promise.all(promises); - } -} diff --git a/common/src/workers/crypto.worker.ts b/common/src/workers/crypto.worker.ts index 65cd6ec5..497af789 100644 --- a/common/src/workers/crypto.worker.ts +++ b/common/src/workers/crypto.worker.ts @@ -9,8 +9,6 @@ import { CryptoService } from '../services/crypto.service'; import { WebCryptoFunctionService } from '../services/webCryptoFunction.service'; import { WorkerLogService } from '../services/workerLogService'; -import { Utils } from '../misc/utils'; - const workerApi: Worker = self as any; // const Keys = ConstantsService.cryptoKeys; let firstRun = true;