From 2dd4781e5e49cd823f8b2b7259224e208e0af475 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 29 Apr 2025 17:56:21 +0200 Subject: [PATCH] Remove legacy encryption services --- .../src/popup/services/init.service.ts | 3 - apps/browser/tsconfig.json | 1 - apps/browser/webpack.config.js | 1 - .../service-container/service-container.ts | 1 - apps/desktop/tsconfig.json | 2 +- .../services/emergency-access.service.ts | 2 - apps/web/src/app/core/init.service.ts | 2 - apps/web/tsconfig.build.json | 3 +- apps/web/tsconfig.json | 3 +- bitwarden_license/bit-web/tsconfig.build.json | 3 +- bitwarden_license/bit-web/tsconfig.json | 1 - .../src/services/jslib-services.module.ts | 13 +- .../abstractions/bulk-encrypt.service.ts | 13 - .../abstractions/crypto-function.service.ts | 18 - ...bulk-encrypt.service.impementation.spec.ts | 170 ---- .../bulk-encrypt.service.implementation.ts | 185 ----- .../encrypt.service.implementation.ts | 242 ------ .../crypto/services/encrypt.service.spec.ts | 746 ------------------ .../crypto/services/encrypt.worker.ts | 81 -- .../fallback-bulk-encrypt.service.spec.ts | 97 --- .../services/fallback-bulk-encrypt.service.ts | 46 -- ...ead-encrypt.service.implementation.spec.ts | 123 --- ...tithread-encrypt.service.implementation.ts | 114 --- .../src/vault/services/cipher.service.spec.ts | 3 - .../src/vault/services/cipher.service.ts | 23 +- 25 files changed, 10 insertions(+), 1886 deletions(-) delete mode 100644 libs/common/src/key-management/crypto/abstractions/bulk-encrypt.service.ts delete mode 100644 libs/common/src/key-management/crypto/services/bulk-encrypt.service.impementation.spec.ts delete mode 100644 libs/common/src/key-management/crypto/services/bulk-encrypt.service.implementation.ts delete mode 100644 libs/common/src/key-management/crypto/services/encrypt.service.spec.ts delete mode 100644 libs/common/src/key-management/crypto/services/encrypt.worker.ts delete mode 100644 libs/common/src/key-management/crypto/services/fallback-bulk-encrypt.service.spec.ts delete mode 100644 libs/common/src/key-management/crypto/services/fallback-bulk-encrypt.service.ts delete mode 100644 libs/common/src/key-management/crypto/services/multithread-encrypt.service.implementation.spec.ts delete mode 100644 libs/common/src/key-management/crypto/services/multithread-encrypt.service.implementation.ts diff --git a/apps/browser/src/popup/services/init.service.ts b/apps/browser/src/popup/services/init.service.ts index c9fe7161259..60e0cb2b411 100644 --- a/apps/browser/src/popup/services/init.service.ts +++ b/apps/browser/src/popup/services/init.service.ts @@ -3,7 +3,6 @@ import { inject, Inject, Injectable } from "@angular/core"; import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; -import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -32,7 +31,6 @@ export class InitService { private viewCacheService: PopupViewCacheService, private configService: ConfigService, private encryptService: EncryptService, - private bulkEncryptService: BulkEncryptService, @Inject(DOCUMENT) private document: Document, ) {} @@ -43,7 +41,6 @@ export class InitService { this.configService.serverConfig$.subscribe((newConfig) => { if (newConfig != null) { this.encryptService.onServerConfigChange(newConfig); - this.bulkEncryptService.onServerConfigChange(newConfig); } }); await this.i18nService.init(); diff --git a/apps/browser/tsconfig.json b/apps/browser/tsconfig.json index e24985f58af..388899572b4 100644 --- a/apps/browser/tsconfig.json +++ b/apps/browser/tsconfig.json @@ -54,6 +54,5 @@ "src", "../../libs/common/src/autofill/constants", "../../libs/common/custom-matchers.d.ts", - "../../libs/common/src/key-management/crypto/services/encrypt.worker.ts" ] } diff --git a/apps/browser/webpack.config.js b/apps/browser/webpack.config.js index 4b66ed7d70a..8078d52e6c2 100644 --- a/apps/browser/webpack.config.js +++ b/apps/browser/webpack.config.js @@ -201,7 +201,6 @@ const mainConfig = { "./src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts", "overlay/menu": "./src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts", - "encrypt-worker": "../../libs/common/src/key-management/crypto/services/encrypt.worker.ts", "content/send-on-installed-message": "./src/vault/content/send-on-installed-message.ts", "content/send-popup-open-message": "./src/vault/content/send-popup-open-message.ts", }, diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index fe2f506f229..de28bc042a8 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -60,7 +60,6 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service"; import { ClientType } from "@bitwarden/common/enums"; import { EncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/encrypt.service.implementation"; -import { FallbackBulkEncryptService } from "@bitwarden/common/key-management/crypto/services/fallback-bulk-encrypt.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { DeviceTrustService } from "@bitwarden/common/key-management/device-trust/services/device-trust.service.implementation"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/services/key-connector.service"; diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json index 78b3512405e..321f60df0ff 100644 --- a/apps/desktop/tsconfig.json +++ b/apps/desktop/tsconfig.json @@ -48,5 +48,5 @@ "angularCompilerOptions": { "strictTemplates": true }, - "include": ["src", "../../libs/common/src/key-management/crypto/services/encrypt.worker.ts"] + "include": ["src"] } diff --git a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts index 5094c0c09ab..983e7c74175 100644 --- a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts +++ b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts @@ -6,7 +6,6 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -58,7 +57,6 @@ export class EmergencyAccessService private apiService: ApiService, private keyService: KeyService, private encryptService: EncryptService, - private bulkEncryptService: BulkEncryptService, private cipherService: CipherService, private logService: LogService, private configService: ConfigService, diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index 43547ff5d57..761e3941d3c 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -7,7 +7,6 @@ import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service"; -import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { DefaultVaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -42,7 +41,6 @@ export class InitService { private ipcService: IpcService, private sdkLoadService: SdkLoadService, private configService: ConfigService, - private bulkEncryptService: BulkEncryptService, @Inject(DOCUMENT) private document: Document, ) {} diff --git a/apps/web/tsconfig.build.json b/apps/web/tsconfig.build.json index 39ab37efbb8..881ba007d76 100644 --- a/apps/web/tsconfig.build.json +++ b/apps/web/tsconfig.build.json @@ -2,7 +2,6 @@ "extends": "./tsconfig.json", "files": ["src/polyfills.ts", "src/main.ts", "src/theme.ts"], "include": [ - "src/connectors/*.ts", - "../../libs/common/src/key-management/crypto/services/encrypt.worker.ts" + "src/connectors/*.ts" ] } diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 3d62a30bc01..7a0a1e4c5eb 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -42,7 +42,6 @@ "include": [ "src/connectors/*.ts", "src/**/*.stories.ts", - "src/**/*.spec.ts", - "../../libs/common/src/key-management/crypto/services/encrypt.worker.ts" + "src/**/*.spec.ts" ] } diff --git a/bitwarden_license/bit-web/tsconfig.build.json b/bitwarden_license/bit-web/tsconfig.build.json index 6313ce27863..0909bfc5b67 100644 --- a/bitwarden_license/bit-web/tsconfig.build.json +++ b/bitwarden_license/bit-web/tsconfig.build.json @@ -8,7 +8,6 @@ "../../bitwarden_license/bit-web/src/main.ts" ], "include": [ - "../../apps/web/src/connectors/*.ts", - "../../libs/common/src/key-management/crypto/services/encrypt.worker.ts" + "../../apps/web/src/connectors/*.ts" ] } diff --git a/bitwarden_license/bit-web/tsconfig.json b/bitwarden_license/bit-web/tsconfig.json index 679513a656f..73bd433f99d 100644 --- a/bitwarden_license/bit-web/tsconfig.json +++ b/bitwarden_license/bit-web/tsconfig.json @@ -47,7 +47,6 @@ "../../apps/web/src/connectors/*.ts", "../../apps/web/src/**/*.stories.ts", "../../apps/web/src/**/*.spec.ts", - "../../libs/common/src/key-management/crypto/services/encrypt.worker.ts", "src/**/*.stories.ts", "src/**/*.spec.ts" diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 1cc2b591412..dd4f79b015a 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -143,11 +143,8 @@ import { BillingApiService } from "@bitwarden/common/billing/services/billing-ap import { OrganizationBillingApiService } from "@bitwarden/common/billing/services/organization/organization-billing-api.service"; import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service"; import { TaxService } from "@bitwarden/common/billing/services/tax.service"; -import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { BulkEncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/bulk-encrypt.service.implementation"; -import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/multithread-encrypt.service.implementation"; import { WebCryptoFunctionService } from "@bitwarden/common/key-management/crypto/services/web-crypto-function.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { DeviceTrustService } from "@bitwarden/common/key-management/device-trust/services/device-trust.service.implementation"; @@ -499,7 +496,6 @@ const safeProviders: SafeProvider[] = [ stateService: StateServiceAbstraction, autofillSettingsService: AutofillSettingsServiceAbstraction, encryptService: EncryptService, - bulkEncryptService: BulkEncryptService, fileUploadService: CipherFileUploadServiceAbstraction, configService: ConfigService, stateProvider: StateProvider, @@ -515,7 +511,6 @@ const safeProviders: SafeProvider[] = [ stateService, autofillSettingsService, encryptService, - bulkEncryptService, fileUploadService, configService, stateProvider, @@ -531,7 +526,6 @@ const safeProviders: SafeProvider[] = [ StateServiceAbstraction, AutofillSettingsServiceAbstraction, EncryptService, - BulkEncryptService, CipherFileUploadServiceAbstraction, ConfigService, StateProvider, @@ -916,14 +910,9 @@ const safeProviders: SafeProvider[] = [ }), safeProvider({ provide: EncryptService, - useClass: MultithreadEncryptServiceImplementation, + useClass: EncryptServiceImplementation, deps: [CryptoFunctionServiceAbstraction, LogService, LOG_MAC_FAILURES], }), - safeProvider({ - provide: BulkEncryptService, - useClass: BulkEncryptServiceImplementation, - deps: [CryptoFunctionServiceAbstraction, LogService], - }), safeProvider({ provide: EventUploadServiceAbstraction, useClass: EventUploadService, diff --git a/libs/common/src/key-management/crypto/abstractions/bulk-encrypt.service.ts b/libs/common/src/key-management/crypto/abstractions/bulk-encrypt.service.ts deleted file mode 100644 index 399ad75231e..00000000000 --- a/libs/common/src/key-management/crypto/abstractions/bulk-encrypt.service.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ServerConfig } from "../../../platform/abstractions/config/server-config"; -import { Decryptable } from "../../../platform/interfaces/decryptable.interface"; -import { InitializerMetadata } from "../../../platform/interfaces/initializer-metadata.interface"; -import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; - -export abstract class BulkEncryptService { - abstract decryptItems( - items: Decryptable[], - key: SymmetricCryptoKey, - ): Promise; - - abstract onServerConfigChange(newConfig: ServerConfig): void; -} diff --git a/libs/common/src/key-management/crypto/abstractions/crypto-function.service.ts b/libs/common/src/key-management/crypto/abstractions/crypto-function.service.ts index 4b89dde7021..d5d89ca880e 100644 --- a/libs/common/src/key-management/crypto/abstractions/crypto-function.service.ts +++ b/libs/common/src/key-management/crypto/abstractions/crypto-function.service.ts @@ -1,8 +1,3 @@ -import { - CbcDecryptParameters, - EcbDecryptParameters, -} from "../../../platform/models/domain/decrypt-parameters"; -import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { CsprngArray } from "../../../types/csprng"; export abstract class CryptoFunctionService { @@ -48,19 +43,6 @@ export abstract class CryptoFunctionService { algorithm: "sha1" | "sha256" | "sha512", ): Promise; abstract compareFast(a: Uint8Array | string, b: Uint8Array | string): Promise; - abstract aesEncrypt(data: Uint8Array, iv: Uint8Array, key: Uint8Array): Promise; - abstract aesDecryptFastParameters( - data: string, - iv: string, - mac: string, - key: SymmetricCryptoKey, - ): CbcDecryptParameters; - abstract aesDecryptFast({ - mode, - parameters, - }: - | { mode: "cbc"; parameters: CbcDecryptParameters } - | { mode: "ecb"; parameters: EcbDecryptParameters }): Promise; abstract aesDecrypt( data: Uint8Array, iv: Uint8Array, diff --git a/libs/common/src/key-management/crypto/services/bulk-encrypt.service.impementation.spec.ts b/libs/common/src/key-management/crypto/services/bulk-encrypt.service.impementation.spec.ts deleted file mode 100644 index bc77cfb410f..00000000000 --- a/libs/common/src/key-management/crypto/services/bulk-encrypt.service.impementation.spec.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { mock, MockProxy } from "jest-mock-extended"; -import * as rxjs from "rxjs"; - -import { ServerConfig } from "../../../platform/abstractions/config/server-config"; -import { LogService } from "../../../platform/abstractions/log.service"; -import { Decryptable } from "../../../platform/interfaces/decryptable.interface"; -import { InitializerMetadata } from "../../../platform/interfaces/initializer-metadata.interface"; -import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; -import { CryptoFunctionService } from "../abstractions/crypto-function.service"; -import { buildSetConfigMessage } from "../types/worker-command.type"; - -import { BulkEncryptServiceImplementation } from "./bulk-encrypt.service.implementation"; - -describe("BulkEncryptServiceImplementation", () => { - const cryptoFunctionService = mock(); - const logService = mock(); - - let sut: BulkEncryptServiceImplementation; - - beforeEach(() => { - sut = new BulkEncryptServiceImplementation(cryptoFunctionService, logService); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - describe("decryptItems", () => { - const key = mock(); - const serverConfig = mock(); - const mockWorker = mock(); - let globalWindow: any; - - beforeEach(() => { - globalWindow = global.window; - - // Mock creating a worker. - global.Worker = jest.fn().mockImplementation(() => mockWorker); - global.URL = jest.fn().mockImplementation(() => "url") as unknown as typeof URL; - global.URL.createObjectURL = jest.fn().mockReturnValue("blob:url"); - global.URL.revokeObjectURL = jest.fn(); - global.URL.canParse = jest.fn().mockReturnValue(true); - - // Mock the workers returned response. - const mockMessageEvent = { - id: "mock-guid", - data: ["decrypted1", "decrypted2"], - }; - const mockMessageEvent$ = rxjs.from([mockMessageEvent]); - jest.spyOn(rxjs, "fromEvent").mockReturnValue(mockMessageEvent$); - }); - - afterEach(() => { - global.window = globalWindow; - }); - - it("throws error if key is null", async () => { - const nullKey = null as unknown as SymmetricCryptoKey; - await expect(sut.decryptItems([], nullKey)).rejects.toThrow("No encryption key provided."); - }); - - it("returns an empty array when items is null", async () => { - const result = await sut.decryptItems(null as any, key); - expect(result).toEqual([]); - }); - - it("returns an empty array when items is empty", async () => { - const result = await sut.decryptItems([], key); - expect(result).toEqual([]); - }); - - it("decrypts items sequentially when window is undefined", async () => { - // Make global window undefined. - delete (global as any).window; - - const mockItems = [createMockDecryptable("item1"), createMockDecryptable("item2")]; - - const result = await sut.decryptItems(mockItems, key); - - expect(logService.info).toHaveBeenCalledWith( - "Window not available in BulkEncryptService, decrypting sequentially", - ); - expect(result).toEqual(["item1", "item2"]); - expect(mockItems[0].decrypt).toHaveBeenCalledWith(key); - expect(mockItems[1].decrypt).toHaveBeenCalledWith(key); - }); - - it("uses workers for decryption when window is available", async () => { - const mockDecryptedItems = ["decrypted1", "decrypted2"]; - jest - .spyOn(sut, "getDecryptedItemsFromWorkers") - .mockResolvedValue(mockDecryptedItems); - - const mockItems = [createMockDecryptable("item1"), createMockDecryptable("item2")]; - - const result = await sut.decryptItems(mockItems, key); - - expect(sut["getDecryptedItemsFromWorkers"]).toHaveBeenCalledWith(mockItems, key); - expect(result).toEqual(mockDecryptedItems); - }); - - it("creates new worker when none exist", async () => { - (sut as any).currentServerConfig = undefined; - const mockItems = [createMockDecryptable("item1"), createMockDecryptable("item2")]; - - await sut.decryptItems(mockItems, key); - - expect(global.Worker).toHaveBeenCalled(); - expect(mockWorker.postMessage).toHaveBeenCalledTimes(1); - expect(mockWorker.postMessage).not.toHaveBeenCalledWith( - buildSetConfigMessage({ newConfig: serverConfig }), - ); - }); - - it("sends a SetConfigMessage to the new worker when there is a current server config", async () => { - (sut as any).currentServerConfig = serverConfig; - const mockItems = [createMockDecryptable("item1"), createMockDecryptable("item2")]; - - await sut.decryptItems(mockItems, key); - - expect(global.Worker).toHaveBeenCalled(); - expect(mockWorker.postMessage).toHaveBeenCalledTimes(2); - expect(mockWorker.postMessage).toHaveBeenCalledWith( - buildSetConfigMessage({ newConfig: serverConfig }), - ); - }); - - it("does not create worker if one exists", async () => { - (sut as any).currentServerConfig = serverConfig; - (sut as any).workers = [mockWorker]; - const mockItems = [createMockDecryptable("item1"), createMockDecryptable("item2")]; - - await sut.decryptItems(mockItems, key); - - expect(global.Worker).not.toHaveBeenCalled(); - expect(mockWorker.postMessage).toHaveBeenCalledTimes(1); - expect(mockWorker.postMessage).not.toHaveBeenCalledWith( - buildSetConfigMessage({ newConfig: serverConfig }), - ); - }); - }); - - describe("onServerConfigChange", () => { - it("updates internal currentServerConfig to new config", () => { - const newConfig = mock(); - - sut.onServerConfigChange(newConfig); - - expect((sut as any).currentServerConfig).toBe(newConfig); - }); - - it("does send a SetConfigMessage to workers when there is a worker", () => { - const newConfig = mock(); - const mockWorker = mock(); - (sut as any).workers = [mockWorker]; - - sut.onServerConfigChange(newConfig); - - expect(mockWorker.postMessage).toHaveBeenCalledWith(buildSetConfigMessage({ newConfig })); - }); - }); -}); - -function createMockDecryptable( - returnValue: any, -): MockProxy> { - const mockDecryptable = mock>(); - mockDecryptable.decrypt.mockResolvedValue(returnValue); - return mockDecryptable; -} diff --git a/libs/common/src/key-management/crypto/services/bulk-encrypt.service.implementation.ts b/libs/common/src/key-management/crypto/services/bulk-encrypt.service.implementation.ts deleted file mode 100644 index c22944ba217..00000000000 --- a/libs/common/src/key-management/crypto/services/bulk-encrypt.service.implementation.ts +++ /dev/null @@ -1,185 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { firstValueFrom, fromEvent, filter, map, takeUntil, defaultIfEmpty, Subject } from "rxjs"; -import { Jsonify } from "type-fest"; - -import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service"; -import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface"; -import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { getClassInitializer } from "@bitwarden/common/platform/services/cryptography/get-class-initializer"; - -import { ServerConfig } from "../../../platform/abstractions/config/server-config"; -import { buildDecryptMessage, buildSetConfigMessage } from "../types/worker-command.type"; - -// TTL (time to live) is not strictly required but avoids tying up memory resources if inactive -const workerTTL = 60000; // 1 minute -const maxWorkers = 8; -const minNumberOfItemsForMultithreading = 400; - -export class BulkEncryptServiceImplementation implements BulkEncryptService { - private workers: Worker[] = []; - private timeout: any; - private currentServerConfig: ServerConfig | undefined = undefined; - - private clear$ = new Subject(); - - constructor( - protected cryptoFunctionService: CryptoFunctionService, - protected logService: LogService, - ) {} - - /** - * Decrypts items using a web worker if the environment supports it. - * Will fall back to the main thread if the window object is not available. - */ - async decryptItems( - items: Decryptable[], - key: SymmetricCryptoKey, - ): Promise { - if (key == null) { - throw new Error("No encryption key provided."); - } - - if (items == null || items.length < 1) { - return []; - } - - if (typeof window === "undefined") { - this.logService.info("Window not available in BulkEncryptService, decrypting sequentially"); - const results = []; - for (let i = 0; i < items.length; i++) { - results.push(await items[i].decrypt(key)); - } - return results; - } - - const decryptedItems = await this.getDecryptedItemsFromWorkers(items, key); - return decryptedItems; - } - - onServerConfigChange(newConfig: ServerConfig): void { - this.currentServerConfig = newConfig; - this.updateWorkerServerConfigs(newConfig); - } - - /** - * Sends items to a set of web workers to decrypt them. This utilizes multiple workers to decrypt items - * faster without interrupting other operations (e.g. updating UI). - */ - private async getDecryptedItemsFromWorkers( - items: Decryptable[], - key: SymmetricCryptoKey, - ): Promise { - if (items == null || items.length < 1) { - return []; - } - - this.clearTimeout(); - - const hardwareConcurrency = navigator.hardwareConcurrency || 1; - let numberOfWorkers = Math.min(hardwareConcurrency, maxWorkers); - if (items.length < minNumberOfItemsForMultithreading) { - numberOfWorkers = 1; - } - - this.logService.info( - `Starting decryption using multithreading with ${numberOfWorkers} workers for ${items.length} items`, - ); - - if (this.workers.length == 0) { - for (let i = 0; i < numberOfWorkers; i++) { - this.workers.push( - new Worker( - new URL( - /* webpackChunkName: 'encrypt-worker' */ - "@bitwarden/common/key-management/crypto/services/encrypt.worker.ts", - import.meta.url, - ), - ), - ); - } - if (this.currentServerConfig != undefined) { - this.updateWorkerServerConfigs(this.currentServerConfig); - } - } - - const itemsPerWorker = Math.floor(items.length / this.workers.length); - const results = []; - - for (const [i, worker] of this.workers.entries()) { - const start = i * itemsPerWorker; - const end = start + itemsPerWorker; - const itemsForWorker = items.slice(start, end); - - // push the remaining items to the last worker - if (i == this.workers.length - 1) { - itemsForWorker.push(...items.slice(end)); - } - - const id = Utils.newGuid(); - const request = buildDecryptMessage({ - id, - items: itemsForWorker, - key: key, - }); - - worker.postMessage(request); - results.push( - firstValueFrom( - fromEvent(worker, "message").pipe( - filter((response: MessageEvent) => response.data?.id === id), - map((response) => JSON.parse(response.data.items)), - map((items) => - items.map((jsonItem: Jsonify) => { - const initializer = getClassInitializer(jsonItem.initializerKey); - return initializer(jsonItem); - }), - ), - takeUntil(this.clear$), - defaultIfEmpty([]), - ), - ), - ); - } - - const decryptedItems = (await Promise.all(results)).flat(); - this.logService.info( - `Finished decrypting ${decryptedItems.length} items using ${numberOfWorkers} workers`, - ); - - this.restartTimeout(); - - return decryptedItems; - } - - private updateWorkerServerConfigs(newConfig: ServerConfig) { - this.workers.forEach((worker) => { - const request = buildSetConfigMessage({ newConfig }); - worker.postMessage(request); - }); - } - - private clear() { - this.clear$.next(); - for (const worker of this.workers) { - worker.terminate(); - } - this.workers = []; - this.clearTimeout(); - } - - private restartTimeout() { - this.clearTimeout(); - this.timeout = setTimeout(() => this.clear(), workerTTL); - } - - private clearTimeout() { - if (this.timeout != null) { - clearTimeout(this.timeout); - } - } -} diff --git a/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts b/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts index 30488cdefb6..084061de661 100644 --- a/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts +++ b/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts @@ -150,222 +150,6 @@ export class EncryptServiceImplementation implements EncryptService { this.blockType0 = getFeatureFlagValue(newConfig, FeatureFlag.PM17987_BlockType0); } - async encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey): Promise { - if (key == null) { - throw new Error("No encryption key provided."); - } - - if (this.blockType0) { - if (key.inner().type === EncryptionType.AesCbc256_B64) { - throw new Error("Type 0 encryption is not supported."); - } - } - - if (plainValue == null) { - return Promise.resolve(null); - } - - if (typeof plainValue === "string") { - return this.encryptUint8Array(Utils.fromUtf8ToArray(plainValue), key); - } else { - return this.encryptUint8Array(plainValue, key); - } - } - - private async encryptUint8Array( - plainValue: Uint8Array, - key: SymmetricCryptoKey, - ): Promise { - if (key == null) { - throw new Error("No encryption key provided."); - } - - if (this.blockType0) { - if (key.inner().type === EncryptionType.AesCbc256_B64) { - throw new Error("Type 0 encryption is not supported."); - } - } - - if (plainValue == null) { - return Promise.resolve(null); - } - - const innerKey = key.inner(); - if (innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) { - const encObj = await this.aesEncrypt(plainValue, innerKey); - const iv = Utils.fromBufferToB64(encObj.iv); - const data = Utils.fromBufferToB64(encObj.data); - const mac = Utils.fromBufferToB64(encObj.mac); - return new EncString(innerKey.type, data, iv, mac); - } else if (innerKey.type === EncryptionType.AesCbc256_B64) { - const encObj = await this.aesEncryptLegacy(plainValue, innerKey); - const iv = Utils.fromBufferToB64(encObj.iv); - const data = Utils.fromBufferToB64(encObj.data); - return new EncString(innerKey.type, data, iv); - } - } - - async encryptToBytes(plainValue: Uint8Array, key: SymmetricCryptoKey): Promise { - if (key == null) { - throw new Error("No encryption key provided."); - } - - if (this.blockType0) { - if (key.inner().type === EncryptionType.AesCbc256_B64) { - throw new Error("Type 0 encryption is not supported."); - } - } - - const innerKey = key.inner(); - if (innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) { - const encValue = await this.aesEncrypt(plainValue, innerKey); - const macLen = encValue.mac.length; - const encBytes = new Uint8Array( - 1 + encValue.iv.byteLength + macLen + encValue.data.byteLength, - ); - encBytes.set([innerKey.type]); - encBytes.set(new Uint8Array(encValue.iv), 1); - encBytes.set(new Uint8Array(encValue.mac), 1 + encValue.iv.byteLength); - encBytes.set(new Uint8Array(encValue.data), 1 + encValue.iv.byteLength + macLen); - return new EncArrayBuffer(encBytes); - } else if (innerKey.type === EncryptionType.AesCbc256_B64) { - const encValue = await this.aesEncryptLegacy(plainValue, innerKey); - const encBytes = new Uint8Array(1 + encValue.iv.byteLength + encValue.data.byteLength); - encBytes.set([innerKey.type]); - encBytes.set(new Uint8Array(encValue.iv), 1); - encBytes.set(new Uint8Array(encValue.data), 1 + encValue.iv.byteLength); - return new EncArrayBuffer(encBytes); - } - } - - async decryptToUtf8( - encString: EncString, - key: SymmetricCryptoKey, - decryptContext: string = "no context", - ): Promise { - if (key == null) { - throw new Error("No key provided for decryption."); - } - - const innerKey = key.inner(); - if (encString.encryptionType !== innerKey.type) { - this.logDecryptError( - "Key encryption type does not match payload encryption type", - innerKey.type, - encString.encryptionType, - decryptContext, - ); - return null; - } - - if (innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) { - const fastParams = this.cryptoFunctionService.aesDecryptFastParameters( - encString.data, - encString.iv, - encString.mac, - key, - ); - - const computedMac = await this.cryptoFunctionService.hmacFast( - fastParams.macData, - fastParams.macKey, - "sha256", - ); - const macsEqual = await this.cryptoFunctionService.compareFast(fastParams.mac, computedMac); - if (!macsEqual) { - this.logMacFailed( - "decryptToUtf8 MAC comparison failed. Key or payload has changed.", - innerKey.type, - encString.encryptionType, - decryptContext, - ); - return null; - } - return await this.cryptoFunctionService.aesDecryptFast({ - mode: "cbc", - parameters: fastParams, - }); - } else if (innerKey.type === EncryptionType.AesCbc256_B64) { - const fastParams = this.cryptoFunctionService.aesDecryptFastParameters( - encString.data, - encString.iv, - undefined, - key, - ); - return await this.cryptoFunctionService.aesDecryptFast({ - mode: "cbc", - parameters: fastParams, - }); - } else { - throw new Error(`Unsupported encryption type`); - } - } - - async decryptToBytes( - encThing: Encrypted, - key: SymmetricCryptoKey, - decryptContext: string = "no context", - ): Promise { - if (key == null) { - throw new Error("No encryption key provided."); - } - - if (encThing == null) { - throw new Error("Nothing provided for decryption."); - } - - const inner = key.inner(); - if (encThing.encryptionType !== inner.type) { - this.logDecryptError( - "Encryption key type mismatch", - inner.type, - encThing.encryptionType, - decryptContext, - ); - return null; - } - - if (inner.type === EncryptionType.AesCbc256_HmacSha256_B64) { - if (encThing.macBytes == null) { - this.logDecryptError("Mac missing", inner.type, encThing.encryptionType, decryptContext); - return null; - } - - const macData = new Uint8Array(encThing.ivBytes.byteLength + encThing.dataBytes.byteLength); - macData.set(new Uint8Array(encThing.ivBytes), 0); - macData.set(new Uint8Array(encThing.dataBytes), encThing.ivBytes.byteLength); - const computedMac = await this.cryptoFunctionService.hmac( - macData, - inner.authenticationKey, - "sha256", - ); - const macsMatch = await this.cryptoFunctionService.compare(encThing.macBytes, computedMac); - if (!macsMatch) { - this.logMacFailed( - "MAC comparison failed. Key or payload has changed.", - inner.type, - encThing.encryptionType, - decryptContext, - ); - return null; - } - - return await this.cryptoFunctionService.aesDecrypt( - encThing.dataBytes, - encThing.ivBytes, - inner.encryptionKey, - "cbc", - ); - } else if (inner.type === EncryptionType.AesCbc256_B64) { - return await this.cryptoFunctionService.aesDecrypt( - encThing.dataBytes, - encThing.ivBytes, - inner.encryptionKey, - "cbc", - ); - } - } - async encapsulateKeyUnsigned( sharedKey: SymmetricCryptoKey, encapsulationKey: Uint8Array, @@ -384,9 +168,6 @@ export class EncryptServiceImplementation implements EncryptService { return new SymmetricCryptoKey(keyBytes); } - /** - * @deprecated Replaced by BulkEncryptService (PM-4154) - */ async decryptItems( items: Decryptable[], key: SymmetricCryptoKey, @@ -403,29 +184,6 @@ export class EncryptServiceImplementation implements EncryptService { return results; } - private async aesEncrypt(data: Uint8Array, key: Aes256CbcHmacKey): Promise { - const obj = new EncryptedObject(); - obj.iv = await this.cryptoFunctionService.randomBytes(16); - obj.data = await this.cryptoFunctionService.aesEncrypt(data, obj.iv, key.encryptionKey); - - 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, key.authenticationKey, "sha256"); - - return obj; - } - - /** - * @deprecated Removed once AesCbc256_B64 support is removed - */ - private async aesEncryptLegacy(data: Uint8Array, key: Aes256CbcKey): Promise { - const obj = new EncryptedObject(); - obj.iv = await this.cryptoFunctionService.randomBytes(16); - obj.data = await this.cryptoFunctionService.aesEncrypt(data, obj.iv, key.encryptionKey); - return obj; - } - private logDecryptError( msg: string, keyEncType: EncryptionType, diff --git a/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts b/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts deleted file mode 100644 index d19de6c0414..00000000000 --- a/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts +++ /dev/null @@ -1,746 +0,0 @@ -import { mockReset, mock } from "jest-mock-extended"; - -import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { EncryptionType } from "@bitwarden/common/platform/enums"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { - Aes256CbcHmacKey, - SymmetricCryptoKey, -} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { CsprngArray } from "@bitwarden/common/types/csprng"; - -import { makeStaticByteArray } from "../../../../spec"; -import { DefaultFeatureFlagValue, FeatureFlag } from "../../../enums/feature-flag.enum"; -import { ServerConfig } from "../../../platform/abstractions/config/server-config"; - -import { EncryptServiceImplementation } from "./encrypt.service.implementation"; - -describe("EncryptService", () => { - const cryptoFunctionService = mock(); - const logService = mock(); - - let encryptService: EncryptServiceImplementation; - - beforeEach(() => { - mockReset(cryptoFunctionService); - mockReset(logService); - - encryptService = new EncryptServiceImplementation(cryptoFunctionService, logService, true); - }); - - describe("wrapSymmetricKey", () => { - it("roundtrip encrypts and decrypts a symmetric key", async () => { - cryptoFunctionService.aesEncrypt.mockResolvedValue(makeStaticByteArray(64, 0)); - cryptoFunctionService.randomBytes.mockResolvedValue(makeStaticByteArray(16) as CsprngArray); - cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(32)); - - const key = new SymmetricCryptoKey(makeStaticByteArray(64)); - const wrappingKey = new SymmetricCryptoKey(makeStaticByteArray(64)); - const encString = await encryptService.wrapSymmetricKey(key, wrappingKey); - expect(encString.encryptionType).toEqual(EncryptionType.AesCbc256_HmacSha256_B64); - expect(encString.data).toEqual(Utils.fromBufferToB64(makeStaticByteArray(64, 0))); - }); - it("fails if key toBeWrapped is null", async () => { - const wrappingKey = new SymmetricCryptoKey(makeStaticByteArray(64)); - await expect(encryptService.wrapSymmetricKey(null, wrappingKey)).rejects.toThrow( - "No keyToBeWrapped provided for wrapping.", - ); - }); - it("fails if wrapping key is null", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(64)); - await expect(encryptService.wrapSymmetricKey(key, null)).rejects.toThrow( - "No wrappingKey provided for wrapping.", - ); - }); - it("fails if type 0 key is provided with flag turned on", async () => { - (encryptService as any).blockType0 = true; - const mock32Key = mock(); - mock32Key.inner.mockReturnValue({ - type: 0, - encryptionKey: makeStaticByteArray(32), - }); - - await expect(encryptService.wrapSymmetricKey(mock32Key, mock32Key)).rejects.toThrow( - "Type 0 encryption is not supported.", - ); - }); - }); - - describe("wrapDecapsulationKey", () => { - it("roundtrip encrypts and decrypts a decapsulation key", async () => { - cryptoFunctionService.aesEncrypt.mockResolvedValue(makeStaticByteArray(64, 0)); - cryptoFunctionService.randomBytes.mockResolvedValue(makeStaticByteArray(16) as CsprngArray); - cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(32)); - - const wrappingKey = new SymmetricCryptoKey(makeStaticByteArray(64)); - const encString = await encryptService.wrapDecapsulationKey( - makeStaticByteArray(64), - wrappingKey, - ); - expect(encString.encryptionType).toEqual(EncryptionType.AesCbc256_HmacSha256_B64); - expect(encString.data).toEqual(Utils.fromBufferToB64(makeStaticByteArray(64, 0))); - }); - it("fails if decapsulation key is null", async () => { - const wrappingKey = new SymmetricCryptoKey(makeStaticByteArray(64)); - await expect(encryptService.wrapDecapsulationKey(null, wrappingKey)).rejects.toThrow( - "No decapsulation key provided for wrapping.", - ); - }); - it("fails if wrapping key is null", async () => { - const decapsulationKey = makeStaticByteArray(64); - await expect(encryptService.wrapDecapsulationKey(decapsulationKey, null)).rejects.toThrow( - "No wrappingKey provided for wrapping.", - ); - }); - it("throws if type 0 key is provided with flag turned on", async () => { - (encryptService as any).blockType0 = true; - const mock32Key = mock(); - mock32Key.inner.mockReturnValue({ - type: 0, - encryptionKey: makeStaticByteArray(32), - }); - - await expect( - encryptService.wrapDecapsulationKey(new Uint8Array(200), mock32Key), - ).rejects.toThrow("Type 0 encryption is not supported."); - }); - }); - - describe("wrapEncapsulationKey", () => { - it("roundtrip encrypts and decrypts an encapsulationKey key", async () => { - cryptoFunctionService.aesEncrypt.mockResolvedValue(makeStaticByteArray(64, 0)); - cryptoFunctionService.randomBytes.mockResolvedValue(makeStaticByteArray(16) as CsprngArray); - cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(32)); - - const wrappingKey = new SymmetricCryptoKey(makeStaticByteArray(64)); - const encString = await encryptService.wrapEncapsulationKey( - makeStaticByteArray(64), - wrappingKey, - ); - expect(encString.encryptionType).toEqual(EncryptionType.AesCbc256_HmacSha256_B64); - expect(encString.data).toEqual(Utils.fromBufferToB64(makeStaticByteArray(64, 0))); - }); - it("fails if encapsulation key is null", async () => { - const wrappingKey = new SymmetricCryptoKey(makeStaticByteArray(64)); - await expect(encryptService.wrapEncapsulationKey(null, wrappingKey)).rejects.toThrow( - "No encapsulation key provided for wrapping.", - ); - }); - it("fails if wrapping key is null", async () => { - const encapsulationKey = makeStaticByteArray(64); - await expect(encryptService.wrapEncapsulationKey(encapsulationKey, null)).rejects.toThrow( - "No wrappingKey provided for wrapping.", - ); - }); - it("throws if type 0 key is provided with flag turned on", async () => { - (encryptService as any).blockType0 = true; - const mock32Key = mock(); - mock32Key.inner.mockReturnValue({ - type: 0, - encryptionKey: makeStaticByteArray(32), - }); - - await expect( - encryptService.wrapEncapsulationKey(new Uint8Array(200), mock32Key), - ).rejects.toThrow("Type 0 encryption is not supported."); - }); - }); - - describe("onServerConfigChange", () => { - const newConfig = mock(); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it("updates internal flag with default value when not present in config", () => { - encryptService.onServerConfigChange(newConfig); - - expect((encryptService as any).blockType0).toBe( - DefaultFeatureFlagValue[FeatureFlag.PM17987_BlockType0], - ); - }); - - test.each([true, false])("updates internal flag with value in config", (expectedValue) => { - newConfig.featureStates = { [FeatureFlag.PM17987_BlockType0]: expectedValue }; - - encryptService.onServerConfigChange(newConfig); - - expect((encryptService as any).blockType0).toBe(expectedValue); - }); - }); - - describe("encrypt", () => { - it("throws if no key is provided", () => { - return expect(encryptService.encrypt(null, null)).rejects.toThrow( - "No encryption key provided.", - ); - }); - - it("throws if type 0 key is provided with flag turned on", async () => { - (encryptService as any).blockType0 = true; - const key = new SymmetricCryptoKey(makeStaticByteArray(32)); - const mock32Key = mock(); - mock32Key.inner.mockReturnValue({ - type: 0, - encryptionKey: makeStaticByteArray(32), - }); - - await expect(encryptService.encrypt(null!, key)).rejects.toThrow( - "Type 0 encryption is not supported.", - ); - await expect(encryptService.encrypt(null!, mock32Key)).rejects.toThrow( - "Type 0 encryption is not supported.", - ); - - const plainValue = "data"; - await expect(encryptService.encrypt(plainValue, key)).rejects.toThrow( - "Type 0 encryption is not supported.", - ); - await expect(encryptService.encrypt(plainValue, mock32Key)).rejects.toThrow( - "Type 0 encryption is not supported.", - ); - }); - - it("returns null if no data is provided with valid key", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(64)); - const actual = await encryptService.encrypt(null, key); - expect(actual).toBeNull(); - }); - - it("creates an EncString for Aes256Cbc", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(32)); - const plainValue = "data"; - cryptoFunctionService.aesEncrypt.mockResolvedValue(makeStaticByteArray(4, 100)); - cryptoFunctionService.randomBytes.mockResolvedValue(makeStaticByteArray(16) as CsprngArray); - const result = await encryptService.encrypt(plainValue, key); - expect(cryptoFunctionService.aesEncrypt).toHaveBeenCalledWith( - Utils.fromByteStringToArray(plainValue), - makeStaticByteArray(16), - makeStaticByteArray(32), - ); - expect(cryptoFunctionService.hmac).not.toHaveBeenCalled(); - - expect(Utils.fromB64ToArray(result.data).length).toEqual(4); - expect(Utils.fromB64ToArray(result.iv).length).toEqual(16); - }); - - it("creates an EncString for Aes256Cbc_HmacSha256_B64", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(64)); - const plainValue = "data"; - cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(32)); - cryptoFunctionService.aesEncrypt.mockResolvedValue(makeStaticByteArray(4, 100)); - cryptoFunctionService.randomBytes.mockResolvedValue(makeStaticByteArray(16) as CsprngArray); - const result = await encryptService.encrypt(plainValue, key); - expect(cryptoFunctionService.aesEncrypt).toHaveBeenCalledWith( - Utils.fromByteStringToArray(plainValue), - makeStaticByteArray(16), - makeStaticByteArray(32), - ); - - const macData = new Uint8Array(16 + 4); - macData.set(makeStaticByteArray(16)); - macData.set(makeStaticByteArray(4, 100), 16); - expect(cryptoFunctionService.hmac).toHaveBeenCalledWith( - macData, - makeStaticByteArray(32, 32), - "sha256", - ); - - expect(Utils.fromB64ToArray(result.data).length).toEqual(4); - expect(Utils.fromB64ToArray(result.iv).length).toEqual(16); - expect(Utils.fromB64ToArray(result.mac).length).toEqual(32); - }); - }); - - describe("encryptToBytes", () => { - const plainValue = makeStaticByteArray(16, 1); - - it("throws if no key is provided", () => { - return expect(encryptService.encryptToBytes(plainValue, null)).rejects.toThrow( - "No encryption key", - ); - }); - - it("throws if type 0 key provided with flag turned on", async () => { - (encryptService as any).blockType0 = true; - const key = new SymmetricCryptoKey(makeStaticByteArray(32)); - const mock32Key = mock(); - mock32Key.inner.mockReturnValue({ - type: 0, - encryptionKey: makeStaticByteArray(32), - }); - - await expect(encryptService.encryptToBytes(plainValue, key)).rejects.toThrow( - "Type 0 encryption is not supported.", - ); - - await expect(encryptService.encryptToBytes(plainValue, mock32Key)).rejects.toThrow( - "Type 0 encryption is not supported.", - ); - }); - - it("encrypts data with provided Aes256Cbc key and returns correct encbuffer", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(32, 0)); - const iv = makeStaticByteArray(16, 80); - const cipherText = makeStaticByteArray(20, 150); - cryptoFunctionService.randomBytes.mockResolvedValue(iv as CsprngArray); - cryptoFunctionService.aesEncrypt.mockResolvedValue(cipherText); - - const actual = await encryptService.encryptToBytes(plainValue, key); - const expectedBytes = new Uint8Array(1 + iv.byteLength + cipherText.byteLength); - expectedBytes.set([EncryptionType.AesCbc256_B64]); - expectedBytes.set(iv, 1); - expectedBytes.set(cipherText, 1 + iv.byteLength); - - expect(actual.buffer).toEqualBuffer(expectedBytes); - }); - - it("encrypts data with provided Aes256Cbc_HmacSha256 key and returns correct encbuffer", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(64, 0)); - const iv = makeStaticByteArray(16, 80); - const mac = makeStaticByteArray(32, 100); - const cipherText = makeStaticByteArray(20, 150); - cryptoFunctionService.randomBytes.mockResolvedValue(iv as CsprngArray); - cryptoFunctionService.aesEncrypt.mockResolvedValue(cipherText); - cryptoFunctionService.hmac.mockResolvedValue(mac); - - const actual = await encryptService.encryptToBytes(plainValue, key); - const expectedBytes = new Uint8Array( - 1 + iv.byteLength + mac.byteLength + cipherText.byteLength, - ); - expectedBytes.set([EncryptionType.AesCbc256_HmacSha256_B64]); - expectedBytes.set(iv, 1); - expectedBytes.set(mac, 1 + iv.byteLength); - expectedBytes.set(cipherText, 1 + iv.byteLength + mac.byteLength); - - expect(actual.buffer).toEqualBuffer(expectedBytes); - }); - }); - - describe("decryptToBytes", () => { - const encType = EncryptionType.AesCbc256_HmacSha256_B64; - const key = new SymmetricCryptoKey(makeStaticByteArray(64, 100)); - const computedMac = new Uint8Array(1); - const encBuffer = new EncArrayBuffer(makeStaticByteArray(60, encType)); - - beforeEach(() => { - cryptoFunctionService.hmac.mockResolvedValue(computedMac); - }); - - it("throws if no key is provided", () => { - return expect(encryptService.decryptToBytes(encBuffer, null)).rejects.toThrow( - "No encryption key", - ); - }); - - it("throws if no encrypted value is provided", () => { - return expect(encryptService.decryptToBytes(null, key)).rejects.toThrow( - "Nothing provided for decryption", - ); - }); - - it("decrypts data with provided key for Aes256CbcHmac", async () => { - const decryptedBytes = makeStaticByteArray(10, 200); - - cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(1)); - cryptoFunctionService.compare.mockResolvedValue(true); - cryptoFunctionService.aesDecrypt.mockResolvedValueOnce(decryptedBytes); - - const actual = await encryptService.decryptToBytes(encBuffer, key); - - expect(cryptoFunctionService.aesDecrypt).toBeCalledWith( - expect.toEqualBuffer(encBuffer.dataBytes), - expect.toEqualBuffer(encBuffer.ivBytes), - expect.toEqualBuffer(key.inner().encryptionKey), - "cbc", - ); - - expect(actual).toEqualBuffer(decryptedBytes); - }); - - it("decrypts data with provided key for Aes256Cbc", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(32, 0)); - const encBuffer = new EncArrayBuffer(makeStaticByteArray(60, EncryptionType.AesCbc256_B64)); - const decryptedBytes = makeStaticByteArray(10, 200); - - cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(1)); - cryptoFunctionService.compare.mockResolvedValue(true); - cryptoFunctionService.aesDecrypt.mockResolvedValueOnce(decryptedBytes); - - const actual = await encryptService.decryptToBytes(encBuffer, key); - - expect(cryptoFunctionService.aesDecrypt).toBeCalledWith( - expect.toEqualBuffer(encBuffer.dataBytes), - expect.toEqualBuffer(encBuffer.ivBytes), - expect.toEqualBuffer(key.inner().encryptionKey), - "cbc", - ); - - expect(actual).toEqualBuffer(decryptedBytes); - }); - - it("compares macs using CryptoFunctionService", async () => { - const expectedMacData = new Uint8Array( - encBuffer.ivBytes.byteLength + encBuffer.dataBytes.byteLength, - ); - expectedMacData.set(new Uint8Array(encBuffer.ivBytes)); - expectedMacData.set(new Uint8Array(encBuffer.dataBytes), encBuffer.ivBytes.byteLength); - - await encryptService.decryptToBytes(encBuffer, key); - - expect(cryptoFunctionService.hmac).toBeCalledWith( - expect.toEqualBuffer(expectedMacData), - (key.inner() as Aes256CbcHmacKey).authenticationKey, - "sha256", - ); - - expect(cryptoFunctionService.compare).toBeCalledWith( - expect.toEqualBuffer(encBuffer.macBytes), - expect.toEqualBuffer(computedMac), - ); - }); - - it("returns null if macs don't match", async () => { - cryptoFunctionService.compare.mockResolvedValue(false); - - const actual = await encryptService.decryptToBytes(encBuffer, key); - expect(cryptoFunctionService.compare).toHaveBeenCalled(); - expect(cryptoFunctionService.aesDecrypt).not.toHaveBeenCalled(); - expect(actual).toBeNull(); - }); - - it("returns null if mac could not be calculated", async () => { - cryptoFunctionService.hmac.mockResolvedValue(null); - - const actual = await encryptService.decryptToBytes(encBuffer, key); - expect(cryptoFunctionService.hmac).toHaveBeenCalled(); - expect(cryptoFunctionService.aesDecrypt).not.toHaveBeenCalled(); - expect(actual).toBeNull(); - }); - - it("returns null if key is Aes256Cbc but encbuffer is Aes256Cbc_HmacSha256", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(32, 0)); - cryptoFunctionService.compare.mockResolvedValue(true); - - const actual = await encryptService.decryptToBytes(encBuffer, key); - - expect(actual).toBeNull(); - expect(cryptoFunctionService.aesDecrypt).not.toHaveBeenCalled(); - }); - - it("returns null if key is Aes256Cbc_HmacSha256 but encbuffer is Aes256Cbc", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(64, 0)); - cryptoFunctionService.compare.mockResolvedValue(true); - const buffer = new EncArrayBuffer(makeStaticByteArray(200, EncryptionType.AesCbc256_B64)); - const actual = await encryptService.decryptToBytes(buffer, key); - - expect(actual).toBeNull(); - expect(cryptoFunctionService.aesDecrypt).not.toHaveBeenCalled(); - }); - }); - - describe("decryptToUtf8", () => { - it("throws if no key is provided", () => { - return expect(encryptService.decryptToUtf8(null, null)).rejects.toThrow( - "No key provided for decryption.", - ); - }); - - it("decrypts data with provided key for AesCbc256_HmacSha256", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(64, 0)); - const encString = new EncString(EncryptionType.AesCbc256_HmacSha256_B64, "data", "iv", "mac"); - cryptoFunctionService.aesDecryptFastParameters.mockReturnValue({ - macData: makeStaticByteArray(32, 0), - macKey: makeStaticByteArray(32, 0), - mac: makeStaticByteArray(32, 0), - } as any); - cryptoFunctionService.hmacFast.mockResolvedValue(makeStaticByteArray(32, 0)); - cryptoFunctionService.compareFast.mockResolvedValue(true); - cryptoFunctionService.aesDecryptFast.mockResolvedValue("data"); - - const actual = await encryptService.decryptToUtf8(encString, key); - expect(actual).toEqual("data"); - expect(cryptoFunctionService.compareFast).toHaveBeenCalledWith( - makeStaticByteArray(32, 0), - makeStaticByteArray(32, 0), - ); - }); - - it("decrypts data with provided key for AesCbc256", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(32, 0)); - const encString = new EncString(EncryptionType.AesCbc256_B64, "data"); - cryptoFunctionService.aesDecryptFastParameters.mockReturnValue({ - macData: makeStaticByteArray(32, 0), - macKey: makeStaticByteArray(32, 0), - mac: makeStaticByteArray(32, 0), - } as any); - cryptoFunctionService.hmacFast.mockResolvedValue(makeStaticByteArray(32, 0)); - cryptoFunctionService.compareFast.mockResolvedValue(true); - cryptoFunctionService.aesDecryptFast.mockResolvedValue("data"); - - const actual = await encryptService.decryptToUtf8(encString, key); - expect(actual).toEqual("data"); - expect(cryptoFunctionService.compareFast).not.toHaveBeenCalled(); - }); - - it("returns null if key is AesCbc256_HMAC but encstring is AesCbc256", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(64, 0)); - const encString = new EncString(EncryptionType.AesCbc256_B64, "data"); - - const actual = await encryptService.decryptToUtf8(encString, key); - expect(actual).toBeNull(); - expect(logService.error).toHaveBeenCalled(); - }); - - it("returns null if key is AesCbc256 but encstring is AesCbc256_HMAC", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(32, 0)); - const encString = new EncString(EncryptionType.AesCbc256_HmacSha256_B64, "data", "iv", "mac"); - - const actual = await encryptService.decryptToUtf8(encString, key); - expect(actual).toBeNull(); - expect(logService.error).toHaveBeenCalled(); - }); - - it("returns null if macs don't match", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(64, 0)); - const encString = new EncString(EncryptionType.AesCbc256_HmacSha256_B64, "data", "iv", "mac"); - cryptoFunctionService.aesDecryptFastParameters.mockReturnValue({ - macData: makeStaticByteArray(32, 0), - macKey: makeStaticByteArray(32, 0), - mac: makeStaticByteArray(32, 0), - } as any); - cryptoFunctionService.hmacFast.mockResolvedValue(makeStaticByteArray(32, 0)); - cryptoFunctionService.compareFast.mockResolvedValue(false); - cryptoFunctionService.aesDecryptFast.mockResolvedValue("data"); - - const actual = await encryptService.decryptToUtf8(encString, key); - expect(actual).toBeNull(); - }); - }); - - describe("decryptToUtf8", () => { - it("throws if no key is provided", () => { - return expect(encryptService.decryptToUtf8(null, null)).rejects.toThrow( - "No key provided for decryption.", - ); - }); - it("returns null if key is mac key but encstring has no mac", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(64, 0)); - const encString = new EncString(EncryptionType.AesCbc256_B64, "data"); - - const actual = await encryptService.decryptToUtf8(encString, key); - expect(actual).toBeNull(); - expect(logService.error).toHaveBeenCalled(); - }); - }); - - describe("encryptString", () => { - it("is a proxy to encrypt", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(64)); - const plainValue = "data"; - encryptService.encrypt = jest.fn(); - await encryptService.encryptString(plainValue, key); - expect(encryptService.encrypt).toHaveBeenCalledWith(plainValue, key); - }); - }); - - describe("encryptBytes", () => { - it("is a proxy to encrypt", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(64)); - const plainValue = makeStaticByteArray(16, 1); - encryptService.encrypt = jest.fn(); - await encryptService.encryptBytes(plainValue, key); - expect(encryptService.encrypt).toHaveBeenCalledWith(plainValue, key); - }); - }); - - describe("encryptFileData", () => { - it("is a proxy to encryptToBytes", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(64)); - const plainValue = makeStaticByteArray(16, 1); - encryptService.encryptToBytes = jest.fn(); - await encryptService.encryptFileData(plainValue, key); - expect(encryptService.encryptToBytes).toHaveBeenCalledWith(plainValue, key); - }); - }); - - describe("decryptString", () => { - it("is a proxy to decryptToUtf8", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(64)); - const encString = new EncString(EncryptionType.AesCbc256_B64, "data"); - encryptService.decryptToUtf8 = jest.fn(); - await encryptService.decryptString(encString, key); - expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encString, key); - }); - }); - - describe("decryptBytes", () => { - it("is a proxy to decryptToBytes", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(64)); - const encString = new EncString(EncryptionType.AesCbc256_B64, "data"); - encryptService.decryptToBytes = jest.fn(); - await encryptService.decryptBytes(encString, key); - expect(encryptService.decryptToBytes).toHaveBeenCalledWith(encString, key); - }); - }); - - describe("decryptFileData", () => { - it("is a proxy to decrypt", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(64)); - const encString = new EncArrayBuffer(makeStaticByteArray(60, EncryptionType.AesCbc256_B64)); - encryptService.decryptToBytes = jest.fn(); - await encryptService.decryptFileData(encString, key); - expect(encryptService.decryptToBytes).toHaveBeenCalledWith(encString, key); - }); - }); - - describe("unwrapDecapsulationKey", () => { - it("is a proxy to decryptBytes", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(64)); - const encString = new EncString(EncryptionType.AesCbc256_B64, "data"); - encryptService.decryptBytes = jest.fn(); - await encryptService.unwrapDecapsulationKey(encString, key); - expect(encryptService.decryptBytes).toHaveBeenCalledWith(encString, key); - }); - }); - - describe("unwrapEncapsulationKey", () => { - it("is a proxy to decryptBytes", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(64)); - const encString = new EncString(EncryptionType.AesCbc256_B64, "data"); - encryptService.decryptBytes = jest.fn(); - await encryptService.unwrapEncapsulationKey(encString, key); - expect(encryptService.decryptBytes).toHaveBeenCalledWith(encString, key); - }); - }); - - describe("unwrapSymmetricKey", () => { - it("is a proxy to decryptBytes", async () => { - const key = new SymmetricCryptoKey(makeStaticByteArray(64)); - const encString = new EncString(EncryptionType.AesCbc256_B64, "data"); - const jestFn = jest.fn(); - jestFn.mockResolvedValue(new Uint8Array(64)); - encryptService.decryptBytes = jestFn; - await encryptService.unwrapSymmetricKey(encString, key); - expect(encryptService.decryptBytes).toHaveBeenCalledWith(encString, key); - }); - }); - - describe("rsa", () => { - const data = makeStaticByteArray(64, 100); - const testKey = new SymmetricCryptoKey(data); - const encryptedData = makeStaticByteArray(10, 150); - const publicKey = makeStaticByteArray(10, 200); - const privateKey = makeStaticByteArray(10, 250); - const encString = makeEncString(encryptedData); - - function makeEncString(data: Uint8Array): EncString { - return new EncString(EncryptionType.Rsa2048_OaepSha1_B64, Utils.fromBufferToB64(data)); - } - - describe("encapsulateKeyUnsigned", () => { - it("throws if no data is provided", () => { - return expect(encryptService.encapsulateKeyUnsigned(null, publicKey)).rejects.toThrow( - "No sharedKey provided for encapsulation", - ); - }); - - it("throws if no public key is provided", () => { - return expect(encryptService.encapsulateKeyUnsigned(testKey, null)).rejects.toThrow( - "No public key", - ); - }); - - it("encrypts data with provided key", async () => { - cryptoFunctionService.rsaEncrypt.mockResolvedValue(encryptedData); - - const actual = await encryptService.encapsulateKeyUnsigned(testKey, publicKey); - - expect(cryptoFunctionService.rsaEncrypt).toBeCalledWith( - expect.toEqualBuffer(testKey.toEncoded()), - expect.toEqualBuffer(publicKey), - "sha1", - ); - - expect(actual).toEqual(encString); - expect(actual.dataBytes).toEqualBuffer(encryptedData); - }); - - it("throws if no data was provided", () => { - return expect(encryptService.rsaEncrypt(null, new Uint8Array(32))).rejects.toThrow( - "No data provided for encryption", - ); - }); - }); - - describe("decapsulateKeyUnsigned", () => { - it("throws if no data is provided", () => { - return expect(encryptService.decapsulateKeyUnsigned(null, privateKey)).rejects.toThrow( - "No data", - ); - }); - - it("throws if no private key is provided", () => { - return expect(encryptService.decapsulateKeyUnsigned(encString, null)).rejects.toThrow( - "No private key", - ); - }); - - it.each([EncryptionType.AesCbc256_B64, EncryptionType.AesCbc256_HmacSha256_B64])( - "throws if encryption type is %s", - async (encType) => { - encString.encryptionType = encType; - - await expect( - encryptService.decapsulateKeyUnsigned(encString, privateKey), - ).rejects.toThrow("Invalid encryption type"); - }, - ); - - it("decrypts data with provided key", async () => { - cryptoFunctionService.rsaDecrypt.mockResolvedValue(data); - - const actual = await encryptService.decapsulateKeyUnsigned(makeEncString(data), privateKey); - - expect(cryptoFunctionService.rsaDecrypt).toBeCalledWith( - expect.toEqualBuffer(data), - expect.toEqualBuffer(privateKey), - "sha1", - ); - - expect(actual.toEncoded()).toEqualBuffer(data); - }); - }); - }); - - describe("hash", () => { - it("hashes a string and returns b64", async () => { - cryptoFunctionService.hash.mockResolvedValue(Uint8Array.from([1, 2, 3])); - expect(await encryptService.hash("test", "sha256")).toEqual("AQID"); - expect(cryptoFunctionService.hash).toHaveBeenCalledWith("test", "sha256"); - }); - }); - - describe("decryptItems", () => { - it("returns empty array if no items are provided", async () => { - const key = mock(); - const actual = await encryptService.decryptItems(null, key); - expect(actual).toEqual([]); - }); - - it("returns items decrypted with provided key", async () => { - const key = mock(); - const decryptable = { - decrypt: jest.fn().mockResolvedValue("decrypted"), - }; - const items = [decryptable]; - const actual = await encryptService.decryptItems(items as any, key); - expect(actual).toEqual(["decrypted"]); - expect(decryptable.decrypt).toHaveBeenCalledWith(key); - }); - }); -}); diff --git a/libs/common/src/key-management/crypto/services/encrypt.worker.ts b/libs/common/src/key-management/crypto/services/encrypt.worker.ts deleted file mode 100644 index e5aeb06560b..00000000000 --- a/libs/common/src/key-management/crypto/services/encrypt.worker.ts +++ /dev/null @@ -1,81 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Jsonify } from "type-fest"; - -import { ServerConfig } from "../../../platform/abstractions/config/server-config"; -import { LogService } from "../../../platform/abstractions/log.service"; -import { Decryptable } from "../../../platform/interfaces/decryptable.interface"; -import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; -import { ConsoleLogService } from "../../../platform/services/console-log.service"; -import { ContainerService } from "../../../platform/services/container.service"; -import { getClassInitializer } from "../../../platform/services/cryptography/get-class-initializer"; -import { - DECRYPT_COMMAND, - SET_CONFIG_COMMAND, - ParsedDecryptCommandData, -} from "../types/worker-command.type"; - -import { EncryptServiceImplementation } from "./encrypt.service.implementation"; -import { WebCryptoFunctionService } from "./web-crypto-function.service"; - -const workerApi: Worker = self as any; - -let inited = false; -let encryptService: EncryptServiceImplementation; -let logService: LogService; - -/** - * Bootstrap the worker environment with services required for decryption - */ -export function init() { - const cryptoFunctionService = new WebCryptoFunctionService(self); - logService = new ConsoleLogService(false); - encryptService = new EncryptServiceImplementation(cryptoFunctionService, logService, true); - - const bitwardenContainerService = new ContainerService(null, encryptService); - bitwardenContainerService.attachToGlobal(self); - - inited = true; -} - -/** - * Listen for messages and decrypt their contents - */ -workerApi.addEventListener("message", async (event: { data: string }) => { - if (!inited) { - init(); - } - - const request: { - command: string; - } = JSON.parse(event.data); - - switch (request.command) { - case DECRYPT_COMMAND: - return await handleDecrypt(request as unknown as ParsedDecryptCommandData); - case SET_CONFIG_COMMAND: { - const newConfig = (request as unknown as { newConfig: Jsonify }).newConfig; - return await handleSetConfig(newConfig); - } - default: - logService.error(`[EncryptWorker] unknown worker command`, request.command, request); - } -}); - -async function handleDecrypt(request: ParsedDecryptCommandData) { - const key = SymmetricCryptoKey.fromJSON(request.key); - const items = request.items.map((jsonItem) => { - const initializer = getClassInitializer>(jsonItem.initializerKey); - return initializer(jsonItem); - }); - const result = await encryptService.decryptItems(items, key); - - workerApi.postMessage({ - id: request.id, - items: JSON.stringify(result), - }); -} - -async function handleSetConfig(newConfig: Jsonify) { - encryptService.onServerConfigChange(ServerConfig.fromJSON(newConfig)); -} diff --git a/libs/common/src/key-management/crypto/services/fallback-bulk-encrypt.service.spec.ts b/libs/common/src/key-management/crypto/services/fallback-bulk-encrypt.service.spec.ts deleted file mode 100644 index c016724e652..00000000000 --- a/libs/common/src/key-management/crypto/services/fallback-bulk-encrypt.service.spec.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { mock } from "jest-mock-extended"; - -import { ServerConfig } from "../../../platform/abstractions/config/server-config"; -import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; -import { BulkEncryptService } from "../abstractions/bulk-encrypt.service"; -import { EncryptService } from "../abstractions/encrypt.service"; - -import { FallbackBulkEncryptService } from "./fallback-bulk-encrypt.service"; - -describe("FallbackBulkEncryptService", () => { - const encryptService = mock(); - const featureFlagEncryptService = mock(); - const serverConfig = mock(); - - let sut: FallbackBulkEncryptService; - - beforeEach(() => { - sut = new FallbackBulkEncryptService(encryptService); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - describe("decryptItems", () => { - const key = mock(); - const mockItems = [{ id: "guid", name: "encryptedValue" }] as any[]; - const mockDecryptedItems = [{ id: "guid", name: "decryptedValue" }] as any[]; - - it("calls decryptItems on featureFlagEncryptService when it is set", async () => { - featureFlagEncryptService.decryptItems.mockResolvedValue(mockDecryptedItems); - await sut.setFeatureFlagEncryptService(featureFlagEncryptService); - - const result = await sut.decryptItems(mockItems, key); - - expect(featureFlagEncryptService.decryptItems).toHaveBeenCalledWith(mockItems, key); - expect(encryptService.decryptItems).not.toHaveBeenCalled(); - expect(result).toEqual(mockDecryptedItems); - }); - - it("calls decryptItems on encryptService when featureFlagEncryptService is not set", async () => { - encryptService.decryptItems.mockResolvedValue(mockDecryptedItems); - - const result = await sut.decryptItems(mockItems, key); - - expect(encryptService.decryptItems).toHaveBeenCalledWith(mockItems, key); - expect(result).toEqual(mockDecryptedItems); - }); - }); - - describe("setFeatureFlagEncryptService", () => { - it("sets the featureFlagEncryptService property", async () => { - await sut.setFeatureFlagEncryptService(featureFlagEncryptService); - - expect((sut as any).featureFlagEncryptService).toBe(featureFlagEncryptService); - }); - - it("does not call onServerConfigChange when currentServerConfig is undefined", async () => { - await sut.setFeatureFlagEncryptService(featureFlagEncryptService); - - expect(featureFlagEncryptService.onServerConfigChange).not.toHaveBeenCalled(); - expect((sut as any).featureFlagEncryptService).toBe(featureFlagEncryptService); - }); - - it("calls onServerConfigChange with currentServerConfig when it is defined", async () => { - sut.onServerConfigChange(serverConfig); - - await sut.setFeatureFlagEncryptService(featureFlagEncryptService); - - expect(featureFlagEncryptService.onServerConfigChange).toHaveBeenCalledWith(serverConfig); - expect((sut as any).featureFlagEncryptService).toBe(featureFlagEncryptService); - }); - }); - - describe("onServerConfigChange", () => { - it("updates internal currentServerConfig to new config", async () => { - sut.onServerConfigChange(serverConfig); - - expect((sut as any).currentServerConfig).toBe(serverConfig); - }); - - it("calls onServerConfigChange on featureFlagEncryptService when it is set", async () => { - await sut.setFeatureFlagEncryptService(featureFlagEncryptService); - - sut.onServerConfigChange(serverConfig); - - expect(featureFlagEncryptService.onServerConfigChange).toHaveBeenCalledWith(serverConfig); - expect(encryptService.onServerConfigChange).not.toHaveBeenCalled(); - }); - - it("calls onServerConfigChange on encryptService when featureFlagEncryptService is not set", () => { - sut.onServerConfigChange(serverConfig); - - expect(encryptService.onServerConfigChange).toHaveBeenCalledWith(serverConfig); - }); - }); -}); diff --git a/libs/common/src/key-management/crypto/services/fallback-bulk-encrypt.service.ts b/libs/common/src/key-management/crypto/services/fallback-bulk-encrypt.service.ts deleted file mode 100644 index 7eefa896e6a..00000000000 --- a/libs/common/src/key-management/crypto/services/fallback-bulk-encrypt.service.ts +++ /dev/null @@ -1,46 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service"; -import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface"; -import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; - -import { ServerConfig } from "../../../platform/abstractions/config/server-config"; -import { EncryptService } from "../abstractions/encrypt.service"; - -/** - * @deprecated For the feature flag from PM-4154, remove once feature is rolled out - */ -export class FallbackBulkEncryptService implements BulkEncryptService { - private featureFlagEncryptService: BulkEncryptService; - private currentServerConfig: ServerConfig | undefined = undefined; - - constructor(protected encryptService: EncryptService) {} - - /** - * Decrypts items using a web worker if the environment supports it. - * Will fall back to the main thread if the window object is not available. - */ - async decryptItems( - items: Decryptable[], - key: SymmetricCryptoKey, - ): Promise { - if (this.featureFlagEncryptService != null) { - return await this.featureFlagEncryptService.decryptItems(items, key); - } else { - return await this.encryptService.decryptItems(items, key); - } - } - - async setFeatureFlagEncryptService(featureFlagEncryptService: BulkEncryptService) { - if (this.currentServerConfig !== undefined) { - featureFlagEncryptService.onServerConfigChange(this.currentServerConfig); - } - this.featureFlagEncryptService = featureFlagEncryptService; - } - - onServerConfigChange(newConfig: ServerConfig): void { - this.currentServerConfig = newConfig; - (this.featureFlagEncryptService ?? this.encryptService).onServerConfigChange(newConfig); - } -} diff --git a/libs/common/src/key-management/crypto/services/multithread-encrypt.service.implementation.spec.ts b/libs/common/src/key-management/crypto/services/multithread-encrypt.service.implementation.spec.ts deleted file mode 100644 index bb966c32507..00000000000 --- a/libs/common/src/key-management/crypto/services/multithread-encrypt.service.implementation.spec.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { mock } from "jest-mock-extended"; -import * as rxjs from "rxjs"; - -import { ServerConfig } from "../../../platform/abstractions/config/server-config"; -import { LogService } from "../../../platform/abstractions/log.service"; -import { Decryptable } from "../../../platform/interfaces/decryptable.interface"; -import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; -import { CryptoFunctionService } from "../abstractions/crypto-function.service"; -import { buildSetConfigMessage } from "../types/worker-command.type"; - -import { EncryptServiceImplementation } from "./encrypt.service.implementation"; -import { MultithreadEncryptServiceImplementation } from "./multithread-encrypt.service.implementation"; - -describe("MultithreadEncryptServiceImplementation", () => { - const cryptoFunctionService = mock(); - const logService = mock(); - const serverConfig = mock(); - - let sut: MultithreadEncryptServiceImplementation; - - beforeEach(() => { - sut = new MultithreadEncryptServiceImplementation(cryptoFunctionService, logService, true); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - describe("decryptItems", () => { - const key = mock(); - const mockWorker = mock(); - - beforeEach(() => { - // Mock creating a worker. - global.Worker = jest.fn().mockImplementation(() => mockWorker); - global.URL = jest.fn().mockImplementation(() => "url") as unknown as typeof URL; - global.URL.createObjectURL = jest.fn().mockReturnValue("blob:url"); - global.URL.revokeObjectURL = jest.fn(); - global.URL.canParse = jest.fn().mockReturnValue(true); - - // Mock the workers returned response. - const mockMessageEvent = { - id: "mock-guid", - data: ["decrypted1", "decrypted2"], - }; - const mockMessageEvent$ = rxjs.from([mockMessageEvent]); - jest.spyOn(rxjs, "fromEvent").mockReturnValue(mockMessageEvent$); - }); - - it("returns empty array if items is null", async () => { - const items = null as unknown as Decryptable[]; - const result = await sut.decryptItems(items, key); - expect(result).toEqual([]); - }); - - it("returns empty array if items is empty", async () => { - const result = await sut.decryptItems([], key); - expect(result).toEqual([]); - }); - - it("creates worker if none exists", async () => { - // Make sure currentServerConfig is undefined so a SetConfigMessage is not sent. - (sut as any).currentServerConfig = undefined; - - await sut.decryptItems([mock>(), mock>()], key); - - expect(global.Worker).toHaveBeenCalled(); - expect(mockWorker.postMessage).toHaveBeenCalledTimes(1); - expect(mockWorker.postMessage).not.toHaveBeenCalledWith( - buildSetConfigMessage({ newConfig: serverConfig }), - ); - }); - - it("sends a SetConfigMessage to the new worker when there is a current server config", async () => { - // Populate currentServerConfig so a SetConfigMessage is sent. - (sut as any).currentServerConfig = serverConfig; - - await sut.decryptItems([mock>(), mock>()], key); - - expect(global.Worker).toHaveBeenCalled(); - expect(mockWorker.postMessage).toHaveBeenCalledTimes(2); - expect(mockWorker.postMessage).toHaveBeenCalledWith( - buildSetConfigMessage({ newConfig: serverConfig }), - ); - }); - - it("does not create worker if one exists", async () => { - (sut as any).currentServerConfig = serverConfig; - (sut as any).worker = mockWorker; - - await sut.decryptItems([mock>(), mock>()], key); - - expect(global.Worker).not.toHaveBeenCalled(); - expect(mockWorker.postMessage).toHaveBeenCalledTimes(1); - expect(mockWorker.postMessage).not.toHaveBeenCalledWith( - buildSetConfigMessage({ newConfig: serverConfig }), - ); - }); - }); - - describe("onServerConfigChange", () => { - it("updates internal currentServerConfig to new config and calls super", () => { - const superSpy = jest.spyOn(EncryptServiceImplementation.prototype, "onServerConfigChange"); - - sut.onServerConfigChange(serverConfig); - - expect(superSpy).toHaveBeenCalledWith(serverConfig); - expect((sut as any).currentServerConfig).toBe(serverConfig); - }); - - it("sends config update to worker if worker exists", () => { - const mockWorker = mock(); - (sut as any).worker = mockWorker; - - sut.onServerConfigChange(serverConfig); - - expect(mockWorker.postMessage).toHaveBeenCalledTimes(1); - expect(mockWorker.postMessage).toHaveBeenCalledWith( - buildSetConfigMessage({ newConfig: serverConfig }), - ); - }); - }); -}); diff --git a/libs/common/src/key-management/crypto/services/multithread-encrypt.service.implementation.ts b/libs/common/src/key-management/crypto/services/multithread-encrypt.service.implementation.ts deleted file mode 100644 index 589450b23cc..00000000000 --- a/libs/common/src/key-management/crypto/services/multithread-encrypt.service.implementation.ts +++ /dev/null @@ -1,114 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { defaultIfEmpty, filter, firstValueFrom, fromEvent, map, Subject, takeUntil } from "rxjs"; -import { Jsonify } from "type-fest"; - -import { Decryptable } from "@bitwarden/common/platform/interfaces/decryptable.interface"; -import { InitializerMetadata } from "@bitwarden/common/platform/interfaces/initializer-metadata.interface"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { getClassInitializer } from "@bitwarden/common/platform/services/cryptography/get-class-initializer"; - -import { ServerConfig } from "../../../platform/abstractions/config/server-config"; -import { buildDecryptMessage, buildSetConfigMessage } from "../types/worker-command.type"; - -import { EncryptServiceImplementation } from "./encrypt.service.implementation"; - -// TTL (time to live) is not strictly required but avoids tying up memory resources if inactive -const workerTTL = 3 * 60000; // 3 minutes - -/** - * @deprecated Replaced by BulkEncryptionService (PM-4154) - */ -export class MultithreadEncryptServiceImplementation extends EncryptServiceImplementation { - private worker: Worker; - private timeout: any; - private currentServerConfig: ServerConfig | undefined = undefined; - - private clear$ = new Subject(); - - /** - * Sends items to a web worker to decrypt them. - * This utilises multithreading to decrypt items faster without interrupting other operations (e.g. updating UI). - */ - async decryptItems( - items: Decryptable[], - key: SymmetricCryptoKey, - ): Promise { - if (items == null || items.length < 1) { - return []; - } - - this.logService.info("Starting decryption using multithreading"); - - if (this.worker == null) { - this.worker = new Worker( - new URL( - /* webpackChunkName: 'encrypt-worker' */ - "@bitwarden/common/key-management/crypto/services/encrypt.worker.ts", - import.meta.url, - ), - ); - if (this.currentServerConfig !== undefined) { - this.updateWorkerServerConfig(this.currentServerConfig); - } - } - - this.restartTimeout(); - - const id = Utils.newGuid(); - const request = buildDecryptMessage({ - id, - items: items, - key: key, - }); - - this.worker.postMessage(request); - - return await firstValueFrom( - fromEvent(this.worker, "message").pipe( - filter((response: MessageEvent) => response.data?.id === id), - map((response) => JSON.parse(response.data.items)), - map((items) => - items.map((jsonItem: Jsonify) => { - const initializer = getClassInitializer(jsonItem.initializerKey); - return initializer(jsonItem); - }), - ), - takeUntil(this.clear$), - defaultIfEmpty([]), - ), - ); - } - - override onServerConfigChange(newConfig: ServerConfig): void { - this.currentServerConfig = newConfig; - super.onServerConfigChange(newConfig); - this.updateWorkerServerConfig(newConfig); - } - - private updateWorkerServerConfig(newConfig: ServerConfig) { - if (this.worker != null) { - const request = buildSetConfigMessage({ newConfig }); - this.worker.postMessage(request); - } - } - - private clear() { - this.clear$.next(); - this.worker?.terminate(); - this.worker = null; - this.clearTimeout(); - } - - private restartTimeout() { - this.clearTimeout(); - this.timeout = setTimeout(() => this.clear(), workerTTL); - } - - private clearTimeout() { - if (this.timeout != null) { - clearTimeout(this.timeout); - } - } -} diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index 57df5f2a376..6db8481b137 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -11,7 +11,6 @@ import { ApiService } from "../../abstractions/api.service"; import { SearchService } from "../../abstractions/search.service"; import { AutofillSettingsService } from "../../autofill/services/autofill-settings.service"; import { DomainSettingsService } from "../../autofill/services/domain-settings.service"; -import { BulkEncryptService } from "../../key-management/crypto/abstractions/bulk-encrypt.service"; import { EncryptService } from "../../key-management/crypto/abstractions/encrypt.service"; import { UriMatchStrategy } from "../../models/domain/domain-service"; import { ConfigService } from "../../platform/abstractions/config/config.service"; @@ -119,7 +118,6 @@ describe("Cipher Service", () => { const i18nService = mock(); const searchService = mock(); const encryptService = mock(); - const bulkEncryptService = mock(); const configService = mock(); accountService = mockAccountServiceWith(mockUserId); const logService = mock(); @@ -145,7 +143,6 @@ describe("Cipher Service", () => { stateService, autofillSettingsService, encryptService, - bulkEncryptService, cipherFileUploadService, configService, stateProvider, diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 21f268d84de..0a3bf8dbdd1 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -97,7 +97,6 @@ export class CipherService implements CipherServiceAbstraction { private stateService: StateService, private autofillSettingsService: AutofillSettingsServiceAbstraction, private encryptService: EncryptService, - private bulkEncryptService: BulkEncryptService, private cipherFileUploadService: CipherFileUploadService, private configService: ConfigService, private stateProvider: StateProvider, @@ -441,17 +440,10 @@ export class CipherService implements CipherServiceAbstraction { const allCipherViews = ( await Promise.all( Object.entries(grouped).map(async ([orgId, groupedCiphers]) => { - if (await this.configService.getFeatureFlag(FeatureFlag.PM4154_BulkEncryptionService)) { - return await this.bulkEncryptService.decryptItems( - groupedCiphers, - keys.orgKeys[orgId as OrganizationId] ?? keys.userKey, - ); - } else { - return await this.encryptService.decryptItems( - groupedCiphers, - keys.orgKeys[orgId as OrganizationId] ?? keys.userKey, - ); - } + return await this.encryptService.decryptItems( + groupedCiphers, + keys.orgKeys[orgId as OrganizationId] ?? keys.userKey, + ); }), ) ) @@ -593,12 +585,7 @@ export class CipherService implements CipherServiceAbstraction { const ciphers = response.data.map((cr) => new Cipher(new CipherData(cr))); const key = await this.keyService.getOrgKey(organizationId); - let decCiphers: CipherView[] = []; - if (await this.configService.getFeatureFlag(FeatureFlag.PM4154_BulkEncryptionService)) { - decCiphers = await this.bulkEncryptService.decryptItems(ciphers, key); - } else { - decCiphers = await this.encryptService.decryptItems(ciphers, key); - } + const decCiphers: CipherView[] = await this.encryptService.decryptItems(ciphers, key); decCiphers.sort(this.getLocaleSortingFunction()); return decCiphers;