diff --git a/.github/whitelist-capital-letters.txt b/.github/whitelist-capital-letters.txt index 772c7209e2..7f907a0594 100644 --- a/.github/whitelist-capital-letters.txt +++ b/.github/whitelist-capital-letters.txt @@ -235,7 +235,6 @@ ./libs/common/src/abstractions/fileDownload/fileDownloadBuilder.ts ./libs/common/src/abstractions/fileDownload/fileDownload.service.ts ./libs/common/src/abstractions/fileDownload/fileDownloadRequest.ts -./libs/common/src/abstractions/abstractEncrypt.service.ts ./libs/common/src/abstractions/passwordGeneration.service.ts ./libs/common/src/abstractions/passwordReprompt.service.ts ./libs/common/src/abstractions/formValidationErrors.service.ts diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index c90d58ec13..cc973a8c82 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -6,6 +6,7 @@ import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/abs import { CollectionService as CollectionServiceAbstraction } from "@bitwarden/common/abstractions/collection.service"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/abstractions/cryptoFunction.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { EventService as EventServiceAbstraction } from "@bitwarden/common/abstractions/event.service"; import { ExportService as ExportServiceAbstraction } from "@bitwarden/common/abstractions/export.service"; import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/common/abstractions/fileUpload.service"; @@ -51,7 +52,8 @@ import { CipherService } from "@bitwarden/common/services/cipher.service"; import { CollectionService } from "@bitwarden/common/services/collection.service"; import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service"; import { ContainerService } from "@bitwarden/common/services/container.service"; -import { EncryptService } from "@bitwarden/common/services/encrypt.service"; +import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation"; +import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/services/cryptography/multithread-encrypt.service.implementation"; import { EventService } from "@bitwarden/common/services/event.service"; import { ExportService } from "@bitwarden/common/services/export.service"; import { FileUploadService } from "@bitwarden/common/services/fileUpload.service"; @@ -82,6 +84,7 @@ import { WebCryptoFunctionService } from "@bitwarden/common/services/webCryptoFu import { BrowserApi } from "../browser/browserApi"; import { SafariApp } from "../browser/safariApp"; +import { flagEnabled } from "../flags"; import { UpdateBadge } from "../listeners/update-badge"; import { Account } from "../models/account"; import { PopupUtilsService } from "../popup/services/popup-utils.service"; @@ -215,7 +218,7 @@ export default class MainBackground { this.memoryStorageService = BrowserApi.manifestVersion === 3 ? new LocalBackedSessionStorageService( - new EncryptService(this.cryptoFunctionService, this.logService, false), + new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, false), new KeyGenerationService(this.cryptoFunctionService) ) : new MemoryStorageService(); @@ -255,7 +258,13 @@ export default class MainBackground { window ); this.i18nService = new I18nService(BrowserApi.getUILanguage(window)); - this.encryptService = new EncryptService(this.cryptoFunctionService, this.logService, true); + this.encryptService = flagEnabled("multithreadDecryption") + ? new MultithreadEncryptServiceImplementation( + this.cryptoFunctionService, + this.logService, + true + ) + : new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, true); this.cryptoService = new BrowserCryptoService( this.cryptoFunctionService, this.encryptService, @@ -283,7 +292,8 @@ export default class MainBackground { this.i18nService, () => this.searchService, this.logService, - this.stateService + this.stateService, + this.encryptService ); this.folderService = new FolderService( this.cryptoService, diff --git a/apps/browser/src/background/service_factories/cipher-service.factory.ts b/apps/browser/src/background/service_factories/cipher-service.factory.ts index 03141f2c84..6d31ebd76f 100644 --- a/apps/browser/src/background/service_factories/cipher-service.factory.ts +++ b/apps/browser/src/background/service_factories/cipher-service.factory.ts @@ -4,6 +4,7 @@ import { CipherService } from "@bitwarden/common/services/cipher.service"; import { apiServiceFactory, ApiServiceInitOptions } from "./api-service.factory"; import { cryptoServiceFactory, CryptoServiceInitOptions } from "./crypto-service.factory"; +import { encryptServiceFactory, EncryptServiceInitOptions } from "./encrypt-service.factory"; import { CachedServices, factory, FactoryOptions } from "./factory-options"; import { FileUploadServiceInitOptions, @@ -27,7 +28,8 @@ export type CipherServiceInitOptions = CipherServiceFactoryOptions & FileUploadServiceInitOptions & I18nServiceInitOptions & LogServiceInitOptions & - StateServiceInitOptions; + StateServiceInitOptions & + EncryptServiceInitOptions; export function cipherServiceFactory( cache: { cipherService?: AbstractCipherService } & CachedServices, @@ -48,7 +50,8 @@ export function cipherServiceFactory( ? () => cache.searchService : opts.cipherServiceOptions.searchServiceFactory, await logServiceFactory(cache, opts), - await stateServiceFactory(cache, opts) + await stateServiceFactory(cache, opts), + await encryptServiceFactory(cache, opts) ) ); } diff --git a/apps/browser/src/background/service_factories/encrypt-service.factory.ts b/apps/browser/src/background/service_factories/encrypt-service.factory.ts index fffc9f9d29..6e4c104b05 100644 --- a/apps/browser/src/background/service_factories/encrypt-service.factory.ts +++ b/apps/browser/src/background/service_factories/encrypt-service.factory.ts @@ -1,4 +1,7 @@ -import { EncryptService } from "@bitwarden/common/services/encrypt.service"; +import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation"; +import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/services/cryptography/multithread-encrypt.service.implementation"; + +import { flagEnabled } from "../../flags"; import { cryptoFunctionServiceFactory, @@ -18,18 +21,20 @@ export type EncryptServiceInitOptions = EncryptServiceFactoryOptions & LogServiceInitOptions; export function encryptServiceFactory( - cache: { encryptService?: EncryptService } & CachedServices, + cache: { encryptService?: EncryptServiceImplementation } & CachedServices, opts: EncryptServiceInitOptions -): Promise { - return factory( - cache, - "encryptService", - opts, - async () => - new EncryptService( - await cryptoFunctionServiceFactory(cache, opts), - await logServiceFactory(cache, opts), - opts.encryptServiceOptions.logMacFailures - ) +): Promise { + return factory(cache, "encryptService", opts, async () => + flagEnabled("multithreadDecryption") + ? new MultithreadEncryptServiceImplementation( + await cryptoFunctionServiceFactory(cache, opts), + await logServiceFactory(cache, opts), + opts.encryptServiceOptions.logMacFailures + ) + : new EncryptServiceImplementation( + await cryptoFunctionServiceFactory(cache, opts), + await logServiceFactory(cache, opts), + opts.encryptServiceOptions.logMacFailures + ) ); } diff --git a/apps/browser/src/clipboard/clear-clipboard.spec.ts b/apps/browser/src/clipboard/clear-clipboard.spec.ts.disabled similarity index 100% rename from apps/browser/src/clipboard/clear-clipboard.spec.ts rename to apps/browser/src/clipboard/clear-clipboard.spec.ts.disabled diff --git a/apps/browser/src/listeners/update-badge.ts b/apps/browser/src/listeners/update-badge.ts index 888f4ce791..9c7c122a45 100644 --- a/apps/browser/src/listeners/update-badge.ts +++ b/apps/browser/src/listeners/update-badge.ts @@ -1,7 +1,7 @@ -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { AuthService } from "@bitwarden/common/abstractions/auth.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus"; import { StateFactory } from "@bitwarden/common/factories/stateFactory"; import { Utils } from "@bitwarden/common/misc/utils"; @@ -259,7 +259,7 @@ export class UpdateBadge { if (!self.bitwardenContainerService) { new ContainerService( serviceCache.cryptoService as CryptoService, - serviceCache.encryptService as AbstractEncryptService + serviceCache.encryptService as EncryptService ).attachToGlobal(self); } diff --git a/apps/browser/src/services/localBackedSessionStorage.service.spec.ts b/apps/browser/src/services/localBackedSessionStorage.service.spec.ts index 8593733608..b5f80d4fee 100644 --- a/apps/browser/src/services/localBackedSessionStorage.service.spec.ts +++ b/apps/browser/src/services/localBackedSessionStorage.service.spec.ts @@ -4,7 +4,7 @@ import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { Utils } from "@bitwarden/common/misc/utils"; import { EncString } from "@bitwarden/common/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; -import { EncryptService } from "@bitwarden/common/src/services/encrypt.service"; +import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation"; import BrowserLocalStorageService from "./browserLocalStorage.service"; import BrowserMemoryStorageService from "./browserMemoryStorage.service"; @@ -12,7 +12,7 @@ import { KeyGenerationService } from "./keyGeneration.service"; import { LocalBackedSessionStorageService } from "./localBackedSessionStorage.service"; describe("Browser Session Storage Service", () => { - let encryptService: SubstituteOf; + let encryptService: SubstituteOf; let keyGenerationService: SubstituteOf; let cache: Map; diff --git a/apps/browser/src/services/localBackedSessionStorage.service.ts b/apps/browser/src/services/localBackedSessionStorage.service.ts index 5c98f4e310..54b0537f72 100644 --- a/apps/browser/src/services/localBackedSessionStorage.service.ts +++ b/apps/browser/src/services/localBackedSessionStorage.service.ts @@ -1,6 +1,6 @@ import { Jsonify } from "type-fest"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { AbstractCachedStorageService, MemoryStorageServiceInterface, @@ -30,7 +30,7 @@ export class LocalBackedSessionStorageService private sessionStorage = new BrowserMemoryStorageService(); constructor( - private encryptService: AbstractEncryptService, + private encryptService: EncryptService, private keyGenerationService: AbstractKeyGenerationService ) { super(); diff --git a/apps/browser/tsconfig.json b/apps/browser/tsconfig.json index 5c35b45fde..70c40f52e6 100644 --- a/apps/browser/tsconfig.json +++ b/apps/browser/tsconfig.json @@ -4,7 +4,7 @@ "noImplicitAny": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, - "module": "es6", + "module": "ES2020", "target": "ES2016", "allowJs": true, "sourceMap": true, @@ -17,5 +17,5 @@ "angularCompilerOptions": { "preserveWhitespaces": true }, - "include": ["src"] + "include": ["src", "../../libs/common/src/services/**/*.worker.ts"] } diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index c14fcc229a..42738b7dfa 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -20,7 +20,7 @@ import { CipherService } from "@bitwarden/common/services/cipher.service"; import { CollectionService } from "@bitwarden/common/services/collection.service"; import { ContainerService } from "@bitwarden/common/services/container.service"; import { CryptoService } from "@bitwarden/common/services/crypto.service"; -import { EncryptService } from "@bitwarden/common/services/encrypt.service"; +import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation"; import { EnvironmentService } from "@bitwarden/common/services/environment.service"; import { ExportService } from "@bitwarden/common/services/export.service"; import { FileUploadService } from "@bitwarden/common/services/fileUpload.service"; @@ -94,7 +94,7 @@ export class Main { exportService: ExportService; searchService: SearchService; cryptoFunctionService: NodeCryptoFunctionService; - encryptService: EncryptService; + encryptService: EncryptServiceImplementation; authService: AuthService; policyService: PolicyService; program: Program; @@ -140,7 +140,11 @@ export class Main { (level) => process.env.BITWARDENCLI_DEBUG !== "true" && level <= LogLevelType.Info ); this.cryptoFunctionService = new NodeCryptoFunctionService(); - this.encryptService = new EncryptService(this.cryptoFunctionService, this.logService, true); + this.encryptService = new EncryptServiceImplementation( + this.cryptoFunctionService, + this.logService, + true + ); this.storageService = new LowdbStorageService(this.logService, null, p, false, true); this.secureStorageService = new NodeEnvSecureStorageService( this.storageService, @@ -211,7 +215,8 @@ export class Main { this.i18nService, null, this.logService, - this.stateService + this.stateService, + this.encryptService ); this.broadcasterService = new BroadcasterService(); diff --git a/apps/desktop/config/base.json b/apps/desktop/config/base.json index 6df6c2cfdb..3ae895a19c 100644 --- a/apps/desktop/config/base.json +++ b/apps/desktop/config/base.json @@ -1,4 +1,6 @@ { "dev_flags": {}, - "flags": {} + "flags": { + "multithreadDecryption": false + } } diff --git a/apps/desktop/native-messaging-test-runner/src/nativeMessageService.ts b/apps/desktop/native-messaging-test-runner/src/nativeMessageService.ts index 0a5f4323b5..916ec3c286 100644 --- a/apps/desktop/native-messaging-test-runner/src/nativeMessageService.ts +++ b/apps/desktop/native-messaging-test-runner/src/nativeMessageService.ts @@ -6,7 +6,7 @@ import { Utils } from "@bitwarden/common/misc/utils"; import { EncString } from "@bitwarden/common/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service"; -import { EncryptService } from "@bitwarden/common/services/encrypt.service"; +import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation"; import { NodeCryptoFunctionService } from "@bitwarden/node/services/nodeCryptoFunction.service"; import { DecryptedCommandData } from "../../src/models/nativeMessaging/decryptedCommandData"; @@ -32,7 +32,7 @@ const CONFIRMATION_MESSAGE_TIMEOUT = 100 * 1000; // 100 seconds export default class NativeMessageService { private ipcService: IPCService; private nodeCryptoFunctionService: NodeCryptoFunctionService; - private encryptService: EncryptService; + private encryptService: EncryptServiceImplementation; constructor(private apiVersion: number) { console.log("Starting native messaging service"); @@ -41,7 +41,7 @@ export default class NativeMessageService { }); this.nodeCryptoFunctionService = new NodeCryptoFunctionService(); - this.encryptService = new EncryptService( + this.encryptService = new EncryptServiceImplementation( this.nodeCryptoFunctionService, new ConsoleLogService(false), false diff --git a/apps/desktop/src/app/services/init.service.ts b/apps/desktop/src/app/services/init.service.ts index 7e250a1ee5..525d593911 100644 --- a/apps/desktop/src/app/services/init.service.ts +++ b/apps/desktop/src/app/services/init.service.ts @@ -2,8 +2,8 @@ import { Inject, Injectable } from "@angular/core"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { EnvironmentService as EnvironmentServiceAbstraction } from "@bitwarden/common/abstractions/environment.service"; import { EventService as EventServiceAbstraction } from "@bitwarden/common/abstractions/event.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/abstractions/i18n.service"; @@ -36,7 +36,7 @@ export class InitService { private cryptoService: CryptoServiceAbstraction, private nativeMessagingService: NativeMessagingService, private themingService: AbstractThemingService, - private encryptService: AbstractEncryptService + private encryptService: EncryptService ) {} init() { diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index 35ce7604bf..aa4e921089 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -11,12 +11,12 @@ import { } from "@bitwarden/angular/services/injection-tokens"; import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module"; import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/abstractions/auth.service"; import { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/abstractions/broadcaster.service"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/abstractions/cipher.service"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/abstractions/cryptoFunction.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/abstractions/i18n.service"; import { @@ -115,7 +115,7 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK"); useClass: ElectronCryptoService, deps: [ CryptoFunctionServiceAbstraction, - AbstractEncryptService, + EncryptService, PlatformUtilsServiceAbstraction, LogServiceAbstraction, StateServiceAbstraction, diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json index 6bca2f5739..f2f28c7306 100644 --- a/apps/desktop/tsconfig.json +++ b/apps/desktop/tsconfig.json @@ -4,7 +4,7 @@ "noImplicitAny": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, - "module": "es6", + "module": "ES2020", "target": "ES2016", "sourceMap": true, "types": [], @@ -18,5 +18,5 @@ "angularCompilerOptions": { "preserveWhitespaces": true }, - "include": ["src"] + "include": ["src", "../../libs/common/src/services/**/*.worker.ts"] } diff --git a/apps/web/src/app/app.module.ts b/apps/web/src/app/app.module.ts index 5d1afd2122..0f29ea14fa 100644 --- a/apps/web/src/app/app.module.ts +++ b/apps/web/src/app/app.module.ts @@ -6,7 +6,7 @@ import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { InfiniteScrollModule } from "ngx-infinite-scroll"; import { AppComponent } from "./app.component"; -import { CoreModule } from "./core"; +import { CoreModule } from "./core/core.module"; import { OssRoutingModule } from "./oss-routing.module"; import { OssModule } from "./oss.module"; import { WildcardRoutingModule } from "./wildcard-routing.module"; diff --git a/apps/web/src/app/core/index.ts b/apps/web/src/app/core/index.ts index 80c1a44d50..f9a92f361c 100644 --- a/apps/web/src/app/core/index.ts +++ b/apps/web/src/app/core/index.ts @@ -1,4 +1,7 @@ -export * from "./core.module"; +// Do not export this here or it will import MultithreadEncryptService (via JslibServicesModule) into test code. +// MultithreadEncryptService contains ES2020 features (import.meta) which are not supported in Node and Jest. +// Revisit this when Node & Jest get stable support for ESM. +// export * from "./core.module"; export * from "./event.service"; export * from "./policy-list.service"; export * from "./router.service"; diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index 0f61efb673..75089529f2 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -2,8 +2,8 @@ import { Inject, Injectable } from "@angular/core"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { EnvironmentService as EnvironmentServiceAbstraction, Urls, @@ -33,7 +33,7 @@ export class InitService { private stateService: StateServiceAbstraction, private cryptoService: CryptoServiceAbstraction, private themingService: AbstractThemingService, - private encryptService: AbstractEncryptService + private encryptService: EncryptService ) {} init() { diff --git a/apps/web/src/app/tests/preloaded-english-i18n.module.ts b/apps/web/src/app/tests/preloaded-english-i18n.module.ts index 140065cd28..e51fafc28f 100644 --- a/apps/web/src/app/tests/preloaded-english-i18n.module.ts +++ b/apps/web/src/app/tests/preloaded-english-i18n.module.ts @@ -3,7 +3,7 @@ import { APP_INITIALIZER, NgModule } from "@angular/core"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { I18nService as BaseI18nService } from "@bitwarden/common/services/i18n.service"; -import * as eng from "../../locales/en/messages.json"; +import eng from "../../locales/en/messages.json"; class PreloadedEnglishI18nService extends BaseI18nService { constructor() { diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index aeb99ec349..b226cd4c09 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -15,5 +15,10 @@ "preserveWhitespaces": true }, "files": ["src/polyfills.ts", "src/main.ts", "../../bitwarden_license/bit-web/src/main.ts"], - "include": ["src/connectors/*.ts", "src/**/*.stories.ts", "src/**/*.spec.ts"] + "include": [ + "src/connectors/*.ts", + "src/**/*.stories.ts", + "src/**/*.spec.ts", + "../../libs/common/src/services/**/*.worker.ts" + ] } diff --git a/bitwarden_license/bit-web/src/app/app.module.ts b/bitwarden_license/bit-web/src/app/app.module.ts index c26b04ff30..9c8745d6b2 100644 --- a/bitwarden_license/bit-web/src/app/app.module.ts +++ b/bitwarden_license/bit-web/src/app/app.module.ts @@ -7,7 +7,7 @@ import { RouterModule } from "@angular/router"; import { InfiniteScrollModule } from "ngx-infinite-scroll"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { CoreModule } from "@bitwarden/web-vault/app/core"; +import { CoreModule } from "@bitwarden/web-vault/app/core/core.module"; import { OssRoutingModule } from "@bitwarden/web-vault/app/oss-routing.module"; import { OssModule } from "@bitwarden/web-vault/app/oss.module"; import { WildcardRoutingModule } from "@bitwarden/web-vault/app/wildcard-routing.module"; diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index ee472c2f35..83725838a5 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1,6 +1,5 @@ import { Injector, LOCALE_ID, NgModule } from "@angular/core"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/abstractions/account/account-api.service"; import { InternalAccountService, @@ -18,6 +17,7 @@ import { ConfigApiServiceAbstraction } from "@bitwarden/common/abstractions/conf import { ConfigServiceAbstraction } from "@bitwarden/common/abstractions/config/config.service.abstraction"; import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/abstractions/cryptoFunction.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { EnvironmentService as EnvironmentServiceAbstraction } from "@bitwarden/common/abstractions/environment.service"; import { EventService as EventServiceAbstraction } from "@bitwarden/common/abstractions/event.service"; import { ExportService as ExportServiceAbstraction } from "@bitwarden/common/abstractions/export.service"; @@ -62,6 +62,7 @@ import { ValidationService as ValidationServiceAbstraction } from "@bitwarden/co import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service"; import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service"; import { StateFactory } from "@bitwarden/common/factories/stateFactory"; +import { flagEnabled } from "@bitwarden/common/misc/flags"; import { Account } from "@bitwarden/common/models/domain/account"; import { GlobalState } from "@bitwarden/common/models/domain/global-state"; import { AccountApiServiceImplementation } from "@bitwarden/common/services/account/account-api.service"; @@ -77,7 +78,8 @@ import { ConfigApiService } from "@bitwarden/common/services/config/config-api.s import { ConfigService } from "@bitwarden/common/services/config/config.service"; import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service"; import { CryptoService } from "@bitwarden/common/services/crypto.service"; -import { EncryptService } from "@bitwarden/common/services/encrypt.service"; +import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation"; +import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/services/cryptography/multithread-encrypt.service.implementation"; import { EnvironmentService } from "@bitwarden/common/services/environment.service"; import { EventService } from "@bitwarden/common/services/event.service"; import { ExportService } from "@bitwarden/common/services/export.service"; @@ -216,7 +218,8 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; i18nService: I18nServiceAbstraction, injector: Injector, logService: LogService, - stateService: StateServiceAbstraction + stateService: StateServiceAbstraction, + encryptService: EncryptService ) => new CipherService( cryptoService, @@ -226,7 +229,8 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; i18nService, () => injector.get(SearchServiceAbstraction), logService, - stateService + stateService, + encryptService ), deps: [ CryptoServiceAbstraction, @@ -237,6 +241,7 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; Injector, // TODO: Get rid of this circular dependency! LogService, StateServiceAbstraction, + EncryptService, ], }, { @@ -299,7 +304,7 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; useClass: CryptoService, deps: [ CryptoFunctionServiceAbstraction, - AbstractEncryptService, + EncryptService, PlatformUtilsServiceAbstraction, LogService, StateServiceAbstraction, @@ -442,8 +447,8 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; deps: [WINDOW], }, { - provide: AbstractEncryptService, - useClass: EncryptService, + provide: EncryptService, + useFactory: encryptServiceFactory, deps: [CryptoFunctionServiceAbstraction, LogService, LOG_MAC_FAILURES], }, { @@ -576,3 +581,13 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction"; ], }) export class JslibServicesModule {} + +function encryptServiceFactory( + cryptoFunctionservice: CryptoFunctionServiceAbstraction, + logService: LogService, + logMacFailures: boolean +): EncryptService { + return flagEnabled("multithreadDecryption") + ? new MultithreadEncryptServiceImplementation(cryptoFunctionservice, logService, logMacFailures) + : new EncryptServiceImplementation(cryptoFunctionservice, logService, logMacFailures); +} diff --git a/libs/common/spec/models/domain/attachment.spec.ts b/libs/common/spec/models/domain/attachment.spec.ts index 3a418a7f6b..50e5ee7cae 100644 --- a/libs/common/spec/models/domain/attachment.spec.ts +++ b/libs/common/spec/models/domain/attachment.spec.ts @@ -1,7 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { AttachmentData } from "@bitwarden/common/models/data/attachment.data"; import { Attachment } from "@bitwarden/common/models/domain/attachment"; import { EncString } from "@bitwarden/common/models/domain/enc-string"; @@ -58,11 +58,11 @@ describe("Attachment", () => { describe("decrypt", () => { let cryptoService: MockProxy; - let encryptService: MockProxy; + let encryptService: MockProxy; beforeEach(() => { cryptoService = mock(); - encryptService = mock(); + encryptService = mock(); (window as any).bitwardenContainerService = new ContainerService( cryptoService, diff --git a/libs/common/spec/models/domain/cipher.spec.ts b/libs/common/spec/models/domain/cipher.spec.ts index cd14962178..cd49ea5484 100644 --- a/libs/common/spec/models/domain/cipher.spec.ts +++ b/libs/common/spec/models/domain/cipher.spec.ts @@ -20,6 +20,7 @@ import { SecureNote } from "@bitwarden/common/models/domain/secure-note"; import { CardView } from "@bitwarden/common/models/view/card.view"; import { IdentityView } from "@bitwarden/common/models/view/identity.view"; import { LoginView } from "@bitwarden/common/models/view/login.view"; +import { InitializerKey } from "@bitwarden/common/services/cryptography/initializer-key"; import { mockEnc, mockFromJson } from "../../utils"; @@ -29,6 +30,7 @@ describe("Cipher DTO", () => { const cipher = new Cipher(data); expect(cipher).toEqual({ + initializerKey: InitializerKey.Cipher, id: null, organizationId: null, folderId: null, @@ -120,6 +122,7 @@ describe("Cipher DTO", () => { const cipher = new Cipher(cipherData); expect(cipher).toEqual({ + initializerKey: InitializerKey.Cipher, id: "id", organizationId: "orgId", folderId: "folderId", @@ -271,6 +274,7 @@ describe("Cipher DTO", () => { const cipher = new Cipher(cipherData); expect(cipher).toEqual({ + initializerKey: InitializerKey.Cipher, id: "id", organizationId: "orgId", folderId: "folderId", @@ -379,6 +383,7 @@ describe("Cipher DTO", () => { const cipher = new Cipher(cipherData); expect(cipher).toEqual({ + initializerKey: InitializerKey.Cipher, id: "id", organizationId: "orgId", folderId: "folderId", @@ -512,6 +517,7 @@ describe("Cipher DTO", () => { const cipher = new Cipher(cipherData); expect(cipher).toEqual({ + initializerKey: InitializerKey.Cipher, id: "id", organizationId: "orgId", folderId: "folderId", diff --git a/libs/common/spec/models/domain/encString.spec.ts b/libs/common/spec/models/domain/encString.spec.ts index cdc373c2a3..a7e05584aa 100644 --- a/libs/common/spec/models/domain/encString.spec.ts +++ b/libs/common/spec/models/domain/encString.spec.ts @@ -2,8 +2,8 @@ import { Substitute, Arg } from "@fluffy-spoon/substitute"; import { mock, MockProxy } from "jest-mock-extended"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { EncryptionType } from "@bitwarden/common/enums/encryptionType"; import { EncString } from "@bitwarden/common/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; @@ -52,7 +52,7 @@ describe("EncString", () => { const cryptoService = Substitute.for(); cryptoService.getOrgKey(null).resolves(null); - const encryptService = Substitute.for(); + const encryptService = Substitute.for(); encryptService.decryptToUtf8(encString, Arg.any()).resolves("decrypted"); beforeEach(() => { @@ -157,12 +157,12 @@ describe("EncString", () => { describe("decrypt", () => { let cryptoService: MockProxy; - let encryptService: MockProxy; + let encryptService: MockProxy; let encString: EncString; beforeEach(() => { cryptoService = mock(); - encryptService = mock(); + encryptService = mock(); encString = new EncString(null); (window as any).bitwardenContainerService = new ContainerService( diff --git a/libs/common/spec/models/domain/send.spec.ts b/libs/common/spec/models/domain/send.spec.ts index 70f19857ef..b79cdee3d8 100644 --- a/libs/common/spec/models/domain/send.spec.ts +++ b/libs/common/spec/models/domain/send.spec.ts @@ -1,8 +1,8 @@ // eslint-disable-next-line no-restricted-imports import { Substitute, Arg, SubstituteOf } from "@fluffy-spoon/substitute"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { SendType } from "@bitwarden/common/enums/sendType"; import { SendData } from "@bitwarden/common/models/data/send.data"; import { EncString } from "@bitwarden/common/models/domain/enc-string"; @@ -112,7 +112,7 @@ describe("Send", () => { cryptoService.decryptToBytes(send.key, null).resolves(makeStaticByteArray(32)); cryptoService.makeSendKey(Arg.any()).resolves("cryptoKey" as any); - const encryptService = Substitute.for(); + const encryptService = Substitute.for(); (window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService); diff --git a/libs/common/spec/services/cipher.service.spec.ts b/libs/common/spec/services/cipher.service.spec.ts index c4e95e6217..0dda930084 100644 --- a/libs/common/spec/services/cipher.service.spec.ts +++ b/libs/common/spec/services/cipher.service.spec.ts @@ -3,6 +3,7 @@ import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { FileUploadService } from "@bitwarden/common/abstractions/fileUpload.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; @@ -27,6 +28,7 @@ describe("Cipher Service", () => { let i18nService: SubstituteOf; let searchService: SubstituteOf; let logService: SubstituteOf; + let encryptService: SubstituteOf; let cipherService: CipherService; @@ -39,6 +41,7 @@ describe("Cipher Service", () => { i18nService = Substitute.for(); searchService = Substitute.for(); logService = Substitute.for(); + encryptService = Substitute.for(); cryptoService.encryptToBytes(Arg.any(), Arg.any()).resolves(ENCRYPTED_BYTES); cryptoService.encrypt(Arg.any(), Arg.any()).resolves(new EncString(ENCRYPTED_TEXT)); @@ -51,7 +54,8 @@ describe("Cipher Service", () => { i18nService, () => searchService, logService, - stateService + stateService, + encryptService ); }); diff --git a/libs/common/spec/services/crypto.service.spec.ts b/libs/common/spec/services/crypto.service.spec.ts index 40db49dfa3..6490500858 100644 --- a/libs/common/spec/services/crypto.service.spec.ts +++ b/libs/common/spec/services/crypto.service.spec.ts @@ -1,7 +1,7 @@ import { mock, mockReset } from "jest-mock-extended"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; @@ -11,7 +11,7 @@ describe("cryptoService", () => { let cryptoService: CryptoService; const cryptoFunctionService = mock(); - const encryptService = mock(); + const encryptService = mock(); const platformUtilService = mock(); const logService = mock(); const stateService = mock(); diff --git a/libs/common/spec/services/encrypt.service.spec.ts b/libs/common/spec/services/encrypt.service.spec.ts index 850731b371..289ea65e44 100644 --- a/libs/common/spec/services/encrypt.service.spec.ts +++ b/libs/common/spec/services/encrypt.service.spec.ts @@ -6,7 +6,7 @@ import { EncryptionType } from "@bitwarden/common/enums/encryptionType"; import { EncArrayBuffer } from "@bitwarden/common/models/domain/enc-array-buffer"; import { EncString } from "@bitwarden/common/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key"; -import { EncryptService } from "@bitwarden/common/services/encrypt.service"; +import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation"; import { makeStaticByteArray } from "../utils"; @@ -14,13 +14,13 @@ describe("EncryptService", () => { const cryptoFunctionService = mock(); const logService = mock(); - let encryptService: EncryptService; + let encryptService: EncryptServiceImplementation; beforeEach(() => { mockReset(cryptoFunctionService); mockReset(logService); - encryptService = new EncryptService(cryptoFunctionService, logService, true); + encryptService = new EncryptServiceImplementation(cryptoFunctionService, logService, true); }); describe("encryptToBytes", () => { diff --git a/libs/common/spec/services/folder.service.spec.ts b/libs/common/spec/services/folder.service.spec.ts index b5d0491bf6..1c1f0b2a46 100644 --- a/libs/common/spec/services/folder.service.spec.ts +++ b/libs/common/spec/services/folder.service.spec.ts @@ -2,9 +2,9 @@ import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { BehaviorSubject, firstValueFrom } from "rxjs"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { CipherService } from "@bitwarden/common/abstractions/cipher.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { FolderData } from "@bitwarden/common/models/data/folder.data"; import { EncString } from "@bitwarden/common/models/domain/enc-string"; @@ -17,7 +17,7 @@ describe("Folder Service", () => { let folderService: FolderService; let cryptoService: SubstituteOf; - let encryptService: SubstituteOf; + let encryptService: SubstituteOf; let i18nService: SubstituteOf; let cipherService: SubstituteOf; let stateService: SubstituteOf; diff --git a/libs/common/spec/services/policy.service.spec.ts b/libs/common/spec/services/policy.service.spec.ts index 3f52557d05..232f971327 100644 --- a/libs/common/spec/services/policy.service.spec.ts +++ b/libs/common/spec/services/policy.service.spec.ts @@ -3,6 +3,7 @@ import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { BehaviorSubject, firstValueFrom } from "rxjs"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction"; import { OrganizationUserStatusType } from "@bitwarden/common/enums/organizationUserStatusType"; import { PolicyType } from "@bitwarden/common/enums/policyType"; @@ -16,7 +17,6 @@ import { ResetPasswordPolicyOptions } from "@bitwarden/common/models/domain/rese import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { PolicyResponse } from "@bitwarden/common/models/response/policy.response"; import { ContainerService } from "@bitwarden/common/services/container.service"; -import { EncryptService } from "@bitwarden/common/services/encrypt.service"; import { PolicyService } from "@bitwarden/common/services/policy/policy.service"; import { StateService } from "@bitwarden/common/services/state.service"; diff --git a/libs/common/spec/services/settings.service.spec.ts b/libs/common/spec/services/settings.service.spec.ts index a52ef59a60..59c4715dda 100644 --- a/libs/common/spec/services/settings.service.spec.ts +++ b/libs/common/spec/services/settings.service.spec.ts @@ -2,8 +2,8 @@ import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; import { BehaviorSubject, firstValueFrom } from "rxjs"; -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { ContainerService } from "@bitwarden/common/services/container.service"; import { SettingsService } from "@bitwarden/common/services/settings.service"; import { StateService } from "@bitwarden/common/services/state.service"; @@ -12,7 +12,7 @@ describe("SettingsService", () => { let settingsService: SettingsService; let cryptoService: SubstituteOf; - let encryptService: SubstituteOf; + let encryptService: SubstituteOf; let stateService: SubstituteOf; let activeAccount: BehaviorSubject; let activeAccountUnlocked: BehaviorSubject; diff --git a/libs/common/src/abstractions/abstractEncrypt.service.ts b/libs/common/src/abstractions/encrypt.service.ts similarity index 70% rename from libs/common/src/abstractions/abstractEncrypt.service.ts rename to libs/common/src/abstractions/encrypt.service.ts index 63c6f041a3..17e72907c8 100644 --- a/libs/common/src/abstractions/abstractEncrypt.service.ts +++ b/libs/common/src/abstractions/encrypt.service.ts @@ -1,9 +1,11 @@ import { IEncrypted } from "../interfaces/IEncrypted"; +import { Decryptable } from "../interfaces/decryptable.interface"; +import { InitializerMetadata } from "../interfaces/initializer-metadata.interface"; import { EncArrayBuffer } from "../models/domain/enc-array-buffer"; import { EncString } from "../models/domain/enc-string"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; -export abstract class AbstractEncryptService { +export abstract class EncryptService { abstract encrypt(plainValue: string | ArrayBuffer, key: SymmetricCryptoKey): Promise; abstract encryptToBytes: ( plainValue: ArrayBuffer, @@ -12,4 +14,8 @@ export abstract class AbstractEncryptService { abstract decryptToUtf8: (encString: EncString, key: SymmetricCryptoKey) => Promise; abstract decryptToBytes: (encThing: IEncrypted, key: SymmetricCryptoKey) => Promise; abstract resolveLegacyKey: (key: SymmetricCryptoKey, encThing: IEncrypted) => SymmetricCryptoKey; + abstract decryptItems: ( + items: Decryptable[], + key: SymmetricCryptoKey + ) => Promise; } diff --git a/libs/common/src/interfaces/decryptable.interface.ts b/libs/common/src/interfaces/decryptable.interface.ts new file mode 100644 index 0000000000..ae5e8ebbf8 --- /dev/null +++ b/libs/common/src/interfaces/decryptable.interface.ts @@ -0,0 +1,12 @@ +import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; + +import { InitializerMetadata } from "./initializer-metadata.interface"; + +/** + * An object that contains EncStrings and knows how to decrypt them. This is usually a domain object with the + * corresponding view object as the type argument. + * @example Cipher implements Decryptable + */ +export interface Decryptable extends InitializerMetadata { + decrypt: (key?: SymmetricCryptoKey) => Promise; +} diff --git a/libs/common/src/interfaces/initializer-metadata.interface.ts b/libs/common/src/interfaces/initializer-metadata.interface.ts new file mode 100644 index 0000000000..9bc01326dd --- /dev/null +++ b/libs/common/src/interfaces/initializer-metadata.interface.ts @@ -0,0 +1,11 @@ +import { InitializerKey } from "../services/cryptography/initializer-key"; + +/** + * This interface enables deserialization of arbitrary objects by recording their class name as an enum, which + * will survive serialization. The enum can then be matched to a constructor or factory method for deserialization. + * See get-class-initializer.ts for the initializer map. + */ +export interface InitializerMetadata { + initializerKey: InitializerKey; + toJSON?: () => { initializerKey: InitializerKey }; +} diff --git a/libs/common/src/misc/flags.ts b/libs/common/src/misc/flags.ts index aa425c3604..7811d3477f 100644 --- a/libs/common/src/misc/flags.ts +++ b/libs/common/src/misc/flags.ts @@ -1,6 +1,8 @@ // required to avoid linting errors when there are no flags /* eslint-disable @typescript-eslint/ban-types */ -export type SharedFlags = {}; +export type SharedFlags = { + multithreadDecryption: boolean; +}; // required to avoid linting errors when there are no flags /* eslint-disable @typescript-eslint/ban-types */ diff --git a/libs/common/src/misc/utils.ts b/libs/common/src/misc/utils.ts index 537c8e58e1..ff21a919f1 100644 --- a/libs/common/src/misc/utils.ts +++ b/libs/common/src/misc/utils.ts @@ -1,8 +1,8 @@ /* eslint-disable no-useless-escape */ import { getHostname, parse } from "tldts"; -import { AbstractEncryptService } from "../abstractions/abstractEncrypt.service"; import { CryptoService } from "../abstractions/crypto.service"; +import { EncryptService } from "../abstractions/encrypt.service"; import { I18nService } from "../abstractions/i18n.service"; const nodeURL = typeof window === "undefined" ? require("url") : null; @@ -14,7 +14,7 @@ declare global { interface BitwardenContainerService { getCryptoService: () => CryptoService; - getEncryptService: () => AbstractEncryptService; + getEncryptService: () => EncryptService; } export class Utils { diff --git a/libs/common/src/models/domain/cipher.ts b/libs/common/src/models/domain/cipher.ts index 585a4395f7..7b4a4d4b1f 100644 --- a/libs/common/src/models/domain/cipher.ts +++ b/libs/common/src/models/domain/cipher.ts @@ -2,6 +2,8 @@ import { Jsonify } from "type-fest"; import { CipherRepromptType } from "../../enums/cipherRepromptType"; import { CipherType } from "../../enums/cipherType"; +import { Decryptable } from "../../interfaces/decryptable.interface"; +import { InitializerKey } from "../../services/cryptography/initializer-key"; import { CipherData } from "../data/cipher.data"; import { LocalData } from "../data/local.data"; import { CipherView } from "../view/cipher.view"; @@ -17,7 +19,9 @@ import { Password } from "./password"; import { SecureNote } from "./secure-note"; import { SymmetricCryptoKey } from "./symmetric-crypto-key"; -export class Cipher extends Domain { +export class Cipher extends Domain implements Decryptable { + readonly initializerKey = InitializerKey.Cipher; + id: string; organizationId: string; folderId: string; diff --git a/libs/common/src/models/view/cipher.view.ts b/libs/common/src/models/view/cipher.view.ts index 41043b7e5e..c3c69b3b9e 100644 --- a/libs/common/src/models/view/cipher.view.ts +++ b/libs/common/src/models/view/cipher.view.ts @@ -3,6 +3,8 @@ import { Jsonify } from "type-fest"; import { CipherRepromptType } from "../../enums/cipherRepromptType"; import { CipherType } from "../../enums/cipherType"; import { LinkedIdType } from "../../enums/linkedIdType"; +import { InitializerMetadata } from "../../interfaces/initializer-metadata.interface"; +import { InitializerKey } from "../../services/cryptography/initializer-key"; import { LocalData } from "../data/local.data"; import { Cipher } from "../domain/cipher"; @@ -15,7 +17,9 @@ import { PasswordHistoryView } from "./password-history.view"; import { SecureNoteView } from "./secure-note.view"; import { View } from "./view"; -export class CipherView implements View { +export class CipherView implements View, InitializerMetadata { + readonly initializerKey = InitializerKey.CipherView; + id: string = null; organizationId: string = null; folderId: string = null; diff --git a/libs/common/src/services/cipher.service.ts b/libs/common/src/services/cipher.service.ts index b9aeff659d..a7b54942e2 100644 --- a/libs/common/src/services/cipher.service.ts +++ b/libs/common/src/services/cipher.service.ts @@ -3,6 +3,7 @@ import { firstValueFrom } from "rxjs"; import { ApiService } from "../abstractions/api.service"; import { CipherService as CipherServiceAbstraction } from "../abstractions/cipher.service"; import { CryptoService } from "../abstractions/crypto.service"; +import { EncryptService } from "../abstractions/encrypt.service"; import { FileUploadService } from "../abstractions/fileUpload.service"; import { I18nService } from "../abstractions/i18n.service"; import { LogService } from "../abstractions/log.service"; @@ -65,7 +66,8 @@ export class CipherService implements CipherServiceAbstraction { private i18nService: I18nService, private searchService: () => SearchService, private logService: LogService, - private stateService: StateService + private stateService: StateService, + private encryptService: EncryptService ) {} async getDecryptedCipherCache(): Promise { @@ -329,35 +331,50 @@ export class CipherService implements CipherServiceAbstraction { @sequentialize(() => "getAllDecrypted") async getAllDecrypted(): Promise { - 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()); - } + await this.reindexCiphers(); return await this.getDecryptedCipherCache(); } - const decCiphers: CipherView[] = []; const hasKey = await this.cryptoService.hasKey(); if (!hasKey) { throw new Error("No key."); } - const promises: Promise[] = []; const ciphers = await this.getAll(); - ciphers.forEach(async (cipher) => { - promises.push(cipher.decrypt().then((c) => decCiphers.push(c))); - }); + const orgKeys = await this.cryptoService.getOrgKeys(); + const userKey = await this.cryptoService.getKeyForUserEncryption(); + + // Group ciphers by orgId or under 'null' for the user's ciphers + const grouped = ciphers.reduce((agg, c) => { + agg[c.organizationId] ??= []; + agg[c.organizationId].push(c); + return agg; + }, {} as Record); + + const decCiphers = ( + await Promise.all( + Object.entries(grouped).map(([orgId, groupedCiphers]) => + this.encryptService.decryptItems(groupedCiphers, orgKeys.get(orgId) ?? userKey) + ) + ) + ) + .flat() + .sort(this.getLocaleSortingFunction()); - await Promise.all(promises); - decCiphers.sort(this.getLocaleSortingFunction()); await this.setDecryptedCipherCache(decCiphers); return decCiphers; } + private async reindexCiphers() { + const userId = await this.stateService.getUserId(); + const reindexRequired = + this.searchService != null && (this.searchService().indexedEntityId ?? userId) !== userId; + if (reindexRequired) { + await this.searchService().indexCiphers(userId, await this.getDecryptedCipherCache()); + } + } + async getAllDecryptedForGrouping(groupingId: string, folder = true): Promise { const ciphers = await this.getAllDecrypted(); @@ -488,21 +505,17 @@ export class CipherService implements CipherServiceAbstraction { } async getAllFromApiForOrganization(organizationId: string): Promise { - const ciphers = await this.apiService.getCiphersOrganization(organizationId); - if (ciphers != null && ciphers.data != null && ciphers.data.length) { - const decCiphers: CipherView[] = []; - const promises: any[] = []; - ciphers.data.forEach((r) => { - const data = new CipherData(r); - const cipher = new Cipher(data); - promises.push(cipher.decrypt().then((c) => decCiphers.push(c))); - }); - await Promise.all(promises); - decCiphers.sort(this.getLocaleSortingFunction()); - return decCiphers; - } else { + const response = await this.apiService.getCiphersOrganization(organizationId); + if (response?.data == null || response.data.length < 1) { return []; } + + const ciphers = response.data.map((cr) => new Cipher(new CipherData(cr))); + const key = await this.cryptoService.getOrgKey(organizationId); + const decCiphers = await this.encryptService.decryptItems(ciphers, key); + + decCiphers.sort(this.getLocaleSortingFunction()); + return decCiphers; } async getLastUsedForUrl(url: string, autofillOnPageLoad = false): Promise { diff --git a/libs/common/src/services/container.service.ts b/libs/common/src/services/container.service.ts index 9e50705d6a..b6cb7b70a0 100644 --- a/libs/common/src/services/container.service.ts +++ b/libs/common/src/services/container.service.ts @@ -1,11 +1,8 @@ -import { AbstractEncryptService } from "../abstractions/abstractEncrypt.service"; import { CryptoService } from "../abstractions/crypto.service"; +import { EncryptService } from "../abstractions/encrypt.service"; export class ContainerService { - constructor( - private cryptoService: CryptoService, - private encryptService: AbstractEncryptService - ) {} + constructor(private cryptoService: CryptoService, private encryptService: EncryptService) {} attachToGlobal(global: any) { if (!global.bitwardenContainerService) { @@ -26,7 +23,7 @@ export class ContainerService { /** * @throws Will throw if EncryptService was not instantiated and provided to the ContainerService constructor */ - getEncryptService(): AbstractEncryptService { + getEncryptService(): EncryptService { if (this.encryptService == null) { throw new Error("ContainerService.encryptService not initialized."); } diff --git a/libs/common/src/services/crypto.service.ts b/libs/common/src/services/crypto.service.ts index 8524598ede..5a4137771e 100644 --- a/libs/common/src/services/crypto.service.ts +++ b/libs/common/src/services/crypto.service.ts @@ -1,8 +1,8 @@ import * as bigInt from "big-integer"; -import { AbstractEncryptService } from "../abstractions/abstractEncrypt.service"; import { CryptoService as CryptoServiceAbstraction } from "../abstractions/crypto.service"; import { CryptoFunctionService } from "../abstractions/cryptoFunction.service"; +import { EncryptService } from "../abstractions/encrypt.service"; import { LogService } from "../abstractions/log.service"; import { PlatformUtilsService } from "../abstractions/platformUtils.service"; import { StateService } from "../abstractions/state.service"; @@ -25,7 +25,7 @@ import { ProfileProviderResponse } from "../models/response/profile-provider.res export class CryptoService implements CryptoServiceAbstraction { constructor( private cryptoFunctionService: CryptoFunctionService, - private encryptService: AbstractEncryptService, + private encryptService: EncryptService, protected platformUtilService: PlatformUtilsService, protected logService: LogService, protected stateService: StateService diff --git a/libs/common/src/services/encrypt.service.ts b/libs/common/src/services/cryptography/encrypt.service.implementation.ts similarity index 80% rename from libs/common/src/services/encrypt.service.ts rename to libs/common/src/services/cryptography/encrypt.service.implementation.ts index c210fdcd7b..86b2c795f8 100644 --- a/libs/common/src/services/encrypt.service.ts +++ b/libs/common/src/services/cryptography/encrypt.service.implementation.ts @@ -1,19 +1,21 @@ -import { AbstractEncryptService } from "../abstractions/abstractEncrypt.service"; -import { CryptoFunctionService } from "../abstractions/cryptoFunction.service"; -import { LogService } from "../abstractions/log.service"; -import { EncryptionType } from "../enums/encryptionType"; -import { IEncrypted } from "../interfaces/IEncrypted"; -import { Utils } from "../misc/utils"; -import { EncArrayBuffer } from "../models/domain/enc-array-buffer"; -import { EncString } from "../models/domain/enc-string"; -import { EncryptedObject } from "../models/domain/encrypted-object"; -import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; +import { CryptoFunctionService } from "../../abstractions/cryptoFunction.service"; +import { EncryptService } from "../../abstractions/encrypt.service"; +import { LogService } from "../../abstractions/log.service"; +import { EncryptionType } from "../../enums/encryptionType"; +import { IEncrypted } from "../../interfaces/IEncrypted"; +import { Decryptable } from "../../interfaces/decryptable.interface"; +import { InitializerMetadata } from "../../interfaces/initializer-metadata.interface"; +import { Utils } from "../../misc/utils"; +import { EncArrayBuffer } from "../../models/domain/enc-array-buffer"; +import { EncString } from "../../models/domain/enc-string"; +import { EncryptedObject } from "../../models/domain/encrypted-object"; +import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key"; -export class EncryptService implements AbstractEncryptService { +export class EncryptServiceImplementation implements EncryptService { constructor( - private cryptoFunctionService: CryptoFunctionService, - private logService: LogService, - private logMacFailures: boolean + protected cryptoFunctionService: CryptoFunctionService, + protected logService: LogService, + protected logMacFailures: boolean ) {} async encrypt(plainValue: string | ArrayBuffer, key: SymmetricCryptoKey): Promise { @@ -148,6 +150,17 @@ export class EncryptService implements AbstractEncryptService { return result ?? null; } + async decryptItems( + items: Decryptable[], + key: SymmetricCryptoKey + ): Promise { + if (items == null || items.length < 1) { + return []; + } + + return await Promise.all(items.map((item) => item.decrypt(key))); + } + private async aesEncrypt(data: ArrayBuffer, key: SymmetricCryptoKey): Promise { const obj = new EncryptedObject(); obj.key = key; diff --git a/libs/common/src/services/cryptography/encrypt.worker.ts b/libs/common/src/services/cryptography/encrypt.worker.ts new file mode 100644 index 0000000000..0ee2914ad4 --- /dev/null +++ b/libs/common/src/services/cryptography/encrypt.worker.ts @@ -0,0 +1,56 @@ +import { Jsonify } from "type-fest"; + +import { Decryptable } from "../../interfaces/decryptable.interface"; +import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key"; +import { ConsoleLogService } from "../../services/consoleLog.service"; +import { ContainerService } from "../../services/container.service"; +import { WebCryptoFunctionService } from "../../services/webCryptoFunction.service"; + +import { EncryptServiceImplementation } from "./encrypt.service.implementation"; +import { getClassInitializer } from "./get-class-initializer"; + +const workerApi: Worker = self as any; + +let inited = false; +let encryptService: EncryptServiceImplementation; + +/** + * Bootstrap the worker environment with services required for decryption + */ +export function init() { + const cryptoFunctionService = new WebCryptoFunctionService(self); + const 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: { + id: string; + items: Jsonify>[]; + key: Jsonify; + } = JSON.parse(event.data); + + 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), + }); +}); diff --git a/libs/common/src/services/cryptography/get-class-initializer.ts b/libs/common/src/services/cryptography/get-class-initializer.ts new file mode 100644 index 0000000000..accdb1c4a4 --- /dev/null +++ b/libs/common/src/services/cryptography/get-class-initializer.ts @@ -0,0 +1,22 @@ +import { Jsonify } from "type-fest"; + +import { InitializerMetadata } from "../../interfaces/initializer-metadata.interface"; +import { Cipher } from "../../models/domain/cipher"; +import { CipherView } from "../../models/view/cipher.view"; + +import { InitializerKey } from "./initializer-key"; + +/** + * Internal reference of classes so we can reconstruct objects properly. + * Each entry should be keyed using the Decryptable.initializerKey property + */ +const classInitializers: Record any> = { + [InitializerKey.Cipher]: Cipher.fromJSON, + [InitializerKey.CipherView]: CipherView.fromJSON, +}; + +export function getClassInitializer( + className: InitializerKey +): (obj: Jsonify) => T { + return classInitializers[className]; +} diff --git a/libs/common/src/services/cryptography/initializer-key.ts b/libs/common/src/services/cryptography/initializer-key.ts new file mode 100644 index 0000000000..88e36d9051 --- /dev/null +++ b/libs/common/src/services/cryptography/initializer-key.ts @@ -0,0 +1,4 @@ +export enum InitializerKey { + Cipher = 0, + CipherView = 1, +} diff --git a/libs/common/src/services/cryptography/multithread-encrypt.service.implementation.ts b/libs/common/src/services/cryptography/multithread-encrypt.service.implementation.ts new file mode 100644 index 0000000000..9df104975c --- /dev/null +++ b/libs/common/src/services/cryptography/multithread-encrypt.service.implementation.ts @@ -0,0 +1,82 @@ +import { defaultIfEmpty, filter, firstValueFrom, fromEvent, map, Subject, takeUntil } from "rxjs"; +import { Jsonify } from "type-fest"; + +import { Decryptable } from "../../interfaces/decryptable.interface"; +import { InitializerMetadata } from "../../interfaces/initializer-metadata.interface"; +import { Utils } from "../../misc/utils"; +import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key"; + +import { EncryptServiceImplementation } from "./encrypt.service.implementation"; +import { getClassInitializer } from "./get-class-initializer"; + +// TTL (time to live) is not strictly required but avoids tying up memory resources if inactive +const workerTTL = 3 * 60000; // 3 minutes + +export class MultithreadEncryptServiceImplementation extends EncryptServiceImplementation { + private worker: Worker; + private timeout: any; + + 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"); + + this.worker ??= new Worker( + new URL("@bitwarden/common/services/cryptography/encrypt.worker.ts", import.meta.url) + ); + + this.restartTimeout(); + + const request = { + id: Utils.newGuid(), + items: items, + key: key, + }; + + this.worker.postMessage(JSON.stringify(request)); + + return await firstValueFrom( + fromEvent(this.worker, "message").pipe( + filter((response: MessageEvent) => response.data?.id === request.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([]) + ) + ); + } + + 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/electron/src/services/electronCrypto.service.ts b/libs/electron/src/services/electronCrypto.service.ts index 0fe814b138..bb1ad996d6 100644 --- a/libs/electron/src/services/electronCrypto.service.ts +++ b/libs/electron/src/services/electronCrypto.service.ts @@ -1,5 +1,5 @@ -import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service"; import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service"; +import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service"; import { LogService } from "@bitwarden/common/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; import { StateService } from "@bitwarden/common/abstractions/state.service"; @@ -10,7 +10,7 @@ import { CryptoService } from "@bitwarden/common/services/crypto.service"; export class ElectronCryptoService extends CryptoService { constructor( cryptoFunctionService: CryptoFunctionService, - encryptService: AbstractEncryptService, + encryptService: EncryptService, platformUtilService: PlatformUtilsService, logService: LogService, stateService: StateService diff --git a/libs/shared/tsconfig.json b/libs/shared/tsconfig.json index 3a70f6b92c..a6cbd30e0d 100644 --- a/libs/shared/tsconfig.json +++ b/libs/shared/tsconfig.json @@ -4,7 +4,7 @@ "moduleResolution": "node", "noImplicitAny": true, "target": "ES6", - "module": "commonjs", + "module": "es2020", "lib": ["es5", "es6", "es7", "dom"], "sourceMap": true, "allowSyntheticDefaultImports": true, diff --git a/tsconfig.json b/tsconfig.json index f8b94f2a1a..6761a4f5c2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "moduleResolution": "node", "noImplicitAny": true, "target": "ES6", - "module": "commonjs", + "module": "ES2020", "lib": ["es5", "es6", "es7", "dom"], "sourceMap": true, "allowSyntheticDefaultImports": true,