diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index 14a218c7141..d95e8333dca 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -186,15 +186,15 @@ export class EditCommand { return Response.notFound(); } - let folderView = await folder.decrypt(); + const userKey = await firstValueFrom(this.keyService.userKey$(activeUserId)); + let folderView = await folder.decrypt(userKey); folderView = FolderExport.toView(req, folderView); - const userKey = await this.keyService.getUserKey(activeUserId); const encFolder = await this.folderService.encrypt(folderView, userKey); try { const folder = await this.folderApiService.save(encFolder, activeUserId); const updatedFolder = new Folder(folder); - const decFolder = await updatedFolder.decrypt(); + const decFolder = await updatedFolder.decrypt(userKey); const res = new FolderResponse(decFolder); return Response.success(res); } catch (e) { diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 93e711d748f..35816b56fb2 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -417,10 +417,11 @@ export class GetCommand extends DownloadCommand { private async getFolder(id: string) { let decFolder: FolderView = null; const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const userKey = await firstValueFrom(this.keyService.userKey$(activeUserId)); if (Utils.isGuid(id)) { const folder = await this.folderService.getFromState(id, activeUserId); if (folder != null) { - decFolder = await folder.decrypt(); + decFolder = await folder.decrypt(userKey); } } else if (id.trim() !== "") { let folders = await this.folderService.getAllDecryptedFromState(activeUserId); diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 5602c593942..d826766dc65 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -181,12 +181,12 @@ export class CreateCommand { private async createFolder(req: FolderExport) { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - const userKey = await this.keyService.getUserKey(activeUserId); + const userKey = await firstValueFrom(this.keyService.userKey$(activeUserId)); const folder = await this.folderService.encrypt(FolderExport.toView(req), userKey); try { const folderData = await this.folderApiService.save(folder, activeUserId); const newFolder = new Folder(folderData); - const decFolder = await newFolder.decrypt(); + const decFolder = await newFolder.decrypt(userKey); const res = new FolderResponse(decFolder); return Response.success(res); } catch (e) { diff --git a/libs/common/spec/utils.ts b/libs/common/spec/utils.ts index db9a7e0842c..f3e81d71ce2 100644 --- a/libs/common/spec/utils.ts +++ b/libs/common/spec/utils.ts @@ -2,7 +2,10 @@ // @ts-strict-ignore import { mock, MockProxy } from "jest-mock-extended"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; +import { ContainerService } from "@bitwarden/common/platform/services/container.service"; +import { KeyService } from "@bitwarden/key-management"; import { EncryptionType } from "../src/platform/enums"; import { Utils } from "../src/platform/misc/utils"; @@ -29,6 +32,7 @@ export function BuildTestObject( export function mockEnc(s: string): MockProxy { const mocked = mock(); + mocked.decryptedValue = s; mocked.decrypt.mockResolvedValue(s); return mocked; @@ -77,4 +81,14 @@ export const mockFromSdk = (stub: any) => { return `${stub}_fromSdk`; }; +export const mockContainerService = () => { + const keyService = mock(); + const encryptService = mock(); + encryptService.decryptString.mockImplementation(async (encStr, _key) => { + return encStr.decryptedValue; + }); + (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); + return (window as any).bitwardenContainerService; +}; + export { trackEmissions, awaitAsync } from "@bitwarden/core-test-utils"; diff --git a/libs/common/src/platform/models/domain/domain-base.ts b/libs/common/src/platform/models/domain/domain-base.ts index a144353f5bc..382ed5fea76 100644 --- a/libs/common/src/platform/models/domain/domain-base.ts +++ b/libs/common/src/platform/models/domain/domain-base.ts @@ -73,14 +73,13 @@ export default class Domain { domain: DomainEncryptableKeys, viewModel: ViewEncryptableKeys, props: EncryptableKeys[], - orgId: string | null, key: SymmetricCryptoKey | null = null, objectContext: string = "No Domain Context", ): Promise { for (const prop of props) { viewModel[prop] = (await domain[prop]?.decrypt( - orgId, + null, key, `Property: ${prop as string}; ObjectContext: ${objectContext}`, )) ?? null; diff --git a/libs/common/src/tools/send/models/domain/send-access.spec.ts b/libs/common/src/tools/send/models/domain/send-access.spec.ts index 597208d517c..686236bff8e 100644 --- a/libs/common/src/tools/send/models/domain/send-access.spec.ts +++ b/libs/common/src/tools/send/models/domain/send-access.spec.ts @@ -1,6 +1,6 @@ import { mock } from "jest-mock-extended"; -import { mockEnc } from "../../../../../spec"; +import { mockContainerService, mockEnc } from "../../../../../spec"; import { SendType } from "../../enums/send-type"; import { SendAccessResponse } from "../response/send-access.response"; @@ -23,6 +23,8 @@ describe("SendAccess", () => { expirationDate: new Date("2022-01-31T12:00:00.000Z"), creatorIdentifier: "creatorIdentifier", } as SendAccessResponse; + + mockContainerService(); }); it("Convert from empty", () => { diff --git a/libs/common/src/tools/send/models/domain/send-access.ts b/libs/common/src/tools/send/models/domain/send-access.ts index 2251e8e5a5a..68d1af7b57e 100644 --- a/libs/common/src/tools/send/models/domain/send-access.ts +++ b/libs/common/src/tools/send/models/domain/send-access.ts @@ -54,7 +54,7 @@ export class SendAccess extends Domain { async decrypt(key: SymmetricCryptoKey): Promise { const model = new SendAccessView(this); - await this.decryptObj(this, model, ["name"], null, key); + await this.decryptObj(this, model, ["name"], key); switch (this.type) { case SendType.File: diff --git a/libs/common/src/tools/send/models/domain/send-file.spec.ts b/libs/common/src/tools/send/models/domain/send-file.spec.ts index 44a84cdc639..f60767e9bdf 100644 --- a/libs/common/src/tools/send/models/domain/send-file.spec.ts +++ b/libs/common/src/tools/send/models/domain/send-file.spec.ts @@ -1,4 +1,4 @@ -import { mockEnc } from "../../../../../spec"; +import { mockContainerService, mockEnc } from "../../../../../spec"; import { SendFileData } from "../data/send-file.data"; import { SendFile } from "./send-file"; @@ -39,6 +39,7 @@ describe("SendFile", () => { }); it("Decrypt", async () => { + mockContainerService(); const sendFile = new SendFile(); sendFile.id = "id"; sendFile.size = "1100"; diff --git a/libs/common/src/tools/send/models/domain/send-file.ts b/libs/common/src/tools/send/models/domain/send-file.ts index 228f4ee81ca..d01f30a1905 100644 --- a/libs/common/src/tools/send/models/domain/send-file.ts +++ b/libs/common/src/tools/send/models/domain/send-file.ts @@ -38,7 +38,6 @@ export class SendFile extends Domain { this, new SendFileView(this), ["fileName"], - null, key, ); } diff --git a/libs/common/src/tools/send/models/domain/send-text.spec.ts b/libs/common/src/tools/send/models/domain/send-text.spec.ts index 6af143ec594..bac2687c897 100644 --- a/libs/common/src/tools/send/models/domain/send-text.spec.ts +++ b/libs/common/src/tools/send/models/domain/send-text.spec.ts @@ -1,4 +1,4 @@ -import { mockEnc } from "../../../../../spec"; +import { mockContainerService, mockEnc } from "../../../../../spec"; import { SendTextData } from "../data/send-text.data"; import { SendText } from "./send-text"; @@ -11,6 +11,8 @@ describe("SendText", () => { text: "encText", hidden: false, }; + + mockContainerService(); }); it("Convert from empty", () => { diff --git a/libs/common/src/tools/send/models/domain/send-text.ts b/libs/common/src/tools/send/models/domain/send-text.ts index 321810292ad..34a0be52dcb 100644 --- a/libs/common/src/tools/send/models/domain/send-text.ts +++ b/libs/common/src/tools/send/models/domain/send-text.ts @@ -30,13 +30,7 @@ export class SendText extends Domain { } decrypt(key: SymmetricCryptoKey): Promise { - return this.decryptObj( - this, - new SendTextView(this), - ["text"], - null, - key, - ); + return this.decryptObj(this, new SendTextView(this), ["text"], key); } static fromJSON(obj: Jsonify) { diff --git a/libs/common/src/tools/send/models/domain/send.spec.ts b/libs/common/src/tools/send/models/domain/send.spec.ts index d465aa97924..dc9ca7d3444 100644 --- a/libs/common/src/tools/send/models/domain/send.spec.ts +++ b/libs/common/src/tools/send/models/domain/send.spec.ts @@ -6,7 +6,7 @@ import { emptyGuid, UserId } from "@bitwarden/common/types/guid"; // eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; -import { makeStaticByteArray, mockEnc } from "../../../../../spec"; +import { makeStaticByteArray, mockContainerService, mockEnc } from "../../../../../spec"; import { EncryptService } from "../../../../key-management/crypto/abstractions/encrypt.service"; import { SymmetricCryptoKey } from "../../../../platform/models/domain/symmetric-crypto-key"; import { ContainerService } from "../../../../platform/services/container.service"; @@ -43,6 +43,8 @@ describe("Send", () => { disabled: false, hideEmail: true, }; + + mockContainerService(); }); it("Convert from empty", () => { diff --git a/libs/common/src/tools/send/models/domain/send.ts b/libs/common/src/tools/send/models/domain/send.ts index 48129d4314a..2bf16de8a44 100644 --- a/libs/common/src/tools/send/models/domain/send.ts +++ b/libs/common/src/tools/send/models/domain/send.ts @@ -89,7 +89,7 @@ export class Send extends Domain { model.key = await encryptService.decryptBytes(this.key, sendKeyEncryptionKey); model.cryptoKey = await keyService.makeSendKey(model.key); - await this.decryptObj(this, model, ["name", "notes"], null, model.cryptoKey); + await this.decryptObj(this, model, ["name", "notes"], model.cryptoKey); switch (this.type) { case SendType.File: diff --git a/libs/common/src/vault/models/domain/attachment.spec.ts b/libs/common/src/vault/models/domain/attachment.spec.ts index 77bb3eda38d..3c97e3b4213 100644 --- a/libs/common/src/vault/models/domain/attachment.spec.ts +++ b/libs/common/src/vault/models/domain/attachment.spec.ts @@ -4,12 +4,10 @@ import { mock, MockProxy } from "jest-mock-extended"; // eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; -import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec"; +import { makeStaticByteArray, mockContainerService, mockEnc, mockFromJson } from "../../../../spec"; import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; -import { ContainerService } from "../../../platform/services/container.service"; -import { OrgKey, UserKey } from "../../../types/key"; import { AttachmentData } from "../../models/data/attachment.data"; import { Attachment } from "../../models/domain/attachment"; @@ -70,10 +68,9 @@ describe("Attachment", () => { let encryptService: MockProxy; beforeEach(() => { - keyService = mock(); - encryptService = mock(); - - (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); + const containerService = mockContainerService(); + keyService = containerService.keyService as MockProxy; + encryptService = containerService.encryptService as MockProxy; }); it("expected output", async () => { @@ -85,14 +82,13 @@ describe("Attachment", () => { attachment.key = mockEnc("key"); attachment.fileName = mockEnc("fileName"); - const userKey = new SymmetricCryptoKey(makeStaticByteArray(64)); - keyService.getUserKey.mockResolvedValue(userKey as UserKey); encryptService.decryptFileData.mockResolvedValue(makeStaticByteArray(32)); encryptService.unwrapSymmetricKey.mockResolvedValue( new SymmetricCryptoKey(makeStaticByteArray(64)), ); - const view = await attachment.decrypt(null); + const userKey = new SymmetricCryptoKey(makeStaticByteArray(64)); + const view = await attachment.decrypt(userKey); expect(view).toEqual({ id: "id", @@ -116,31 +112,11 @@ describe("Attachment", () => { it("uses the provided key without depending on KeyService", async () => { const providedKey = mock(); - await attachment.decrypt(null, "", providedKey); + await attachment.decrypt(providedKey, ""); expect(keyService.getUserKey).not.toHaveBeenCalled(); expect(encryptService.unwrapSymmetricKey).toHaveBeenCalledWith(attachment.key, providedKey); }); - - it("gets an organization key if required", async () => { - const orgKey = mock(); - keyService.getOrgKey.calledWith("orgId").mockResolvedValue(orgKey); - - await attachment.decrypt("orgId", "", null); - - expect(keyService.getOrgKey).toHaveBeenCalledWith("orgId"); - expect(encryptService.unwrapSymmetricKey).toHaveBeenCalledWith(attachment.key, orgKey); - }); - - it("gets the user's decryption key if required", async () => { - const userKey = mock(); - keyService.getUserKey.mockResolvedValue(userKey); - - await attachment.decrypt(null, "", null); - - expect(keyService.getUserKey).toHaveBeenCalled(); - expect(encryptService.unwrapSymmetricKey).toHaveBeenCalledWith(attachment.key, userKey); - }); }); }); diff --git a/libs/common/src/vault/models/domain/attachment.ts b/libs/common/src/vault/models/domain/attachment.ts index 7b43af9be55..d9dfa128028 100644 --- a/libs/common/src/vault/models/domain/attachment.ts +++ b/libs/common/src/vault/models/domain/attachment.ts @@ -1,6 +1,5 @@ import { Jsonify } from "type-fest"; -import { OrgKey, UserKey } from "@bitwarden/common/types/key"; import { Attachment as SdkAttachment } from "@bitwarden/sdk-internal"; import { EncString } from "../../../key-management/crypto/models/enc-string"; @@ -34,21 +33,19 @@ export class Attachment extends Domain { } async decrypt( - orgId: string | undefined, + decryptionKey: SymmetricCryptoKey, context = "No Cipher Context", - encKey?: SymmetricCryptoKey, ): Promise { const view = await this.decryptObj( this, new AttachmentView(this), ["fileName"], - orgId ?? null, - encKey, + decryptionKey, "DomainType: Attachment; " + context, ); if (this.key != null) { - view.key = await this.decryptAttachmentKey(orgId, encKey); + view.key = await this.decryptAttachmentKey(decryptionKey); view.encryptedKey = this.key; // Keep the encrypted key for the view } @@ -56,27 +53,15 @@ export class Attachment extends Domain { } private async decryptAttachmentKey( - orgId: string | undefined, - encKey?: SymmetricCryptoKey, + decryptionKey: SymmetricCryptoKey, ): Promise { try { if (this.key == null) { return undefined; } - if (encKey == null) { - const key = await this.getKeyForDecryption(orgId); - - // If we don't have a key, we can't decrypt - if (key == null) { - return undefined; - } - - encKey = key; - } - const encryptService = Utils.getContainerService().getEncryptService(); - const decValue = await encryptService.unwrapSymmetricKey(this.key, encKey); + const decValue = await encryptService.unwrapSymmetricKey(this.key, decryptionKey); return decValue; } catch (e) { // eslint-disable-next-line no-console @@ -85,11 +70,6 @@ export class Attachment extends Domain { } } - private async getKeyForDecryption(orgId: string | undefined): Promise { - const keyService = Utils.getContainerService().getKeyService(); - return orgId != null ? await keyService.getOrgKey(orgId) : await keyService.getUserKey(); - } - toAttachmentData(): AttachmentData { const a = new AttachmentData(); if (this.size != null) { diff --git a/libs/common/src/vault/models/domain/card.spec.ts b/libs/common/src/vault/models/domain/card.spec.ts index 185c2fa4b8f..1dfda7be359 100644 --- a/libs/common/src/vault/models/domain/card.spec.ts +++ b/libs/common/src/vault/models/domain/card.spec.ts @@ -1,4 +1,9 @@ -import { mockEnc, mockFromJson } from "../../../../spec"; +import { + makeSymmetricCryptoKey, + mockContainerService, + mockEnc, + mockFromJson, +} from "../../../../spec"; import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string"; import { CardData } from "../../../vault/models/data/card.data"; import { Card } from "../../models/domain/card"; @@ -65,7 +70,10 @@ describe("Card", () => { card.expYear = mockEnc("expYear"); card.code = mockEnc("code"); - const view = await card.decrypt(null); + const userKey = makeSymmetricCryptoKey(64); + + mockContainerService(); + const view = await card.decrypt(userKey); expect(view).toEqual({ _brand: "brand", diff --git a/libs/common/src/vault/models/domain/card.ts b/libs/common/src/vault/models/domain/card.ts index b3a087d44fb..2d636e8e265 100644 --- a/libs/common/src/vault/models/domain/card.ts +++ b/libs/common/src/vault/models/domain/card.ts @@ -31,16 +31,11 @@ export class Card extends Domain { this.code = conditionalEncString(obj.code); } - async decrypt( - orgId: string | undefined, - context = "No Cipher Context", - encKey?: SymmetricCryptoKey, - ): Promise { + async decrypt(encKey: SymmetricCryptoKey, context = "No Cipher Context"): Promise { return this.decryptObj( this, new CardView(), ["cardholderName", "brand", "number", "expMonth", "expYear", "code"], - orgId ?? null, encKey, "DomainType: Card; " + context, ); diff --git a/libs/common/src/vault/models/domain/cipher.spec.ts b/libs/common/src/vault/models/domain/cipher.spec.ts index 87301928c57..4fe5e89451c 100644 --- a/libs/common/src/vault/models/domain/cipher.spec.ts +++ b/libs/common/src/vault/models/domain/cipher.spec.ts @@ -2,9 +2,7 @@ import { mock } from "jest-mock-extended"; import { Jsonify } from "type-fest"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. -// eslint-disable-next-line no-restricted-imports -import { KeyService } from "@bitwarden/key-management"; +import { MockProxy } from "@bitwarden/common/platform/spec/mock-deep"; import { CipherType as SdkCipherType, UriMatchType, @@ -14,11 +12,15 @@ import { EncString as SdkEncString, } from "@bitwarden/sdk-internal"; -import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec/utils"; +import { + makeStaticByteArray, + mockContainerService, + mockEnc, + mockFromJson, +} from "../../../../spec/utils"; import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; import { EncString } from "../../../key-management/crypto/models/enc-string"; import { UriMatchStrategy } from "../../../models/domain/domain-service"; -import { ContainerService } from "../../../platform/services/container.service"; import { InitializerKey } from "../../../platform/services/cryptography/initializer-key"; import { UserId } from "../../../types/guid"; import { CipherService } from "../../abstractions/cipher.service"; @@ -39,7 +41,16 @@ import { IdentityView } from "../../models/view/identity.view"; import { LoginView } from "../../models/view/login.view"; import { CipherPermissionsApi } from "../api/cipher-permissions.api"; +const mockSymmetricKey = new SymmetricCryptoKey(makeStaticByteArray(64)); + describe("Cipher DTO", () => { + let encryptService: MockProxy; + + beforeEach(() => { + const containerService = mockContainerService(); + encryptService = containerService.encryptService; + }); + it("Convert from empty CipherData", () => { const data = new CipherData(); const cipher = new Cipher(data); @@ -95,13 +106,12 @@ describe("Cipher DTO", () => { login.decrypt.mockResolvedValue(loginView); cipher.login = login; - const keyService = mock(); - const encryptService = mock(); const cipherService = mock(); encryptService.unwrapSymmetricKey.mockRejectedValue(new Error("Failed to unwrap key")); - - (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); + cipherService.getKeyForCipherKeyDecryption.mockResolvedValue( + new SymmetricCryptoKey(makeStaticByteArray(64)), + ); const cipherView = await cipher.decrypt( await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId), @@ -317,19 +327,11 @@ describe("Cipher DTO", () => { login.decrypt.mockResolvedValue(loginView); cipher.login = login; - const keyService = mock(); - const encryptService = mock(); - const cipherService = mock(); - encryptService.unwrapSymmetricKey.mockResolvedValue( new SymmetricCryptoKey(makeStaticByteArray(64)), ); - (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); - - const cipherView = await cipher.decrypt( - await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId), - ); + const cipherView = await cipher.decrypt(mockSymmetricKey); expect(cipherView).toMatchObject({ id: "id", @@ -445,19 +447,11 @@ describe("Cipher DTO", () => { cipher.permissions = new CipherPermissionsApi(); cipher.archivedDate = undefined; - const keyService = mock(); - const encryptService = mock(); - const cipherService = mock(); - encryptService.unwrapSymmetricKey.mockResolvedValue( new SymmetricCryptoKey(makeStaticByteArray(64)), ); - (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); - - const cipherView = await cipher.decrypt( - await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId), - ); + const cipherView = await cipher.decrypt(mockSymmetricKey); expect(cipherView).toMatchObject({ id: "id", @@ -591,19 +585,11 @@ describe("Cipher DTO", () => { card.decrypt.mockResolvedValue(cardView); cipher.card = card; - const keyService = mock(); - const encryptService = mock(); - const cipherService = mock(); - encryptService.unwrapSymmetricKey.mockResolvedValue( new SymmetricCryptoKey(makeStaticByteArray(64)), ); - (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); - - const cipherView = await cipher.decrypt( - await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId), - ); + const cipherView = await cipher.decrypt(mockSymmetricKey); expect(cipherView).toMatchObject({ id: "id", @@ -761,19 +747,11 @@ describe("Cipher DTO", () => { identity.decrypt.mockResolvedValue(identityView); cipher.identity = identity; - const keyService = mock(); - const encryptService = mock(); - const cipherService = mock(); - encryptService.unwrapSymmetricKey.mockResolvedValue( new SymmetricCryptoKey(makeStaticByteArray(64)), ); - (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); - - const cipherView = await cipher.decrypt( - await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId), - ); + const cipherView = await cipher.decrypt(mockSymmetricKey); expect(cipherView).toMatchObject({ id: "id", diff --git a/libs/common/src/vault/models/domain/cipher.ts b/libs/common/src/vault/models/domain/cipher.ts index abddb73422b..599b1c765e4 100644 --- a/libs/common/src/vault/models/domain/cipher.ts +++ b/libs/common/src/vault/models/domain/cipher.ts @@ -1,5 +1,6 @@ import { Jsonify } from "type-fest"; +import { assertNonNullish } from "@bitwarden/common/auth/utils"; import { Cipher as SdkCipher } from "@bitwarden/sdk-internal"; import { EncString } from "../../../key-management/crypto/models/enc-string"; @@ -123,19 +124,22 @@ export class Cipher extends Domain implements Decryptable { } } - // We are passing the organizationId into the EncString.decrypt() method here, but because the encKey will always be - // present and so the organizationId will not be used. - // We will refactor the EncString.decrypt() in https://bitwarden.atlassian.net/browse/PM-3762 to remove the dependency on the organizationId. - async decrypt(encKey: SymmetricCryptoKey): Promise { + async decrypt(userKeyOrOrgKey: SymmetricCryptoKey): Promise { + assertNonNullish(userKeyOrOrgKey, "userKeyOrOrgKey", "Cipher decryption"); + const model = new CipherView(this); let bypassValidation = true; + // By default, the user/organization key is used for decryption + let cipherDecryptionKey = userKeyOrOrgKey; + + // If there is a cipher key present, unwrap it and use it for decryption if (this.key != null) { const encryptService = Utils.getContainerService().getEncryptService(); try { - const cipherKey = await encryptService.unwrapSymmetricKey(this.key, encKey); - encKey = cipherKey; + const cipherKey = await encryptService.unwrapSymmetricKey(this.key, userKeyOrOrgKey); + cipherDecryptionKey = cipherKey; bypassValidation = false; } catch { model.name = "[error: cannot decrypt]"; @@ -144,22 +148,15 @@ export class Cipher extends Domain implements Decryptable { } } - await this.decryptObj( - this, - model, - ["name", "notes"], - this.organizationId ?? null, - encKey, - ); + await this.decryptObj(this, model, ["name", "notes"], cipherDecryptionKey); switch (this.type) { case CipherType.Login: if (this.login != null) { model.login = await this.login.decrypt( - this.organizationId, bypassValidation, + userKeyOrOrgKey, `Cipher Id: ${this.id}`, - encKey, ); } break; @@ -170,29 +167,17 @@ export class Cipher extends Domain implements Decryptable { break; case CipherType.Card: if (this.card != null) { - model.card = await this.card.decrypt( - this.organizationId, - `Cipher Id: ${this.id}`, - encKey, - ); + model.card = await this.card.decrypt(userKeyOrOrgKey, `Cipher Id: ${this.id}`); } break; case CipherType.Identity: if (this.identity != null) { - model.identity = await this.identity.decrypt( - this.organizationId, - `Cipher Id: ${this.id}`, - encKey, - ); + model.identity = await this.identity.decrypt(userKeyOrOrgKey, `Cipher Id: ${this.id}`); } break; case CipherType.SshKey: if (this.sshKey != null) { - model.sshKey = await this.sshKey.decrypt( - this.organizationId, - `Cipher Id: ${this.id}`, - encKey, - ); + model.sshKey = await this.sshKey.decrypt(userKeyOrOrgKey, `Cipher Id: ${this.id}`); } break; default: @@ -203,9 +188,8 @@ export class Cipher extends Domain implements Decryptable { const attachments: AttachmentView[] = []; for (const attachment of this.attachments) { const decryptedAttachment = await attachment.decrypt( - this.organizationId, + userKeyOrOrgKey, `Cipher Id: ${this.id}`, - encKey, ); attachments.push(decryptedAttachment); } @@ -215,7 +199,7 @@ export class Cipher extends Domain implements Decryptable { if (this.fields != null && this.fields.length > 0) { const fields: FieldView[] = []; for (const field of this.fields) { - const decryptedField = await field.decrypt(this.organizationId, encKey); + const decryptedField = await field.decrypt(userKeyOrOrgKey); fields.push(decryptedField); } model.fields = fields; @@ -224,7 +208,7 @@ export class Cipher extends Domain implements Decryptable { if (this.passwordHistory != null && this.passwordHistory.length > 0) { const passwordHistory: PasswordHistoryView[] = []; for (const ph of this.passwordHistory) { - const decryptedPh = await ph.decrypt(this.organizationId, encKey); + const decryptedPh = await ph.decrypt(userKeyOrOrgKey); passwordHistory.push(decryptedPh); } model.passwordHistory = passwordHistory; diff --git a/libs/common/src/vault/models/domain/fido2-credential.spec.ts b/libs/common/src/vault/models/domain/fido2-credential.spec.ts index 3f43775433e..05504ea5666 100644 --- a/libs/common/src/vault/models/domain/fido2-credential.spec.ts +++ b/libs/common/src/vault/models/domain/fido2-credential.spec.ts @@ -1,4 +1,4 @@ -import { mockEnc } from "../../../../spec"; +import { makeSymmetricCryptoKey, mockContainerService, mockEnc } from "../../../../spec"; import { EncString } from "../../../key-management/crypto/models/enc-string"; import { EncryptionType } from "../../../platform/enums"; import { Fido2CredentialData } from "../data/fido2-credential.data"; @@ -103,7 +103,10 @@ describe("Fido2Credential", () => { credential.discoverable = mockEnc("true"); credential.creationDate = mockDate; - const credentialView = await credential.decrypt(null); + mockContainerService(); + + const cipherKey = makeSymmetricCryptoKey(64); + const credentialView = await credential.decrypt(cipherKey); expect(credentialView).toEqual({ credentialId: "credentialId", diff --git a/libs/common/src/vault/models/domain/fido2-credential.ts b/libs/common/src/vault/models/domain/fido2-credential.ts index eff95c4d0bd..5123b965c85 100644 --- a/libs/common/src/vault/models/domain/fido2-credential.ts +++ b/libs/common/src/vault/models/domain/fido2-credential.ts @@ -46,10 +46,7 @@ export class Fido2Credential extends Domain { this.creationDate = new Date(obj.creationDate); } - async decrypt( - orgId: string | undefined, - encKey?: SymmetricCryptoKey, - ): Promise { + async decrypt(decryptionKey: SymmetricCryptoKey): Promise { const view = await this.decryptObj( this, new Fido2CredentialView(), @@ -65,8 +62,7 @@ export class Fido2Credential extends Domain { "rpName", "userDisplayName", ], - orgId ?? null, - encKey, + decryptionKey, ); const { counter } = await this.decryptObj< @@ -74,7 +70,7 @@ export class Fido2Credential extends Domain { { counter: string; } - >(this, { counter: "" }, ["counter"], orgId ?? null, encKey); + >(this, { counter: "" }, ["counter"], decryptionKey); // Counter will end up as NaN if this fails view.counter = parseInt(counter); @@ -82,8 +78,7 @@ export class Fido2Credential extends Domain { this, { discoverable: "" }, ["discoverable"], - orgId ?? null, - encKey, + decryptionKey, ); view.discoverable = discoverable === "true"; view.creationDate = this.creationDate; diff --git a/libs/common/src/vault/models/domain/field.spec.ts b/libs/common/src/vault/models/domain/field.spec.ts index 0a4bc8e3c29..bcaccbcf369 100644 --- a/libs/common/src/vault/models/domain/field.spec.ts +++ b/libs/common/src/vault/models/domain/field.spec.ts @@ -6,7 +6,7 @@ import { IdentityLinkedIdType, } from "@bitwarden/sdk-internal"; -import { mockEnc, mockFromJson } from "../../../../spec"; +import { mockContainerService, mockEnc, mockFromJson } from "../../../../spec"; import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string"; import { CardLinkedId, IdentityLinkedId, LoginLinkedId } from "../../enums"; import { FieldData } from "../../models/data/field.data"; @@ -22,6 +22,7 @@ describe("Field", () => { value: "encValue", linkedId: null, }; + mockContainerService(); }); it("Convert from empty", () => { diff --git a/libs/common/src/vault/models/domain/field.ts b/libs/common/src/vault/models/domain/field.ts index adec9263515..d61a2dbd82c 100644 --- a/libs/common/src/vault/models/domain/field.ts +++ b/libs/common/src/vault/models/domain/field.ts @@ -33,14 +33,8 @@ export class Field extends Domain { this.value = conditionalEncString(obj.value); } - decrypt(orgId: string | undefined, encKey?: SymmetricCryptoKey): Promise { - return this.decryptObj( - this, - new FieldView(this), - ["name", "value"], - orgId ?? null, - encKey, - ); + decrypt(encKey: SymmetricCryptoKey): Promise { + return this.decryptObj(this, new FieldView(this), ["name", "value"], encKey); } toFieldData(): FieldData { diff --git a/libs/common/src/vault/models/domain/folder.spec.ts b/libs/common/src/vault/models/domain/folder.spec.ts index a837fbb2726..d9e9e265d91 100644 --- a/libs/common/src/vault/models/domain/folder.spec.ts +++ b/libs/common/src/vault/models/domain/folder.spec.ts @@ -1,6 +1,12 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { makeEncString, makeSymmetricCryptoKey, mockEnc, mockFromJson } from "../../../../spec"; +import { + makeEncString, + makeSymmetricCryptoKey, + mockContainerService, + mockEnc, + mockFromJson, +} from "../../../../spec"; import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string"; import { FolderData } from "../../models/data/folder.data"; @@ -15,6 +21,7 @@ describe("Folder", () => { name: "encName", revisionDate: "2022-01-31T12:00:00.000Z", }; + mockContainerService(); }); it("Convert", () => { @@ -33,7 +40,7 @@ describe("Folder", () => { folder.name = mockEnc("encName"); folder.revisionDate = new Date("2022-01-31T12:00:00.000Z"); - const view = await folder.decrypt(); + const view = await folder.decrypt(null); expect(view).toEqual({ id: "id", diff --git a/libs/common/src/vault/models/domain/folder.ts b/libs/common/src/vault/models/domain/folder.ts index 50c67eee01f..c336095f15d 100644 --- a/libs/common/src/vault/models/domain/folder.ts +++ b/libs/common/src/vault/models/domain/folder.ts @@ -39,8 +39,8 @@ export class Folder extends Domain { this.revisionDate = obj.revisionDate != null ? new Date(obj.revisionDate) : null; } - decrypt(): Promise { - return this.decryptObj(this, new FolderView(this), ["name"], null); + decrypt(key: SymmetricCryptoKey): Promise { + return this.decryptObj(this, new FolderView(this), ["name"], key); } async decryptWithKey( diff --git a/libs/common/src/vault/models/domain/identity.spec.ts b/libs/common/src/vault/models/domain/identity.spec.ts index 411f6d1c9ea..1e3bdc15373 100644 --- a/libs/common/src/vault/models/domain/identity.spec.ts +++ b/libs/common/src/vault/models/domain/identity.spec.ts @@ -1,4 +1,4 @@ -import { mockEnc, mockFromJson } from "../../../../spec"; +import { mockContainerService, mockEnc, mockFromJson } from "../../../../spec"; import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string"; import { IdentityData } from "../../models/data/identity.data"; import { Identity } from "../../models/domain/identity"; @@ -27,6 +27,8 @@ describe("Identity", () => { passportNumber: "encpassportNumber", licenseNumber: "enclicenseNumber", }; + + mockContainerService(); }); it("Convert from empty", () => { diff --git a/libs/common/src/vault/models/domain/identity.ts b/libs/common/src/vault/models/domain/identity.ts index e2def3eb386..5424dda1502 100644 --- a/libs/common/src/vault/models/domain/identity.ts +++ b/libs/common/src/vault/models/domain/identity.ts @@ -56,9 +56,8 @@ export class Identity extends Domain { } decrypt( - orgId: string | undefined, + encKey: SymmetricCryptoKey, context: string = "No Cipher Context", - encKey?: SymmetricCryptoKey, ): Promise { return this.decryptObj( this, @@ -83,7 +82,6 @@ export class Identity extends Domain { "passportNumber", "licenseNumber", ], - orgId ?? null, encKey, "DomainType: Identity; " + context, ); diff --git a/libs/common/src/vault/models/domain/login-uri.spec.ts b/libs/common/src/vault/models/domain/login-uri.spec.ts index 2effd1bb9fe..ecdb1bbe00b 100644 --- a/libs/common/src/vault/models/domain/login-uri.spec.ts +++ b/libs/common/src/vault/models/domain/login-uri.spec.ts @@ -1,9 +1,14 @@ -import { MockProxy, mock } from "jest-mock-extended"; +import { MockProxy } from "jest-mock-extended"; import { Jsonify } from "type-fest"; import { UriMatchType } from "@bitwarden/sdk-internal"; -import { mockEnc, mockFromJson } from "../../../../spec"; +import { + makeSymmetricCryptoKey, + mockContainerService, + mockEnc, + mockFromJson, +} from "../../../../spec"; import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; import { EncString } from "../../../key-management/crypto/models/enc-string"; import { UriMatchStrategy } from "../../../models/domain/domain-service"; @@ -14,6 +19,7 @@ import { LoginUri } from "./login-uri"; describe("LoginUri", () => { let data: LoginUriData; + let encryptService: MockProxy; beforeEach(() => { data = { @@ -21,6 +27,9 @@ describe("LoginUri", () => { uriChecksum: "encUriChecksum", match: UriMatchStrategy.Domain, }; + + const containerService = mockContainerService(); + encryptService = containerService.getEncryptService(); }); it("Convert from empty", () => { @@ -83,22 +92,13 @@ describe("LoginUri", () => { }); describe("validateChecksum", () => { - let encryptService: MockProxy; - - beforeEach(() => { - encryptService = mock(); - global.bitwardenContainerService = { - getEncryptService: () => encryptService, - getKeyService: () => null, - }; - }); - it("returns true if checksums match", async () => { const loginUri = new LoginUri(); loginUri.uriChecksum = mockEnc("checksum"); encryptService.hash.mockResolvedValue("checksum"); - const actual = await loginUri.validateChecksum("uri", undefined, undefined); + const key = makeSymmetricCryptoKey(64); + const actual = await loginUri.validateChecksum("uri", key); expect(actual).toBe(true); expect(encryptService.hash).toHaveBeenCalledWith("uri", "sha256"); @@ -109,7 +109,7 @@ describe("LoginUri", () => { loginUri.uriChecksum = mockEnc("checksum"); encryptService.hash.mockResolvedValue("incorrect checksum"); - const actual = await loginUri.validateChecksum("uri", undefined, undefined); + const actual = await loginUri.validateChecksum("uri", undefined); expect(actual).toBe(false); }); diff --git a/libs/common/src/vault/models/domain/login-uri.ts b/libs/common/src/vault/models/domain/login-uri.ts index 42acca25d6f..e784d10dbd7 100644 --- a/libs/common/src/vault/models/domain/login-uri.ts +++ b/libs/common/src/vault/models/domain/login-uri.ts @@ -31,29 +31,27 @@ export class LoginUri extends Domain { } decrypt( - orgId: string | undefined, + encKey: SymmetricCryptoKey, context: string = "No Cipher Context", - encKey?: SymmetricCryptoKey, ): Promise { return this.decryptObj( this, new LoginUriView(this), ["uri"], - orgId ?? null, encKey, context, ); } - async validateChecksum(clearTextUri: string, orgId?: string, encKey?: SymmetricCryptoKey) { + async validateChecksum(clearTextUri: string, encKey: SymmetricCryptoKey) { if (this.uriChecksum == null) { return false; } - const keyService = Utils.getContainerService().getEncryptService(); - const localChecksum = await keyService.hash(clearTextUri, "sha256"); + const encryptService = Utils.getContainerService().getEncryptService(); + const localChecksum = await encryptService.hash(clearTextUri, "sha256"); - const remoteChecksum = await this.uriChecksum.decrypt(orgId ?? null, encKey); + const remoteChecksum = await encryptService.decryptString(this.uriChecksum, encKey); return remoteChecksum === localChecksum; } diff --git a/libs/common/src/vault/models/domain/login.spec.ts b/libs/common/src/vault/models/domain/login.spec.ts index 6ebcfea057a..fbacd912c60 100644 --- a/libs/common/src/vault/models/domain/login.spec.ts +++ b/libs/common/src/vault/models/domain/login.spec.ts @@ -1,6 +1,6 @@ import { MockProxy, mock } from "jest-mock-extended"; -import { mockEnc, mockFromJson } from "../../../../spec"; +import { mockContainerService, mockEnc, mockFromJson } from "../../../../spec"; import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string"; import { UriMatchStrategy } from "../../../models/domain/domain-service"; import { LoginData } from "../../models/data/login.data"; @@ -14,6 +14,10 @@ import { Fido2CredentialView } from "../view/fido2-credential.view"; import { Fido2Credential } from "./fido2-credential"; describe("Login DTO", () => { + beforeEach(() => { + mockContainerService(); + }); + it("Convert from empty LoginData", () => { const data = new LoginData(); const login = new Login(data); @@ -107,7 +111,7 @@ describe("Login DTO", () => { loginUri.validateChecksum.mockResolvedValue(true); login.uris = [loginUri]; - const loginView = await login.decrypt(null, true); + const loginView = await login.decrypt(true, null); expect(loginView).toEqual(expectedView); }); @@ -119,7 +123,7 @@ describe("Login DTO", () => { .mockResolvedValueOnce(true); login.uris = [loginUri, loginUri, loginUri]; - const loginView = await login.decrypt(null, false); + const loginView = await login.decrypt(false, null); expect(loginView).toEqual(expectedView); }); }); diff --git a/libs/common/src/vault/models/domain/login.ts b/libs/common/src/vault/models/domain/login.ts index a9cec13fc7c..830fc48b7d1 100644 --- a/libs/common/src/vault/models/domain/login.ts +++ b/libs/common/src/vault/models/domain/login.ts @@ -44,16 +44,14 @@ export class Login extends Domain { } async decrypt( - orgId: string | undefined, bypassValidation: boolean, + encKey: SymmetricCryptoKey, context: string = "No Cipher Context", - encKey?: SymmetricCryptoKey, ): Promise { const view = await this.decryptObj( this, new LoginView(this), ["username", "password", "totp"], - orgId ?? null, encKey, `DomainType: Login; ${context}`, ); @@ -66,7 +64,7 @@ export class Login extends Domain { continue; } - const uri = await this.uris[i].decrypt(orgId, context, encKey); + const uri = await this.uris[i].decrypt(encKey, context); const uriString = uri.uri; if (uriString == null) { @@ -79,7 +77,7 @@ export class Login extends Domain { // So we bypass the validation if there's no cipher.key or proceed with the validation and // Skip the value if it's been tampered with. const isValidUri = - bypassValidation || (await this.uris[i].validateChecksum(uriString, orgId, encKey)); + bypassValidation || (await this.uris[i].validateChecksum(uriString, encKey)); if (isValidUri) { view.uris.push(uri); @@ -89,7 +87,7 @@ export class Login extends Domain { if (this.fido2Credentials != null) { view.fido2Credentials = await Promise.all( - this.fido2Credentials.map((key) => key.decrypt(orgId, encKey)), + this.fido2Credentials.map((key) => key.decrypt(encKey)), ); } diff --git a/libs/common/src/vault/models/domain/password.spec.ts b/libs/common/src/vault/models/domain/password.spec.ts index 4b2de34beca..fe9918972c4 100644 --- a/libs/common/src/vault/models/domain/password.spec.ts +++ b/libs/common/src/vault/models/domain/password.spec.ts @@ -1,4 +1,4 @@ -import { mockEnc, mockFromJson } from "../../../../spec"; +import { mockContainerService, mockEnc, mockFromJson } from "../../../../spec"; import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string"; import { PasswordHistoryData } from "../../models/data/password-history.data"; import { Password } from "../../models/domain/password"; @@ -11,6 +11,7 @@ describe("Password", () => { password: "encPassword", lastUsedDate: "2022-01-31T12:00:00.000Z", }; + mockContainerService(); }); it("Convert from empty", () => { diff --git a/libs/common/src/vault/models/domain/password.ts b/libs/common/src/vault/models/domain/password.ts index 84e8919b905..960fd3d5e52 100644 --- a/libs/common/src/vault/models/domain/password.ts +++ b/libs/common/src/vault/models/domain/password.ts @@ -22,12 +22,11 @@ export class Password extends Domain { this.lastUsedDate = new Date(obj.lastUsedDate); } - decrypt(orgId: string | undefined, encKey?: SymmetricCryptoKey): Promise { + decrypt(encKey: SymmetricCryptoKey): Promise { return this.decryptObj( this, new PasswordHistoryView(this), ["password"], - orgId ?? null, encKey, "DomainType: PasswordHistory", ); diff --git a/libs/common/src/vault/models/domain/secure-note.spec.ts b/libs/common/src/vault/models/domain/secure-note.spec.ts index e445e9ea035..c5746d8f8e7 100644 --- a/libs/common/src/vault/models/domain/secure-note.spec.ts +++ b/libs/common/src/vault/models/domain/secure-note.spec.ts @@ -1,3 +1,4 @@ +import { mockContainerService } from "../../../../spec"; import { SecureNoteType } from "../../enums"; import { SecureNoteData } from "../data/secure-note.data"; @@ -10,6 +11,8 @@ describe("SecureNote", () => { data = { type: SecureNoteType.Generic, }; + + mockContainerService(); }); it("Convert from empty", () => { diff --git a/libs/common/src/vault/models/domain/ssh-key.spec.ts b/libs/common/src/vault/models/domain/ssh-key.spec.ts index 10149ebc82d..c4ecc1e54f6 100644 --- a/libs/common/src/vault/models/domain/ssh-key.spec.ts +++ b/libs/common/src/vault/models/domain/ssh-key.spec.ts @@ -1,7 +1,7 @@ import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { EncString as SdkEncString, SshKey as SdkSshKey } from "@bitwarden/sdk-internal"; -import { mockEnc } from "../../../../spec"; +import { mockContainerService, mockEnc } from "../../../../spec"; import { SshKeyApi } from "../api/ssh-key.api"; import { SshKeyData } from "../data/ssh-key.data"; @@ -18,6 +18,8 @@ describe("Sshkey", () => { KeyFingerprint: "keyFingerprint", }), ); + + mockContainerService(); }); it("Convert", () => { diff --git a/libs/common/src/vault/models/domain/ssh-key.ts b/libs/common/src/vault/models/domain/ssh-key.ts index a7028321a44..dc9a23403a8 100644 --- a/libs/common/src/vault/models/domain/ssh-key.ts +++ b/libs/common/src/vault/models/domain/ssh-key.ts @@ -24,16 +24,11 @@ export class SshKey extends Domain { this.keyFingerprint = new EncString(obj.keyFingerprint); } - decrypt( - orgId: string | undefined, - context = "No Cipher Context", - encKey?: SymmetricCryptoKey, - ): Promise { + decrypt(encKey: SymmetricCryptoKey, context = "No Cipher Context"): Promise { return this.decryptObj( this, new SshKeyView(), ["privateKey", "publicKey", "keyFingerprint"], - orgId ?? null, encKey, "DomainType: SshKey; " + context, ); diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index fe2926144b8..50823807fcf 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -55,7 +55,7 @@ const ENCRYPTED_BYTES = mock(); const cipherData: CipherData = { id: "id", - organizationId: "orgId", + organizationId: "4ff8c0b2-1d3e-4f8c-9b2d-1d3e4f8c0b2" as OrganizationId, folderId: "folderId", edit: true, viewPassword: true, @@ -119,6 +119,8 @@ describe("Cipher Service", () => { beforeEach(() => { encryptService.encryptFileData.mockReturnValue(Promise.resolve(ENCRYPTED_BYTES)); encryptService.encryptString.mockReturnValue(Promise.resolve(new EncString(ENCRYPTED_TEXT))); + keyService.orgKeys$.mockReturnValue(of({ [orgId]: makeSymmetricCryptoKey(32) as OrgKey })); + keyService.userKey$.mockReturnValue(of(makeSymmetricCryptoKey(64) as UserKey)); // Mock i18nService collator i18nService.collator = { @@ -181,9 +183,6 @@ describe("Cipher Service", () => { const testCipher = new Cipher(cipherData); const expectedRevisionDate = "2022-01-31T12:00:00.000Z"; - keyService.getOrgKey.mockReturnValue( - Promise.resolve(new SymmetricCryptoKey(new Uint8Array(32)) as OrgKey), - ); keyService.makeDataEncKey.mockReturnValue( Promise.resolve([ new SymmetricCryptoKey(new Uint8Array(32)), diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index a3bd229e80c..7eebe960a7f 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -1564,10 +1564,15 @@ export class CipherService implements CipherServiceAbstraction { } async getKeyForCipherKeyDecryption(cipher: Cipher, userId: UserId): Promise { - return ( - (await this.keyService.getOrgKey(cipher.organizationId)) || - ((await this.keyService.getUserKey(userId)) as UserKey) - ); + if (cipher.organizationId == null) { + return await firstValueFrom(this.keyService.userKey$(userId)); + } else { + return await firstValueFrom( + this.keyService + .orgKeys$(userId) + .pipe(map((orgKeys) => orgKeys[cipher.organizationId as OrganizationId] as OrgKey)), + ); + } } async setAddEditCipherInfo(value: AddEditCipherInfo, userId: UserId) { diff --git a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts index bf74bcd69fa..1f5be7f18ab 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts @@ -6,7 +6,6 @@ import { filter, firstValueFrom } from "rxjs"; // eslint-disable-next-line no-restricted-imports import { Collection, CollectionView } from "@bitwarden/admin-console/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { @@ -46,6 +45,7 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { } async parse(data: string): Promise { + const account = await firstValueFrom(this.accountService.activeAccount$); this.result = new ImportResult(); const results: BitwardenJsonExport = JSON.parse(data); if (results == null || results.items == null) { @@ -54,9 +54,9 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { } if (results.encrypted) { - await this.parseEncrypted(results as any); + await this.parseEncrypted(results as any, account.id); } else { - await this.parseDecrypted(results as any); + await this.parseDecrypted(results as any, account.id); } return this.result; @@ -64,9 +64,8 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { private async parseEncrypted( results: BitwardenEncryptedIndividualJsonExport | BitwardenEncryptedOrgJsonExport, + userId: UserId, ) { - const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - if (results.encKeyValidation_DO_NOT_EDIT != null) { const orgKeys = await firstValueFrom(this.keyService.orgKeys$(userId)); let keyForDecryption: SymmetricCryptoKey = orgKeys?.[this.organizationId]; @@ -84,8 +83,8 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { } const groupingsMap = this.organization - ? await this.parseCollections(userId, results as BitwardenEncryptedOrgJsonExport) - : await this.parseFolders(results as BitwardenEncryptedIndividualJsonExport); + ? await this.parseCollections(results as BitwardenEncryptedOrgJsonExport, userId) + : await this.parseFolders(results as BitwardenEncryptedIndividualJsonExport, userId); for (const c of results.items) { const cipher = CipherWithIdExport.toDomain(c); @@ -125,12 +124,11 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { private async parseDecrypted( results: BitwardenUnEncryptedIndividualJsonExport | BitwardenUnEncryptedOrgJsonExport, + userId: UserId, ) { - const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - const groupingsMap = this.organization - ? await this.parseCollections(userId, results as BitwardenUnEncryptedOrgJsonExport) - : await this.parseFolders(results as BitwardenUnEncryptedIndividualJsonExport); + ? await this.parseCollections(results as BitwardenUnEncryptedOrgJsonExport, userId) + : await this.parseFolders(results as BitwardenUnEncryptedIndividualJsonExport, userId); results.items.forEach((c) => { const cipher = CipherWithIdExport.toView(c); @@ -169,11 +167,14 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { private async parseFolders( data: BitwardenUnEncryptedIndividualJsonExport | BitwardenEncryptedIndividualJsonExport, + userId: UserId, ): Promise> | null { if (data.folders == null) { return null; } + const userKey = await firstValueFrom(this.keyService.userKey$(userId)); + const groupingsMap = new Map(); for (const f of data.folders) { @@ -181,7 +182,7 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { if (data.encrypted) { const folder = FolderWithIdExport.toDomain(f); if (folder != null) { - folderView = await folder.decrypt(); + folderView = await folder.decrypt(userKey); } } else { folderView = FolderWithIdExport.toView(f); @@ -196,8 +197,8 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { } private async parseCollections( - userId: UserId, data: BitwardenUnEncryptedOrgJsonExport | BitwardenEncryptedOrgJsonExport, + userId: UserId, ): Promise> | null { if (data.collections == null) { return null;