diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index 14a218c7141..93c381e4093 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -186,7 +186,7 @@ export class EditCommand { return Response.notFound(); } - let folderView = await folder.decrypt(); + let folderView = await folder.decrypt(activeUserId); folderView = FolderExport.toView(req, folderView); const userKey = await this.keyService.getUserKey(activeUserId); @@ -194,7 +194,7 @@ export class EditCommand { try { const folder = await this.folderApiService.save(encFolder, activeUserId); const updatedFolder = new Folder(folder); - const decFolder = await updatedFolder.decrypt(); + const decFolder = await updatedFolder.decrypt(activeUserId); 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 a994ad3117c..57b7b84c700 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -420,7 +420,7 @@ export class GetCommand extends DownloadCommand { if (Utils.isGuid(id)) { const folder = await this.folderService.getFromState(id, activeUserId); if (folder != null) { - decFolder = await folder.decrypt(); + decFolder = await folder.decrypt(activeUserId); } } else if (id.trim() !== "") { let folders = await this.folderService.getAllDecryptedFromState(activeUserId); diff --git a/apps/cli/src/tools/send/commands/receive.command.ts b/apps/cli/src/tools/send/commands/receive.command.ts index a412f7c1667..3a8f38dc3b1 100644 --- a/apps/cli/src/tools/send/commands/receive.command.ts +++ b/apps/cli/src/tools/send/commands/receive.command.ts @@ -5,6 +5,8 @@ import * as inquirer from "inquirer"; import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; @@ -37,6 +39,7 @@ export class SendReceiveCommand extends DownloadCommand { private platformUtilsService: PlatformUtilsService, private environmentService: EnvironmentService, private sendApiService: SendApiService, + private accountService: AccountService, apiService: ApiService, ) { super(encryptService, apiService); @@ -152,6 +155,8 @@ export class SendReceiveCommand extends DownloadCommand { key: Uint8Array, ): Promise { try { + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const sendResponse = await this.sendApiService.postSendAccess( id, this.sendAccessRequest, @@ -160,7 +165,7 @@ export class SendReceiveCommand extends DownloadCommand { const sendAccess = new SendAccess(sendResponse); this.decKey = await this.keyService.makeSendKey(key); - return await sendAccess.decrypt(this.decKey); + return await sendAccess.decrypt(activeUserId, this.decKey); } catch (e) { if (e instanceof ErrorResponse) { if (e.statusCode === 401) { diff --git a/apps/cli/src/tools/send/send.program.ts b/apps/cli/src/tools/send/send.program.ts index 6c643e04cd0..9cbb5d9526b 100644 --- a/apps/cli/src/tools/send/send.program.ts +++ b/apps/cli/src/tools/send/send.program.ts @@ -123,6 +123,7 @@ export class SendProgram extends BaseProgram { this.serviceContainer.platformUtilsService, this.serviceContainer.environmentService, this.serviceContainer.sendApiService, + this.serviceContainer.accountService, this.serviceContainer.apiService, ); const response = await cmd.run(url, options); diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 03a205e9c48..b2989fa8f36 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(activeUserId); const res = new FolderResponse(decFolder); return Response.success(res); } catch (e) { diff --git a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts index b91bc932e83..7ee27daae55 100644 --- a/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts +++ b/apps/web/src/app/auth/emergency-access/services/emergency-access.service.ts @@ -264,7 +264,9 @@ export class EmergencyAccessService let ciphers: CipherView[] = []; const ciphersEncrypted = response.ciphers.map((c) => new Cipher(c)); - ciphers = await Promise.all(ciphersEncrypted.map(async (c) => c.decrypt(grantorUserKey))); + ciphers = await Promise.all( + ciphersEncrypted.map(async (c) => c.decrypt(grantorUserKey, activeUserId)), + ); return ciphers.sort(this.cipherService.getLocaleSortingFunction()); } diff --git a/apps/web/src/app/dirt/reports/pages/cipher-report.component.ts b/apps/web/src/app/dirt/reports/pages/cipher-report.component.ts index 69dd360ad31..accc90b7c8a 100644 --- a/apps/web/src/app/dirt/reports/pages/cipher-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/cipher-report.component.ts @@ -239,6 +239,7 @@ export class CipherReportComponent implements OnDestroy { // convert cipher to cipher view model const updatedCipherView = await updatedCipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), + activeUserId, ); // request downstream report status if the cipher was updated diff --git a/apps/web/src/app/tools/send/send-access/access.component.ts b/apps/web/src/app/tools/send/send-access/access.component.ts index 273f1c8c979..9b12587d099 100644 --- a/apps/web/src/app/tools/send/send-access/access.component.ts +++ b/apps/web/src/app/tools/send/send-access/access.component.ts @@ -3,7 +3,10 @@ import { Component, OnInit } from "@angular/core"; import { FormBuilder } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; +import { firstValueFrom } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -64,6 +67,7 @@ export class AccessComponent implements OnInit { private i18nService: I18nService, private layoutWrapperDataService: AnonLayoutWrapperDataService, protected formBuilder: FormBuilder, + private accountService: AccountService, ) {} protected get expirationDate() { @@ -118,7 +122,8 @@ export class AccessComponent implements OnInit { this.passwordRequired = false; const sendAccess = new SendAccess(sendResponse); this.decKey = await this.keyService.makeSendKey(keyArray); - this.send = await sendAccess.decrypt(this.decKey); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + this.send = await sendAccess.decrypt(activeUserId, this.decKey); } catch (e) { if (e instanceof ErrorResponse) { if (e.statusCode === 401) { diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index b48db2bba91..0b422711186 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -498,6 +498,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { updatedCipherView = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), + activeUserId, ); } else { const updatedCipher = await this.cipherService.get( diff --git a/libs/common/spec/utils.ts b/libs/common/spec/utils.ts index db9a7e0842c..a0588b10016 100644 --- a/libs/common/spec/utils.ts +++ b/libs/common/spec/utils.ts @@ -27,10 +27,9 @@ export function BuildTestObject( return Object.assign(constructor === null ? {} : new constructor(), def) as T; } +/** @deprecated */ export function mockEnc(s: string): MockProxy { const mocked = mock(); - mocked.decrypt.mockResolvedValue(s); - return mocked; } diff --git a/libs/common/src/key-management/crypto/models/enc-string.spec.ts b/libs/common/src/key-management/crypto/models/enc-string.spec.ts index 1be28d58963..64472d566cc 100644 --- a/libs/common/src/key-management/crypto/models/enc-string.spec.ts +++ b/libs/common/src/key-management/crypto/models/enc-string.spec.ts @@ -1,15 +1,4 @@ -import { mock, MockProxy } from "jest-mock-extended"; - -// 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 { makeStaticByteArray } from "../../../../spec"; import { EncryptionType } from "../../../platform/enums"; -import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; -import { ContainerService } from "../../../platform/services/container.service"; -import { UserKey, OrgKey } from "../../../types/key"; -import { EncryptService } from "../abstractions/encrypt.service"; import { EncString } from "./enc-string"; @@ -77,41 +66,6 @@ describe("EncString", () => { expect(dataBytes.length).toBeGreaterThan(0); }); }); - - describe("decrypt", () => { - const encString = new EncString(EncryptionType.Rsa2048_OaepSha256_B64, "data"); - - const keyService = mock(); - keyService.hasUserKey.mockResolvedValue(true); - keyService.getUserKey.mockResolvedValue( - new SymmetricCryptoKey(makeStaticByteArray(32)) as UserKey, - ); - - const encryptService = mock(); - encryptService.decryptString - .calledWith(encString, expect.anything()) - .mockResolvedValue("decrypted"); - - beforeEach(() => { - (window as any).bitwardenContainerService = new ContainerService( - keyService, - encryptService, - ); - }); - - it("decrypts correctly", async () => { - const decrypted = await encString.decrypt(null); - - expect(decrypted).toBe("decrypted"); - }); - - it("result should be cached", async () => { - const decrypted = await encString.decrypt(null); - expect(encryptService.decryptString).toBeCalledTimes(1); - - expect(decrypted).toBe("decrypted"); - }); - }); }); describe("AesCbc256_B64", () => { @@ -249,66 +203,6 @@ describe("EncString", () => { }); }); - describe("decrypt", () => { - let keyService: MockProxy; - let encryptService: MockProxy; - let encString: EncString; - - beforeEach(() => { - keyService = mock(); - encryptService = mock(); - encString = new EncString(null); - - (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); - }); - - it("handles value it can't decrypt", async () => { - encryptService.decryptString.mockRejectedValue("error"); - - (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); - - const decrypted = await encString.decrypt(null); - - expect(decrypted).toBe("[error: cannot decrypt]"); - - expect(encString).toEqual({ - decryptedValue: "[error: cannot decrypt]", - encryptedString: null, - }); - }); - - it("uses provided key without depending on KeyService", async () => { - const key = mock(); - - await encString.decrypt(null, key); - - expect(keyService.getUserKey).not.toHaveBeenCalled(); - expect(encryptService.decryptString).toHaveBeenCalledWith(encString, key); - }); - - it("gets an organization key if required", async () => { - const orgKey = mock(); - - keyService.getOrgKey.calledWith("orgId").mockResolvedValue(orgKey); - - await encString.decrypt("orgId", null); - - expect(keyService.getOrgKey).toHaveBeenCalledWith("orgId"); - expect(encryptService.decryptString).toHaveBeenCalledWith(encString, orgKey); - }); - - it("gets the user's decryption key if required", async () => { - const userKey = mock(); - - keyService.getUserKey.mockResolvedValue(userKey); - - await encString.decrypt(null, null); - - expect(keyService.getUserKey).toHaveBeenCalledWith(); - expect(encryptService.decryptString).toHaveBeenCalledWith(encString, userKey); - }); - }); - describe("toJSON", () => { it("Should be represented by the encrypted string", () => { const encString = new EncString(EncryptionType.AesCbc256_B64, "data", "iv"); diff --git a/libs/common/src/key-management/crypto/models/enc-string.ts b/libs/common/src/key-management/crypto/models/enc-string.ts index a737771f799..f895e807abf 100644 --- a/libs/common/src/key-management/crypto/models/enc-string.ts +++ b/libs/common/src/key-management/crypto/models/enc-string.ts @@ -2,11 +2,10 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString as SdkEncString } from "@bitwarden/sdk-internal"; import { EncryptionType, EXPECTED_NUM_PARTS_BY_ENCRYPTION_TYPE } from "../../../platform/enums"; -import { Utils } from "../../../platform/misc/utils"; -import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; export const DECRYPT_ERROR = "[error: cannot decrypt]"; @@ -156,46 +155,6 @@ export class EncString { return EXPECTED_NUM_PARTS_BY_ENCRYPTION_TYPE[encType] === encPieces.length; } - - /** - * @deprecated - This function is deprecated. Use EncryptService.decryptString instead. - * @returns - The decrypted string, or `[error: cannot decrypt]` if decryption fails. - */ - async decrypt( - orgId: string | null, - key: SymmetricCryptoKey | null = null, - context?: string, - ): 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.decryptString(this, key); - } catch (e) { - // eslint-disable-next-line no-console - console.error( - "[EncString Generic Decrypt] failed to decrypt encstring. Context: " + - (context ?? "No context"), - e, - ); - this.decryptedValue = DECRYPT_ERROR; - } - return this.decryptedValue; - } - - private async getKeyForDecryption(orgId: string) { - const keyService = Utils.getContainerService().getKeyService(); - return orgId != null ? await keyService.getOrgKey(orgId) : await keyService.getUserKey(); - } } /** diff --git a/libs/common/src/platform/interfaces/decryptable.interface.ts b/libs/common/src/platform/interfaces/decryptable.interface.ts index 35895bfd6ff..9ad934bbf46 100644 --- a/libs/common/src/platform/interfaces/decryptable.interface.ts +++ b/libs/common/src/platform/interfaces/decryptable.interface.ts @@ -1,3 +1,5 @@ +import { UserId } from "@bitwarden/user-core"; + import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; import { InitializerMetadata } from "./initializer-metadata.interface"; @@ -8,5 +10,5 @@ import { InitializerMetadata } from "./initializer-metadata.interface"; * @example Cipher implements Decryptable */ export interface Decryptable extends InitializerMetadata { - decrypt: (key: SymmetricCryptoKey) => Promise; + decrypt: (key: SymmetricCryptoKey, userId: UserId) => Promise; } diff --git a/libs/common/src/platform/models/domain/domain-base.ts b/libs/common/src/platform/models/domain/domain-base.ts index a144353f5bc..e20ef42bf75 100644 --- a/libs/common/src/platform/models/domain/domain-base.ts +++ b/libs/common/src/platform/models/domain/domain-base.ts @@ -1,7 +1,12 @@ +import { firstValueFrom, map } from "rxjs"; import { ConditionalExcept, ConditionalKeys } from "type-fest"; +import { OrganizationId } from "@bitwarden/common/types/guid"; +import { UserId } from "@bitwarden/user-core"; + import { EncString } from "../../../key-management/crypto/models/enc-string"; import { View } from "../../../models/view/view"; +import { Utils } from "../../misc/utils"; import { SymmetricCryptoKey } from "./symmetric-crypto-key"; @@ -69,21 +74,34 @@ export default class Domain { } } + /** @deprecated */ protected async decryptObj( domain: DomainEncryptableKeys, viewModel: ViewEncryptableKeys, props: EncryptableKeys[], + userId: UserId, orgId: string | null, key: SymmetricCryptoKey | null = null, - objectContext: string = "No Domain Context", + _objectContext: string = "No Domain Context", ): Promise { + const keyService = Utils.getContainerService().getKeyService(); + if (key == null) { + if (orgId != null) { + key = await firstValueFrom( + keyService + .orgKeys$(userId) + .pipe(map((orgKeys) => orgKeys![orgId as OrganizationId] ?? null)), + ); + } else { + key = await firstValueFrom(keyService.userKey$(userId)); + } + } + + const encService = Utils.getContainerService().getEncryptService(); for (const prop of props) { - viewModel[prop] = - (await domain[prop]?.decrypt( - orgId, - key, - `Property: ${prop as string}; ObjectContext: ${objectContext}`, - )) ?? null; + if (domain[prop] != null) { + viewModel[prop] = await encService.decryptString(domain[prop], key!); + } } return viewModel as V; 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..9fc04b88b8d 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 @@ -67,7 +67,7 @@ describe("SendAccess", () => { sendAccess.expirationDate = new Date("2022-01-31T12:00:00.000Z"); sendAccess.creatorIdentifier = "creatorIdentifier"; - const view = await sendAccess.decrypt(null); + const view = await sendAccess.decrypt(null, null); expect(text.decrypt).toHaveBeenCalledTimes(1); 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..87efa4eb84d 100644 --- a/libs/common/src/tools/send/models/domain/send-access.ts +++ b/libs/common/src/tools/send/models/domain/send-access.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { UserId } from "@bitwarden/user-core"; + import { EncString } from "../../../../key-management/crypto/models/enc-string"; import Domain from "../../../../platform/models/domain/domain-base"; import { SymmetricCryptoKey } from "../../../../platform/models/domain/symmetric-crypto-key"; @@ -51,17 +53,17 @@ export class SendAccess extends Domain { } } - async decrypt(key: SymmetricCryptoKey): Promise { + async decrypt(userId: UserId, key: SymmetricCryptoKey): Promise { const model = new SendAccessView(this); - await this.decryptObj(this, model, ["name"], null, key); + await this.decryptObj(this, model, ["name"], userId, null, key); switch (this.type) { case SendType.File: - model.file = await this.file.decrypt(key); + model.file = await this.file.decrypt(userId, key); break; case SendType.Text: - model.text = await this.text.decrypt(key); + model.text = await this.text.decrypt(userId, key); break; default: break; 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..bd9e397c61b 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 @@ -45,7 +45,7 @@ describe("SendFile", () => { sendFile.sizeName = "1.1 KB"; sendFile.fileName = mockEnc("fileName"); - const view = await sendFile.decrypt(null); + const view = await sendFile.decrypt(null, null); expect(view).toEqual({ fileName: "fileName", 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..ba07a130fbd 100644 --- a/libs/common/src/tools/send/models/domain/send-file.ts +++ b/libs/common/src/tools/send/models/domain/send-file.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { UserId } from "@bitwarden/user-core"; + import { EncString } from "../../../../key-management/crypto/models/enc-string"; import Domain from "../../../../platform/models/domain/domain-base"; import { SymmetricCryptoKey } from "../../../../platform/models/domain/symmetric-crypto-key"; @@ -33,11 +35,12 @@ export class SendFile extends Domain { ); } - async decrypt(key: SymmetricCryptoKey): Promise { + async decrypt(userId: UserId, key: SymmetricCryptoKey): Promise { return await this.decryptObj( this, new SendFileView(this), ["fileName"], + userId, 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..6c5e8e5580d 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 @@ -37,7 +37,7 @@ describe("SendText", () => { secureNote.text = mockEnc("text"); secureNote.hidden = true; - const view = await secureNote.decrypt(null); + const view = await secureNote.decrypt(null, null); expect(view).toEqual({ text: "text", 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..882def46165 100644 --- a/libs/common/src/tools/send/models/domain/send-text.ts +++ b/libs/common/src/tools/send/models/domain/send-text.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { UserId } from "@bitwarden/user-core"; + import { EncString } from "../../../../key-management/crypto/models/enc-string"; import Domain from "../../../../platform/models/domain/domain-base"; import { SymmetricCryptoKey } from "../../../../platform/models/domain/symmetric-crypto-key"; @@ -29,11 +31,12 @@ export class SendText extends Domain { ); } - decrypt(key: SymmetricCryptoKey): Promise { + decrypt(userId: UserId, key: SymmetricCryptoKey): Promise { return this.decryptObj( this, new SendTextView(this), ["text"], + userId, null, key, ); 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..ccaadd4dbd0 100644 --- a/libs/common/src/tools/send/models/domain/send.spec.ts +++ b/libs/common/src/tools/send/models/domain/send.spec.ts @@ -130,12 +130,6 @@ describe("Send", () => { const view = await send.decrypt(userId); expect(text.decrypt).toHaveBeenNthCalledWith(1, "cryptoKey"); - expect(send.name.decrypt).toHaveBeenNthCalledWith( - 1, - null, - "cryptoKey", - "Property: name; ObjectContext: No Domain Context", - ); expect(view).toMatchObject({ id: "id", diff --git a/libs/common/src/tools/send/models/domain/send.ts b/libs/common/src/tools/send/models/domain/send.ts index 48129d4314a..ebe1b8f70d4 100644 --- a/libs/common/src/tools/send/models/domain/send.ts +++ b/libs/common/src/tools/send/models/domain/send.ts @@ -89,14 +89,21 @@ 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"], + userId, + null, + model.cryptoKey, + ); switch (this.type) { case SendType.File: - model.file = await this.file.decrypt(model.cryptoKey); + model.file = await this.file.decrypt(userId, model.cryptoKey); break; case SendType.Text: - model.text = await this.text.decrypt(model.cryptoKey); + model.text = await this.text.decrypt(userId, model.cryptoKey); break; default: break; diff --git a/libs/common/src/vault/models/domain/attachment.spec.ts b/libs/common/src/vault/models/domain/attachment.spec.ts index 972c77537ff..a7c871a9d5f 100644 --- a/libs/common/src/vault/models/domain/attachment.spec.ts +++ b/libs/common/src/vault/models/domain/attachment.spec.ts @@ -86,7 +86,7 @@ describe("Attachment", () => { new SymmetricCryptoKey(makeStaticByteArray(64)), ); - const view = await attachment.decrypt(null); + const view = await attachment.decrypt(null, null); expect(view).toEqual({ id: "id", @@ -110,7 +110,7 @@ describe("Attachment", () => { it("uses the provided key without depending on KeyService", async () => { const providedKey = mock(); - await attachment.decrypt(null, "", providedKey); + await attachment.decrypt(null, null, "", providedKey); expect(keyService.getUserKey).not.toHaveBeenCalled(); expect(encryptService.unwrapSymmetricKey).toHaveBeenCalledWith(attachment.key, providedKey); @@ -120,7 +120,7 @@ describe("Attachment", () => { const orgKey = mock(); keyService.getOrgKey.calledWith("orgId").mockResolvedValue(orgKey); - await attachment.decrypt("orgId", "", null); + await attachment.decrypt(null, "orgId", "", null); expect(keyService.getOrgKey).toHaveBeenCalledWith("orgId"); expect(encryptService.unwrapSymmetricKey).toHaveBeenCalledWith(attachment.key, orgKey); diff --git a/libs/common/src/vault/models/domain/attachment.ts b/libs/common/src/vault/models/domain/attachment.ts index 7b43af9be55..4cc66158caf 100644 --- a/libs/common/src/vault/models/domain/attachment.ts +++ b/libs/common/src/vault/models/domain/attachment.ts @@ -2,6 +2,7 @@ import { Jsonify } from "type-fest"; import { OrgKey, UserKey } from "@bitwarden/common/types/key"; import { Attachment as SdkAttachment } from "@bitwarden/sdk-internal"; +import { UserId } from "@bitwarden/user-core"; import { EncString } from "../../../key-management/crypto/models/enc-string"; import { Utils } from "../../../platform/misc/utils"; @@ -34,6 +35,7 @@ export class Attachment extends Domain { } async decrypt( + userId: UserId, orgId: string | undefined, context = "No Cipher Context", encKey?: SymmetricCryptoKey, @@ -42,6 +44,7 @@ export class Attachment extends Domain { this, new AttachmentView(this), ["fileName"], + userId, orgId ?? null, encKey, "DomainType: Attachment; " + context, diff --git a/libs/common/src/vault/models/domain/card.spec.ts b/libs/common/src/vault/models/domain/card.spec.ts index a4d242329a4..2e376490b95 100644 --- a/libs/common/src/vault/models/domain/card.spec.ts +++ b/libs/common/src/vault/models/domain/card.spec.ts @@ -58,7 +58,7 @@ describe("Card", () => { card.expYear = mockEnc("expYear"); card.code = mockEnc("code"); - const view = await card.decrypt(null); + const view = await card.decrypt(null, null); 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..b03e349a8d0 100644 --- a/libs/common/src/vault/models/domain/card.ts +++ b/libs/common/src/vault/models/domain/card.ts @@ -1,6 +1,7 @@ import { Jsonify } from "type-fest"; import { Card as SdkCard } from "@bitwarden/sdk-internal"; +import { UserId } from "@bitwarden/user-core"; import { EncString } from "../../../key-management/crypto/models/enc-string"; import Domain from "../../../platform/models/domain/domain-base"; @@ -32,6 +33,7 @@ export class Card extends Domain { } async decrypt( + userId: UserId, orgId: string | undefined, context = "No Cipher Context", encKey?: SymmetricCryptoKey, @@ -40,6 +42,7 @@ export class Card extends Domain { this, new CardView(), ["cardholderName", "brand", "number", "expMonth", "expYear", "code"], + userId, 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 4052c9e5338..a70bd4c99be 100644 --- a/libs/common/src/vault/models/domain/cipher.spec.ts +++ b/libs/common/src/vault/models/domain/cipher.spec.ts @@ -105,6 +105,7 @@ describe("Cipher DTO", () => { const cipherView = await cipher.decrypt( await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId), + null, ); expect(cipherView).toMatchObject({ @@ -329,6 +330,7 @@ describe("Cipher DTO", () => { const cipherView = await cipher.decrypt( await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId), + null, ); expect(cipherView).toMatchObject({ @@ -457,6 +459,7 @@ describe("Cipher DTO", () => { const cipherView = await cipher.decrypt( await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId), + null, ); expect(cipherView).toMatchObject({ @@ -603,6 +606,7 @@ describe("Cipher DTO", () => { const cipherView = await cipher.decrypt( await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId), + null, ); expect(cipherView).toMatchObject({ @@ -773,6 +777,7 @@ describe("Cipher DTO", () => { const cipherView = await cipher.decrypt( await cipherService.getKeyForCipherKeyDecryption(cipher, mockUserId), + null, ); expect(cipherView).toMatchObject({ diff --git a/libs/common/src/vault/models/domain/cipher.ts b/libs/common/src/vault/models/domain/cipher.ts index 5e284232936..6688334b869 100644 --- a/libs/common/src/vault/models/domain/cipher.ts +++ b/libs/common/src/vault/models/domain/cipher.ts @@ -1,6 +1,7 @@ import { Jsonify } from "type-fest"; import { Cipher as SdkCipher } from "@bitwarden/sdk-internal"; +import { UserId } from "@bitwarden/user-core"; import { EncString } from "../../../key-management/crypto/models/enc-string"; import { asUuid, uuidAsString } from "../../../platform/abstractions/sdk/sdk.service"; @@ -123,7 +124,7 @@ 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(encKey: SymmetricCryptoKey, userId: UserId): Promise { const model = new CipherView(this); let bypassValidation = true; @@ -145,14 +146,17 @@ export class Cipher extends Domain implements Decryptable { this, model, ["name", "notes"], + userId, this.organizationId ?? null, encKey, + "cipher", ); switch (this.type) { case CipherType.Login: if (this.login != null) { model.login = await this.login.decrypt( + userId, this.organizationId, bypassValidation, `Cipher Id: ${this.id}`, @@ -168,6 +172,7 @@ export class Cipher extends Domain implements Decryptable { case CipherType.Card: if (this.card != null) { model.card = await this.card.decrypt( + userId, this.organizationId, `Cipher Id: ${this.id}`, encKey, @@ -177,6 +182,7 @@ export class Cipher extends Domain implements Decryptable { case CipherType.Identity: if (this.identity != null) { model.identity = await this.identity.decrypt( + userId, this.organizationId, `Cipher Id: ${this.id}`, encKey, @@ -186,6 +192,7 @@ export class Cipher extends Domain implements Decryptable { case CipherType.SshKey: if (this.sshKey != null) { model.sshKey = await this.sshKey.decrypt( + userId, this.organizationId, `Cipher Id: ${this.id}`, encKey, @@ -200,6 +207,7 @@ export class Cipher extends Domain implements Decryptable { const attachments: AttachmentView[] = []; for (const attachment of this.attachments) { const decryptedAttachment = await attachment.decrypt( + userId, this.organizationId, `Cipher Id: ${this.id}`, encKey, @@ -212,7 +220,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(userId, this.organizationId, encKey); fields.push(decryptedField); } model.fields = fields; @@ -221,7 +229,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(userId, this.organizationId, encKey); 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..d22732414dc 100644 --- a/libs/common/src/vault/models/domain/fido2-credential.spec.ts +++ b/libs/common/src/vault/models/domain/fido2-credential.spec.ts @@ -103,7 +103,7 @@ describe("Fido2Credential", () => { credential.discoverable = mockEnc("true"); credential.creationDate = mockDate; - const credentialView = await credential.decrypt(null); + const credentialView = await credential.decrypt(null, null); 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..bfc0153bd62 100644 --- a/libs/common/src/vault/models/domain/fido2-credential.ts +++ b/libs/common/src/vault/models/domain/fido2-credential.ts @@ -1,6 +1,7 @@ import { Jsonify } from "type-fest"; import { Fido2Credential as SdkFido2Credential } from "@bitwarden/sdk-internal"; +import { UserId } from "@bitwarden/user-core"; import { EncString } from "../../../key-management/crypto/models/enc-string"; import Domain from "../../../platform/models/domain/domain-base"; @@ -47,6 +48,7 @@ export class Fido2Credential extends Domain { } async decrypt( + userId: UserId, orgId: string | undefined, encKey?: SymmetricCryptoKey, ): Promise { @@ -65,6 +67,7 @@ export class Fido2Credential extends Domain { "rpName", "userDisplayName", ], + userId, orgId ?? null, encKey, ); @@ -74,7 +77,7 @@ export class Fido2Credential extends Domain { { counter: string; } - >(this, { counter: "" }, ["counter"], orgId ?? null, encKey); + >(this, { counter: "" }, ["counter"], userId, orgId ?? null, encKey); // Counter will end up as NaN if this fails view.counter = parseInt(counter); @@ -82,6 +85,7 @@ export class Fido2Credential extends Domain { this, { discoverable: "" }, ["discoverable"], + userId, orgId ?? null, encKey, ); diff --git a/libs/common/src/vault/models/domain/field.spec.ts b/libs/common/src/vault/models/domain/field.spec.ts index d99336adad0..7214e2f7bae 100644 --- a/libs/common/src/vault/models/domain/field.spec.ts +++ b/libs/common/src/vault/models/domain/field.spec.ts @@ -58,7 +58,7 @@ describe("Field", () => { field.name = mockEnc("encName"); field.value = mockEnc("encValue"); - const view = await field.decrypt(null); + const view = await field.decrypt(null, null); expect(view).toEqual({ type: 0, diff --git a/libs/common/src/vault/models/domain/field.ts b/libs/common/src/vault/models/domain/field.ts index 2ee3a9af8a5..5d7519f6e9f 100644 --- a/libs/common/src/vault/models/domain/field.ts +++ b/libs/common/src/vault/models/domain/field.ts @@ -1,6 +1,7 @@ import { Jsonify } from "type-fest"; import { Field as SdkField, LinkedIdType as SdkLinkedIdType } from "@bitwarden/sdk-internal"; +import { UserId } from "@bitwarden/user-core"; import { EncString } from "../../../key-management/crypto/models/enc-string"; import Domain from "../../../platform/models/domain/domain-base"; @@ -28,11 +29,16 @@ export class Field extends Domain { this.value = conditionalEncString(obj.value); } - decrypt(orgId: string | undefined, encKey?: SymmetricCryptoKey): Promise { + decrypt( + userId: UserId, + orgId: string | undefined, + encKey?: SymmetricCryptoKey, + ): Promise { return this.decryptObj( this, new FieldView(this), ["name", "value"], + userId, orgId ?? null, encKey, ); diff --git a/libs/common/src/vault/models/domain/folder.spec.ts b/libs/common/src/vault/models/domain/folder.spec.ts index a837fbb2726..4b1a9222b07 100644 --- a/libs/common/src/vault/models/domain/folder.spec.ts +++ b/libs/common/src/vault/models/domain/folder.spec.ts @@ -33,7 +33,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..04c21e7393a 100644 --- a/libs/common/src/vault/models/domain/folder.ts +++ b/libs/common/src/vault/models/domain/folder.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { UserId } from "@bitwarden/user-core"; + import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; import { EncString } from "../../../key-management/crypto/models/enc-string"; import Domain from "../../../platform/models/domain/domain-base"; @@ -39,8 +41,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(userId: UserId): Promise { + return this.decryptObj(this, new FolderView(this), ["name"], userId, null); } 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 c2c2363fa0d..9e9eda81e8a 100644 --- a/libs/common/src/vault/models/domain/identity.spec.ts +++ b/libs/common/src/vault/models/domain/identity.spec.ts @@ -107,7 +107,7 @@ describe("Identity", () => { identity.passportNumber = mockEnc("mockPassportNumber"); identity.licenseNumber = mockEnc("mockLicenseNumber"); - const view = await identity.decrypt(null); + const view = await identity.decrypt(null, null); expect(view).toEqual({ _firstName: "mockFirstName", diff --git a/libs/common/src/vault/models/domain/identity.ts b/libs/common/src/vault/models/domain/identity.ts index e2def3eb386..60c8346c505 100644 --- a/libs/common/src/vault/models/domain/identity.ts +++ b/libs/common/src/vault/models/domain/identity.ts @@ -1,6 +1,7 @@ import { Jsonify } from "type-fest"; import { Identity as SdkIdentity } from "@bitwarden/sdk-internal"; +import { UserId } from "@bitwarden/user-core"; import { EncString } from "../../../key-management/crypto/models/enc-string"; import Domain from "../../../platform/models/domain/domain-base"; @@ -56,6 +57,7 @@ export class Identity extends Domain { } decrypt( + userId: UserId, orgId: string | undefined, context: string = "No Cipher Context", encKey?: SymmetricCryptoKey, @@ -83,6 +85,7 @@ export class Identity extends Domain { "passportNumber", "licenseNumber", ], + userId, 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 982b435384b..c027fdbef1e 100644 --- a/libs/common/src/vault/models/domain/login-uri.spec.ts +++ b/libs/common/src/vault/models/domain/login-uri.spec.ts @@ -53,7 +53,7 @@ describe("LoginUri", () => { loginUri.match = UriMatchStrategy.Exact; loginUri.uri = mockEnc("uri"); - const view = await loginUri.decrypt(null); + const view = await loginUri.decrypt(null, null); expect(view).toEqual({ _uri: "uri", @@ -77,7 +77,7 @@ describe("LoginUri", () => { loginUri.uriChecksum = mockEnc("checksum"); encryptService.hash.mockResolvedValue("checksum"); - const actual = await loginUri.validateChecksum("uri", undefined, undefined); + const actual = await loginUri.validateChecksum("uri", undefined, undefined, undefined); expect(actual).toBe(true); expect(encryptService.hash).toHaveBeenCalledWith("uri", "sha256"); @@ -88,7 +88,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, undefined, 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 cac487747f8..5e917bb7db8 100644 --- a/libs/common/src/vault/models/domain/login-uri.ts +++ b/libs/common/src/vault/models/domain/login-uri.ts @@ -1,6 +1,11 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { firstValueFrom, map } from "rxjs"; import { Jsonify } from "type-fest"; +import { OrganizationId } from "@bitwarden/common/types/guid"; import { LoginUri as SdkLoginUri } from "@bitwarden/sdk-internal"; +import { UserId } from "@bitwarden/user-core"; import { EncString } from "../../../key-management/crypto/models/enc-string"; import { UriMatchStrategySetting } from "../../../models/domain/domain-service"; @@ -28,6 +33,7 @@ export class LoginUri extends Domain { } decrypt( + userId: UserId, orgId: string | undefined, context: string = "No Cipher Context", encKey?: SymmetricCryptoKey, @@ -36,21 +42,41 @@ export class LoginUri extends Domain { this, new LoginUriView(this), ["uri"], + userId, orgId ?? null, encKey, context, ); } - async validateChecksum(clearTextUri: string, orgId?: string, encKey?: SymmetricCryptoKey) { + async validateChecksum( + clearTextUri: string, + userId: UserId, + orgId?: string, + key?: SymmetricCryptoKey, + ) { if (this.uriChecksum == null) { return false; } - const keyService = Utils.getContainerService().getEncryptService(); - const localChecksum = await keyService.hash(clearTextUri, "sha256"); + const keyService = Utils.getContainerService().getKeyService(); + const encService = Utils.getContainerService().getEncryptService(); + const localChecksum = await encService.hash(clearTextUri, "sha256"); - const remoteChecksum = await this.uriChecksum.decrypt(orgId ?? null, encKey); + if (key == null) { + if (orgId != null) { + key = await firstValueFrom( + keyService + .orgKeys$(userId) + .pipe(map((orgKeys) => orgKeys[orgId as OrganizationId] ?? null)), + ); + } else { + key = await firstValueFrom(keyService.userKey$(userId)); + } + } + const remoteChecksum = await encService.decryptString(this.uriChecksum, key); + + /// WARNING: This is not constant time. This should be moved to the SDK 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 9f03e225b7f..1e93d0f39e2 100644 --- a/libs/common/src/vault/models/domain/login.spec.ts +++ b/libs/common/src/vault/models/domain/login.spec.ts @@ -99,7 +99,7 @@ describe("Login DTO", () => { loginUri.validateChecksum.mockResolvedValue(true); login.uris = [loginUri]; - const loginView = await login.decrypt(null, true); + const loginView = await login.decrypt(null, null, true); expect(loginView).toEqual(expectedView); }); @@ -111,7 +111,7 @@ describe("Login DTO", () => { .mockResolvedValueOnce(true); login.uris = [loginUri, loginUri, loginUri]; - const loginView = await login.decrypt(null, false); + const loginView = await login.decrypt(null, null, false); 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 13342c69014..b3325934e7d 100644 --- a/libs/common/src/vault/models/domain/login.ts +++ b/libs/common/src/vault/models/domain/login.ts @@ -1,6 +1,7 @@ import { Jsonify } from "type-fest"; import { Login as SdkLogin } from "@bitwarden/sdk-internal"; +import { UserId } from "@bitwarden/user-core"; import { EncString } from "../../../key-management/crypto/models/enc-string"; import Domain from "../../../platform/models/domain/domain-base"; @@ -44,6 +45,7 @@ export class Login extends Domain { } async decrypt( + userId: UserId, orgId: string | undefined, bypassValidation: boolean, context: string = "No Cipher Context", @@ -53,6 +55,7 @@ export class Login extends Domain { this, new LoginView(this), ["username", "password", "totp"], + userId, orgId ?? null, encKey, `DomainType: Login; ${context}`, @@ -66,7 +69,7 @@ export class Login extends Domain { continue; } - const uri = await this.uris[i].decrypt(orgId, context, encKey); + const uri = await this.uris[i].decrypt(userId, orgId, context, encKey); const uriString = uri.uri; if (uriString == null) { @@ -79,7 +82,8 @@ 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, userId, orgId, encKey)); if (isValidUri) { view.uris.push(uri); @@ -89,7 +93,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(userId, orgId, encKey)), ); } diff --git a/libs/common/src/vault/models/domain/password.spec.ts b/libs/common/src/vault/models/domain/password.spec.ts index 2e37c5e8375..c8012c894e1 100644 --- a/libs/common/src/vault/models/domain/password.spec.ts +++ b/libs/common/src/vault/models/domain/password.spec.ts @@ -41,7 +41,7 @@ describe("Password", () => { password.password = mockEnc("password"); password.lastUsedDate = new Date("2022-01-31T12:00:00.000Z"); - const view = await password.decrypt(null); + const view = await password.decrypt(null, null); expect(view).toEqual({ password: "password", diff --git a/libs/common/src/vault/models/domain/password.ts b/libs/common/src/vault/models/domain/password.ts index 84e8919b905..02399de488f 100644 --- a/libs/common/src/vault/models/domain/password.ts +++ b/libs/common/src/vault/models/domain/password.ts @@ -1,6 +1,7 @@ import { Jsonify } from "type-fest"; import { PasswordHistory } from "@bitwarden/sdk-internal"; +import { UserId } from "@bitwarden/user-core"; import { EncString } from "../../../key-management/crypto/models/enc-string"; import Domain from "../../../platform/models/domain/domain-base"; @@ -22,11 +23,16 @@ export class Password extends Domain { this.lastUsedDate = new Date(obj.lastUsedDate); } - decrypt(orgId: string | undefined, encKey?: SymmetricCryptoKey): Promise { + decrypt( + userId: UserId, + orgId: string | undefined, + encKey?: SymmetricCryptoKey, + ): Promise { return this.decryptObj( this, new PasswordHistoryView(this), ["password"], + userId, orgId ?? null, encKey, "DomainType: PasswordHistory", 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 38228e54a4a..33d9e471521 100644 --- a/libs/common/src/vault/models/domain/ssh-key.spec.ts +++ b/libs/common/src/vault/models/domain/ssh-key.spec.ts @@ -56,7 +56,7 @@ describe("Sshkey", () => { keyFingerprint: "keyFingerprint", }; - const loginView = await sshKey.decrypt(null); + const loginView = await sshKey.decrypt(null, null); expect(loginView).toEqual(expectedView); }); diff --git a/libs/common/src/vault/models/domain/ssh-key.ts b/libs/common/src/vault/models/domain/ssh-key.ts index a7028321a44..2e86a6ec91f 100644 --- a/libs/common/src/vault/models/domain/ssh-key.ts +++ b/libs/common/src/vault/models/domain/ssh-key.ts @@ -1,6 +1,7 @@ import { Jsonify } from "type-fest"; import { SshKey as SdkSshKey } from "@bitwarden/sdk-internal"; +import { UserId } from "@bitwarden/user-core"; import { EncString } from "../../../key-management/crypto/models/enc-string"; import Domain from "../../../platform/models/domain/domain-base"; @@ -25,6 +26,7 @@ export class SshKey extends Domain { } decrypt( + userId: UserId, orgId: string | undefined, context = "No Cipher Context", encKey?: SymmetricCryptoKey, @@ -33,6 +35,7 @@ export class SshKey extends Domain { this, new SshKeyView(), ["privateKey", "publicKey", "keyFingerprint"], + userId, orgId ?? null, encKey, "DomainType: SshKey; " + context, diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index efe7bc2b89b..86e285c9120 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -12,6 +12,7 @@ import { } from "rxjs"; import { SemVer } from "semver"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessageSender } from "@bitwarden/common/platform/messaging"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. @@ -520,7 +521,7 @@ export class CipherService implements CipherServiceAbstraction { const key = keys.orgKeys[orgId as OrganizationId] ?? keys.userKey; return await Promise.all( groupedCiphers.map(async (cipher) => { - return await cipher.decrypt(key); + return await cipher.decrypt(key, userId); }), ); }), @@ -558,7 +559,7 @@ export class CipherService implements CipherServiceAbstraction { return await this.cipherEncryptionService.decrypt(cipher, userId); } else { const encKey = await this.getKeyForCipherKeyDecryption(cipher, userId); - return await cipher.decrypt(encKey); + return await cipher.decrypt(encKey, userId); } } @@ -718,6 +719,8 @@ export class CipherService implements CipherServiceAbstraction { response: ListResponse, organizationId: string, ): Promise { + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + if (response?.data == null || response.data.length < 1) { return []; } @@ -726,7 +729,7 @@ export class CipherService implements CipherServiceAbstraction { const key = await this.keyService.getOrgKey(organizationId); const decCiphers: CipherView[] = await Promise.all( ciphers.map(async (cipher) => { - return await cipher.decrypt(key); + return await cipher.decrypt(key, userId); }), ); diff --git a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts index 76965a364eb..96b452a3122 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts @@ -6,6 +6,7 @@ import { concatMap, firstValueFrom, map } 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 { @@ -15,7 +16,7 @@ import { } from "@bitwarden/common/models/export"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -import { OrganizationId } from "@bitwarden/common/types/guid"; +import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { KeyService } from "@bitwarden/key-management"; @@ -167,6 +168,8 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { private async parseFolders( data: BitwardenUnEncryptedIndividualJsonExport | BitwardenEncryptedIndividualJsonExport, ): Promise> | null { + const userId: UserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + if (data.folders == null) { return null; } @@ -178,7 +181,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(userId); } } else { folderView = FolderWithIdExport.toView(f); diff --git a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts index 335f45b0ce2..f5f77666a39 100644 --- a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts +++ b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts @@ -175,7 +175,7 @@ export class DefaultUserAsymmetricKeysRegenerationService } try { - const cipherView = await cipher.decrypt(userKey); + const cipherView = await cipher.decrypt(userKey, userId); if (cipherView.decryptionFailure) { this.logService.error(