diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index cab48c0bc5..99114bc007 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -413,7 +413,7 @@ export default class MainBackground { this.eventService, this.logService ); - this.containerService = new ContainerService(this.cryptoService); + this.containerService = new ContainerService(this.cryptoService, this.encryptService); this.auditService = new AuditService(this.cryptoFunctionService, this.apiService); this.exportService = new ExportService( this.folderService, diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index a168182ddc..734a030943 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -193,7 +193,7 @@ export class Main { this.organizationApiService = new OrganizationApiService(this.apiService); - this.containerService = new ContainerService(this.cryptoService); + this.containerService = new ContainerService(this.cryptoService, this.encryptService); this.settingsService = new SettingsService(this.stateService); diff --git a/apps/desktop/src/app/services/init.service.ts b/apps/desktop/src/app/services/init.service.ts index 41e6c6a1da..722ae013fc 100644 --- a/apps/desktop/src/app/services/init.service.ts +++ b/apps/desktop/src/app/services/init.service.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from "@angular/core"; import { WINDOW } 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 { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service"; import { EnvironmentService as EnvironmentServiceAbstraction } from "@bitwarden/common/abstractions/environment.service"; import { EventService as EventServiceAbstraction } from "@bitwarden/common/abstractions/event.service"; @@ -34,7 +35,8 @@ export class InitService { private stateService: StateServiceAbstraction, private cryptoService: CryptoServiceAbstraction, private nativeMessagingService: NativeMessagingService, - private themingService: AbstractThemingService + private themingService: AbstractThemingService, + private encryptService: AbstractEncryptService ) {} init() { @@ -65,7 +67,7 @@ export class InitService { await this.stateService.setInstalledVersion(currentVersion); } - const containerService = new ContainerService(this.cryptoService); + const containerService = new ContainerService(this.cryptoService, this.encryptService); containerService.attachToGlobal(this.win); }; } diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index 4915b0a088..03f4d99f3a 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -2,6 +2,7 @@ import { Inject, Injectable } from "@angular/core"; import { WINDOW } 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 { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service"; import { EnvironmentService as EnvironmentServiceAbstraction, @@ -31,7 +32,8 @@ export class InitService { private twoFactorService: TwoFactorServiceAbstraction, private stateService: StateServiceAbstraction, private cryptoService: CryptoServiceAbstraction, - private themingService: AbstractThemingService + private themingService: AbstractThemingService, + private encryptService: AbstractEncryptService ) {} init() { @@ -51,7 +53,7 @@ export class InitService { const htmlEl = this.win.document.documentElement; htmlEl.classList.add("locale_" + this.i18nService.translationLocale); await this.themingService.monitorThemeChanges(); - const containerService = new ContainerService(this.cryptoService); + const containerService = new ContainerService(this.cryptoService, this.encryptService); containerService.attachToGlobal(this.win); }; } diff --git a/libs/common/spec/models/domain/attachment.spec.ts b/libs/common/spec/models/domain/attachment.spec.ts index 4a608c3a4a..835628d47d 100644 --- a/libs/common/spec/models/domain/attachment.spec.ts +++ b/libs/common/spec/models/domain/attachment.spec.ts @@ -1,8 +1,10 @@ -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 { AttachmentData } from "@bitwarden/common/models/data/attachmentData"; import { Attachment } from "@bitwarden/common/models/domain/attachment"; +import { EncString } from "@bitwarden/common/models/domain/encString"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey"; import { ContainerService } from "@bitwarden/common/services/container.service"; @@ -54,30 +56,79 @@ describe("Attachment", () => { expect(attachment.toAttachmentData()).toEqual(data); }); - it("Decrypt", async () => { - const attachment = new Attachment(); - attachment.id = "id"; - attachment.url = "url"; - attachment.size = "1100"; - attachment.sizeName = "1.1 KB"; - attachment.key = mockEnc("key"); - attachment.fileName = mockEnc("fileName"); + describe("decrypt", () => { + let cryptoService: MockProxy; + let encryptService: MockProxy; - const cryptoService = Substitute.for(); - cryptoService.getOrgKey(null).resolves(null); - cryptoService.decryptToBytes(Arg.any(), Arg.any()).resolves(makeStaticByteArray(32)); + beforeEach(() => { + cryptoService = mock(); + encryptService = mock(); - (window as any).bitwardenContainerService = new ContainerService(cryptoService); + (window as any).bitwardenContainerService = new ContainerService( + cryptoService, + encryptService + ); + }); - const view = await attachment.decrypt(null); + it("expected output", async () => { + const attachment = new Attachment(); + attachment.id = "id"; + attachment.url = "url"; + attachment.size = "1100"; + attachment.sizeName = "1.1 KB"; + attachment.key = mockEnc("key"); + attachment.fileName = mockEnc("fileName"); - expect(view).toEqual({ - id: "id", - url: "url", - size: "1100", - sizeName: "1.1 KB", - fileName: "fileName", - key: expect.any(SymmetricCryptoKey), + encryptService.decryptToBytes.mockResolvedValue(makeStaticByteArray(32)); + + const view = await attachment.decrypt(null); + + expect(view).toEqual({ + id: "id", + url: "url", + size: "1100", + sizeName: "1.1 KB", + fileName: "fileName", + key: expect.any(SymmetricCryptoKey), + }); + }); + + describe("decrypts attachment.key", () => { + let attachment: Attachment; + + beforeEach(() => { + attachment = new Attachment(); + attachment.key = mock(); + }); + + it("uses the provided key without depending on CryptoService", async () => { + const providedKey = mock(); + + await attachment.decrypt(null, providedKey); + + expect(cryptoService.getKeyForUserEncryption).not.toHaveBeenCalled(); + expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, providedKey); + }); + + it("gets an organization key if required", async () => { + const orgKey = mock(); + cryptoService.getOrgKey.calledWith("orgId").mockResolvedValue(orgKey); + + await attachment.decrypt("orgId", null); + + expect(cryptoService.getOrgKey).toHaveBeenCalledWith("orgId"); + expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, orgKey); + }); + + it("gets the user's decryption key if required", async () => { + const userKey = mock(); + cryptoService.getKeyForUserEncryption.mockResolvedValue(userKey); + + await attachment.decrypt(null, null); + + expect(cryptoService.getKeyForUserEncryption).toHaveBeenCalled(); + expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, userKey); + }); }); }); }); diff --git a/libs/common/spec/models/domain/encString.spec.ts b/libs/common/spec/models/domain/encString.spec.ts index aa59d64574..adcfaf0706 100644 --- a/libs/common/spec/models/domain/encString.spec.ts +++ b/libs/common/spec/models/domain/encString.spec.ts @@ -1,5 +1,7 @@ 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 { EncryptionType } from "@bitwarden/common/enums/encryptionType"; import { EncString } from "@bitwarden/common/models/domain/encString"; @@ -48,10 +50,15 @@ describe("EncString", () => { const cryptoService = Substitute.for(); cryptoService.getOrgKey(null).resolves(null); - cryptoService.decryptToUtf8(encString, Arg.any()).resolves("decrypted"); + + const encryptService = Substitute.for(); + encryptService.decryptToUtf8(encString, Arg.any()).resolves("decrypted"); beforeEach(() => { - (window as any).bitwardenContainerService = new ContainerService(cryptoService); + (window as any).bitwardenContainerService = new ContainerService( + cryptoService, + encryptService + ); }); it("decrypts correctly", async () => { @@ -62,7 +69,7 @@ describe("EncString", () => { it("result should be cached", async () => { const decrypted = await encString.decrypt(null); - cryptoService.received(1).decryptToUtf8(Arg.any(), Arg.any()); + encryptService.received(1).decryptToUtf8(Arg.any(), Arg.any()); expect(decrypted).toBe("decrypted"); }); @@ -148,25 +155,28 @@ describe("EncString", () => { }); describe("decrypt", () => { - it("throws exception when bitwarden container not initialized", async () => { - const encString = new EncString(null); + let cryptoService: MockProxy; + let encryptService: MockProxy; + let encString: EncString; - expect.assertions(1); - try { - await encString.decrypt(null); - } catch (e) { - expect(e.message).toEqual("global bitwardenContainerService not initialized."); - } + beforeEach(() => { + cryptoService = mock(); + encryptService = mock(); + encString = new EncString(null); + + (window as any).bitwardenContainerService = new ContainerService( + cryptoService, + encryptService + ); }); it("handles value it can't decrypt", async () => { - const encString = new EncString(null); + encryptService.decryptToUtf8.mockRejectedValue("error"); - const cryptoService = Substitute.for(); - cryptoService.getOrgKey(null).resolves(null); - cryptoService.decryptToUtf8(encString, Arg.any()).throws("error"); - - (window as any).bitwardenContainerService = new ContainerService(cryptoService); + (window as any).bitwardenContainerService = new ContainerService( + cryptoService, + encryptService + ); const decrypted = await encString.decrypt(null); @@ -178,18 +188,35 @@ describe("EncString", () => { }); }); - it("passes along key", async () => { - const encString = new EncString(null); - const key = Substitute.for(); - - const cryptoService = Substitute.for(); - cryptoService.getOrgKey(null).resolves(null); - - (window as any).bitwardenContainerService = new ContainerService(cryptoService); + it("uses provided key without depending on CryptoService", async () => { + const key = mock(); await encString.decrypt(null, key); - cryptoService.received().decryptToUtf8(encString, key); + expect(cryptoService.getKeyForUserEncryption).not.toHaveBeenCalled(); + expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encString, key); + }); + + it("gets an organization key if required", async () => { + const orgKey = mock(); + + cryptoService.getOrgKey.calledWith("orgId").mockResolvedValue(orgKey); + + await encString.decrypt("orgId", null); + + expect(cryptoService.getOrgKey).toHaveBeenCalledWith("orgId"); + expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encString, orgKey); + }); + + it("gets the user's decryption key if required", async () => { + const userKey = mock(); + + cryptoService.getKeyForUserEncryption.mockResolvedValue(userKey); + + await encString.decrypt(null, null); + + expect(cryptoService.getKeyForUserEncryption).toHaveBeenCalledWith(); + expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encString, userKey); }); }); diff --git a/libs/common/spec/models/domain/send.spec.ts b/libs/common/spec/models/domain/send.spec.ts index 92a3a5ed2d..575821abc9 100644 --- a/libs/common/spec/models/domain/send.spec.ts +++ b/libs/common/spec/models/domain/send.spec.ts @@ -1,5 +1,6 @@ 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 { SendType } from "@bitwarden/common/enums/sendType"; import { SendData } from "@bitwarden/common/models/data/sendData"; @@ -110,7 +111,9 @@ describe("Send", () => { cryptoService.decryptToBytes(send.key, null).resolves(makeStaticByteArray(32)); cryptoService.makeSendKey(Arg.any()).resolves("cryptoKey" as any); - (window as any).bitwardenContainerService = new ContainerService(cryptoService); + const encryptService = Substitute.for(); + + (window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService); const view = await send.decrypt(); diff --git a/libs/common/spec/services/folder.service.spec.ts b/libs/common/spec/services/folder.service.spec.ts index 31b1aad718..211c1f788e 100644 --- a/libs/common/spec/services/folder.service.spec.ts +++ b/libs/common/spec/services/folder.service.spec.ts @@ -1,6 +1,7 @@ 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 { I18nService } from "@bitwarden/common/abstractions/i18n.service"; @@ -15,6 +16,7 @@ describe("Folder Service", () => { let folderService: FolderService; let cryptoService: SubstituteOf; + let encryptService: SubstituteOf; let i18nService: SubstituteOf; let cipherService: SubstituteOf; let stateService: SubstituteOf; @@ -23,6 +25,7 @@ describe("Folder Service", () => { beforeEach(() => { cryptoService = Substitute.for(); + encryptService = Substitute.for(); i18nService = Substitute.for(); cipherService = Substitute.for(); stateService = Substitute.for(); @@ -34,7 +37,7 @@ describe("Folder Service", () => { }); stateService.activeAccount$.returns(activeAccount); stateService.activeAccountUnlocked$.returns(activeAccountUnlocked); - (window as any).bitwardenContainerService = new ContainerService(cryptoService); + (window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService); folderService = new FolderService(cryptoService, i18nService, cipherService, stateService); }); diff --git a/libs/common/spec/services/settings.service.spec.ts b/libs/common/spec/services/settings.service.spec.ts index f3733a1425..f81e4d341c 100644 --- a/libs/common/spec/services/settings.service.spec.ts +++ b/libs/common/spec/services/settings.service.spec.ts @@ -1,6 +1,7 @@ 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 { ContainerService } from "@bitwarden/common/services/container.service"; import { SettingsService } from "@bitwarden/common/services/settings.service"; @@ -10,12 +11,14 @@ describe("SettingsService", () => { let settingsService: SettingsService; let cryptoService: SubstituteOf; + let encryptService: SubstituteOf; let stateService: SubstituteOf; let activeAccount: BehaviorSubject; let activeAccountUnlocked: BehaviorSubject; beforeEach(() => { cryptoService = Substitute.for(); + encryptService = Substitute.for(); stateService = Substitute.for(); activeAccount = new BehaviorSubject("123"); activeAccountUnlocked = new BehaviorSubject(true); @@ -23,7 +26,7 @@ describe("SettingsService", () => { stateService.getSettings().resolves({ equivalentDomains: [["test"], ["domains"]] }); stateService.activeAccount$.returns(activeAccount); stateService.activeAccountUnlocked$.returns(activeAccountUnlocked); - (window as any).bitwardenContainerService = new ContainerService(cryptoService); + (window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService); settingsService = new SettingsService(stateService); }); diff --git a/libs/common/src/misc/utils.ts b/libs/common/src/misc/utils.ts index 524c438470..0aebd9d728 100644 --- a/libs/common/src/misc/utils.ts +++ b/libs/common/src/misc/utils.ts @@ -3,6 +3,7 @@ import * as tldjs from "tldjs"; import { CryptoService } from "@bitwarden/common/abstractions/crypto.service"; +import { AbstractEncryptService } from "../abstractions/abstractEncrypt.service"; import { I18nService } from "../abstractions/i18n.service"; const nodeURL = typeof window === "undefined" ? require("url") : null; @@ -14,6 +15,7 @@ declare global { interface BitwardenContainerService { getCryptoService: () => CryptoService; + getEncryptService: () => AbstractEncryptService; } export class Utils { @@ -368,6 +370,16 @@ export class Utils { return s.charAt(0).toUpperCase() + s.slice(1); } + /** + * @throws Will throw an error if the ContainerService has not been attached to the window object + */ + static getContainerService(): BitwardenContainerService { + if (this.global.bitwardenContainerService == null) { + throw new Error("global bitwardenContainerService not initialized."); + } + return this.global.bitwardenContainerService; + } + private static validIpAddress(ipString: string): boolean { const ipRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; diff --git a/libs/common/src/models/domain/attachment.ts b/libs/common/src/models/domain/attachment.ts index 053438390d..6696a0f753 100644 --- a/libs/common/src/models/domain/attachment.ts +++ b/libs/common/src/models/domain/attachment.ts @@ -1,4 +1,3 @@ -import { CryptoService } from "../../abstractions/crypto.service"; import { Utils } from "../../misc/utils"; import { AttachmentData } from "../data/attachmentData"; import { AttachmentView } from "../view/attachmentView"; @@ -47,26 +46,33 @@ export class Attachment extends Domain { ); if (this.key != null) { - let cryptoService: CryptoService; - const containerService = Utils.global.bitwardenContainerService; - if (containerService) { - cryptoService = containerService.getCryptoService(); - } else { - throw new Error("global bitwardenContainerService not initialized."); - } - - try { - const orgKey = await cryptoService.getOrgKey(orgId); - const decValue = await cryptoService.decryptToBytes(this.key, orgKey ?? encKey); - view.key = new SymmetricCryptoKey(decValue); - } catch (e) { - // TODO: error? - } + view.key = await this.decryptAttachmentKey(orgId, encKey); } return view; } + private async decryptAttachmentKey(orgId: string, encKey?: SymmetricCryptoKey) { + try { + if (encKey == null) { + encKey = await this.getKeyForDecryption(orgId); + } + + const encryptService = Utils.getContainerService().getEncryptService(); + const decValue = await encryptService.decryptToBytes(this.key, encKey); + return new SymmetricCryptoKey(decValue); + } catch (e) { + // TODO: error? + } + } + + private async getKeyForDecryption(orgId: string) { + const cryptoService = Utils.getContainerService().getCryptoService(); + return orgId != null + ? await cryptoService.getOrgKey(orgId) + : await cryptoService.getKeyForUserEncryption(); + } + toAttachmentData(): AttachmentData { const a = new AttachmentData(); a.size = this.size; diff --git a/libs/common/src/models/domain/encString.ts b/libs/common/src/models/domain/encString.ts index 46c267bb04..01376c9093 100644 --- a/libs/common/src/models/domain/encString.ts +++ b/libs/common/src/models/domain/encString.ts @@ -2,7 +2,6 @@ import { Jsonify } from "type-fest"; import { IEncrypted } from "@bitwarden/common/interfaces/IEncrypted"; -import { CryptoService } from "../../abstractions/crypto.service"; import { EncryptionType } from "../../enums/encryptionType"; import { Utils } from "../../misc/utils"; @@ -29,30 +28,6 @@ export class EncString implements IEncrypted { } } - async decrypt(orgId: string, key: SymmetricCryptoKey = null): Promise { - if (this.decryptedValue != null) { - return this.decryptedValue; - } - - let cryptoService: CryptoService; - const containerService = Utils.global.bitwardenContainerService; - if (containerService) { - cryptoService = containerService.getCryptoService(); - } else { - throw new Error("global bitwardenContainerService not initialized."); - } - - try { - if (key == null) { - key = await cryptoService.getOrgKey(orgId); - } - this.decryptedValue = await cryptoService.decryptToUtf8(this, key); - } catch (e) { - this.decryptedValue = "[error: cannot decrypt]"; - } - return this.decryptedValue; - } - get ivBytes(): ArrayBuffer { return this.iv == null ? null : Utils.fromB64ToArray(this.iv).buffer; } @@ -160,4 +135,32 @@ export class EncString implements IEncrypted { encPieces, }; } + + async decrypt(orgId: string, key: SymmetricCryptoKey = null): Promise { + if (this.decryptedValue != null) { + return this.decryptedValue; + } + + try { + if (key == null) { + key = await this.getKeyForDecryption(orgId); + } + if (key == null) { + throw new Error("No key to decrypt EncString with orgId " + orgId); + } + + const encryptService = Utils.getContainerService().getEncryptService(); + this.decryptedValue = await encryptService.decryptToUtf8(this, key); + } catch (e) { + this.decryptedValue = "[error: cannot decrypt]"; + } + return this.decryptedValue; + } + + private async getKeyForDecryption(orgId: string) { + const cryptoService = Utils.getContainerService().getCryptoService(); + return orgId != null + ? await cryptoService.getOrgKey(orgId) + : await cryptoService.getKeyForUserEncryption(); + } } diff --git a/libs/common/src/models/domain/send.ts b/libs/common/src/models/domain/send.ts index 8bf56acbee..43ca31a715 100644 --- a/libs/common/src/models/domain/send.ts +++ b/libs/common/src/models/domain/send.ts @@ -1,4 +1,3 @@ -import { CryptoService } from "../../abstractions/crypto.service"; import { SendType } from "../../enums/sendType"; import { Utils } from "../../misc/utils"; import { SendData } from "../data/sendData"; @@ -71,13 +70,7 @@ export class Send extends Domain { async decrypt(): Promise { const model = new SendView(this); - let cryptoService: CryptoService; - const containerService = Utils.global.bitwardenContainerService; - if (containerService) { - cryptoService = containerService.getCryptoService(); - } else { - throw new Error("global bitwardenContainerService not initialized."); - } + const cryptoService = Utils.getContainerService().getCryptoService(); try { model.key = await cryptoService.decryptToBytes(this.key, null); diff --git a/libs/common/src/services/container.service.ts b/libs/common/src/services/container.service.ts index 848a8b0389..9e50705d6a 100644 --- a/libs/common/src/services/container.service.ts +++ b/libs/common/src/services/container.service.ts @@ -1,7 +1,11 @@ +import { AbstractEncryptService } from "../abstractions/abstractEncrypt.service"; import { CryptoService } from "../abstractions/crypto.service"; export class ContainerService { - constructor(private cryptoService: CryptoService) {} + constructor( + private cryptoService: CryptoService, + private encryptService: AbstractEncryptService + ) {} attachToGlobal(global: any) { if (!global.bitwardenContainerService) { @@ -9,7 +13,23 @@ export class ContainerService { } } + /** + * @throws Will throw if CryptoService was not instantiated and provided to the ContainerService constructor + */ getCryptoService(): CryptoService { + if (this.cryptoService == null) { + throw new Error("ContainerService.cryptoService not initialized."); + } return this.cryptoService; } + + /** + * @throws Will throw if EncryptService was not instantiated and provided to the ContainerService constructor + */ + getEncryptService(): AbstractEncryptService { + if (this.encryptService == null) { + throw new Error("ContainerService.encryptService not initialized."); + } + return this.encryptService; + } }