mirror of
https://github.com/bitwarden/jslib
synced 2025-12-24 04:04:38 +00:00
WIP start building out CryptoWorkerService
This commit is contained in:
7
common/src/abstractions/cryptoWorker.service.ts
Normal file
7
common/src/abstractions/cryptoWorker.service.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { CipherData } from '../models/data/cipherData';
|
||||
import { CipherView } from '../models/view/cipherView';
|
||||
|
||||
export abstract class CryptoWorkerService {
|
||||
decryptCiphers: (cipherData: CipherData[]) => Promise<CipherView[]>;
|
||||
terminateAll: () => Promise<void[]>;
|
||||
}
|
||||
@@ -24,6 +24,7 @@ export abstract class PlatformUtilsService {
|
||||
getApplicationVersion: () => Promise<string>;
|
||||
supportsWebAuthn: (win: Window) => boolean;
|
||||
supportsDuo: () => boolean;
|
||||
supportsWorkers: () => boolean;
|
||||
showToast: (
|
||||
type: "error" | "success" | "warning" | "info",
|
||||
title: string,
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export abstract class WebWorkerService {
|
||||
create: (name: string) => Worker;
|
||||
terminate: (name: string) => Promise<void>;
|
||||
terminateAll: () => Promise<void[]>;
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<string, Set<string>>([
|
||||
["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<CipherView[]> {
|
||||
@@ -326,25 +330,33 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
|
||||
@sequentialize(() => "getAllDecrypted")
|
||||
async getAllDecrypted(): Promise<CipherView[]> {
|
||||
|
||||
// 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[] = [];
|
||||
|
||||
106
common/src/services/cryptoWorker.service.ts
Normal file
106
common/src/services/cryptoWorker.service.ts
Normal file
@@ -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<CipherView[]> {
|
||||
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<CipherView[]> {
|
||||
// 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<void[]> {
|
||||
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<void>((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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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<string, Worker>();
|
||||
|
||||
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<void> {
|
||||
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<void[]> {
|
||||
const promises: Promise<void>[] = [];
|
||||
this.workers.forEach((worker, name) => {
|
||||
promises.push(this.terminate(name));
|
||||
});
|
||||
if (promises.length === 0) {
|
||||
return;
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user