1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-06 10:33:57 +00:00

[EC-499] Add encryptService to domain model decryption (#3385)

Co-authored-by: Matt Gibson <mgibson@bitwarden.com>
This commit is contained in:
Thomas Rittson
2022-09-02 11:15:19 +10:00
committed by GitHub
parent 063acfef40
commit cff2422d7f
14 changed files with 231 additions and 106 deletions

View File

@@ -1,8 +1,10 @@
import Substitute, { Arg } from "@fluffy-spoon/substitute";
import { mock, MockProxy } from "jest-mock-extended";
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { AttachmentData } from "@bitwarden/common/models/data/attachmentData";
import { Attachment } from "@bitwarden/common/models/domain/attachment";
import { EncString } from "@bitwarden/common/models/domain/encString";
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
import { ContainerService } from "@bitwarden/common/services/container.service";
@@ -54,30 +56,79 @@ describe("Attachment", () => {
expect(attachment.toAttachmentData()).toEqual(data);
});
it("Decrypt", async () => {
const attachment = new Attachment();
attachment.id = "id";
attachment.url = "url";
attachment.size = "1100";
attachment.sizeName = "1.1 KB";
attachment.key = mockEnc("key");
attachment.fileName = mockEnc("fileName");
describe("decrypt", () => {
let cryptoService: MockProxy<CryptoService>;
let encryptService: MockProxy<AbstractEncryptService>;
const cryptoService = Substitute.for<CryptoService>();
cryptoService.getOrgKey(null).resolves(null);
cryptoService.decryptToBytes(Arg.any(), Arg.any()).resolves(makeStaticByteArray(32));
beforeEach(() => {
cryptoService = mock<CryptoService>();
encryptService = mock<AbstractEncryptService>();
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
(window as any).bitwardenContainerService = new ContainerService(
cryptoService,
encryptService
);
});
const view = await attachment.decrypt(null);
it("expected output", async () => {
const attachment = new Attachment();
attachment.id = "id";
attachment.url = "url";
attachment.size = "1100";
attachment.sizeName = "1.1 KB";
attachment.key = mockEnc("key");
attachment.fileName = mockEnc("fileName");
expect(view).toEqual({
id: "id",
url: "url",
size: "1100",
sizeName: "1.1 KB",
fileName: "fileName",
key: expect.any(SymmetricCryptoKey),
encryptService.decryptToBytes.mockResolvedValue(makeStaticByteArray(32));
const view = await attachment.decrypt(null);
expect(view).toEqual({
id: "id",
url: "url",
size: "1100",
sizeName: "1.1 KB",
fileName: "fileName",
key: expect.any(SymmetricCryptoKey),
});
});
describe("decrypts attachment.key", () => {
let attachment: Attachment;
beforeEach(() => {
attachment = new Attachment();
attachment.key = mock<EncString>();
});
it("uses the provided key without depending on CryptoService", async () => {
const providedKey = mock<SymmetricCryptoKey>();
await attachment.decrypt(null, providedKey);
expect(cryptoService.getKeyForUserEncryption).not.toHaveBeenCalled();
expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, providedKey);
});
it("gets an organization key if required", async () => {
const orgKey = mock<SymmetricCryptoKey>();
cryptoService.getOrgKey.calledWith("orgId").mockResolvedValue(orgKey);
await attachment.decrypt("orgId", null);
expect(cryptoService.getOrgKey).toHaveBeenCalledWith("orgId");
expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, orgKey);
});
it("gets the user's decryption key if required", async () => {
const userKey = mock<SymmetricCryptoKey>();
cryptoService.getKeyForUserEncryption.mockResolvedValue(userKey);
await attachment.decrypt(null, null);
expect(cryptoService.getKeyForUserEncryption).toHaveBeenCalled();
expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, userKey);
});
});
});
});

View File

@@ -1,5 +1,7 @@
import Substitute, { Arg } from "@fluffy-spoon/substitute";
import { mock, MockProxy } from "jest-mock-extended";
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { EncryptionType } from "@bitwarden/common/enums/encryptionType";
import { EncString } from "@bitwarden/common/models/domain/encString";
@@ -48,10 +50,15 @@ describe("EncString", () => {
const cryptoService = Substitute.for<CryptoService>();
cryptoService.getOrgKey(null).resolves(null);
cryptoService.decryptToUtf8(encString, Arg.any()).resolves("decrypted");
const encryptService = Substitute.for<AbstractEncryptService>();
encryptService.decryptToUtf8(encString, Arg.any()).resolves("decrypted");
beforeEach(() => {
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
(window as any).bitwardenContainerService = new ContainerService(
cryptoService,
encryptService
);
});
it("decrypts correctly", async () => {
@@ -62,7 +69,7 @@ describe("EncString", () => {
it("result should be cached", async () => {
const decrypted = await encString.decrypt(null);
cryptoService.received(1).decryptToUtf8(Arg.any(), Arg.any());
encryptService.received(1).decryptToUtf8(Arg.any(), Arg.any());
expect(decrypted).toBe("decrypted");
});
@@ -148,25 +155,28 @@ describe("EncString", () => {
});
describe("decrypt", () => {
it("throws exception when bitwarden container not initialized", async () => {
const encString = new EncString(null);
let cryptoService: MockProxy<CryptoService>;
let encryptService: MockProxy<AbstractEncryptService>;
let encString: EncString;
expect.assertions(1);
try {
await encString.decrypt(null);
} catch (e) {
expect(e.message).toEqual("global bitwardenContainerService not initialized.");
}
beforeEach(() => {
cryptoService = mock<CryptoService>();
encryptService = mock<AbstractEncryptService>();
encString = new EncString(null);
(window as any).bitwardenContainerService = new ContainerService(
cryptoService,
encryptService
);
});
it("handles value it can't decrypt", async () => {
const encString = new EncString(null);
encryptService.decryptToUtf8.mockRejectedValue("error");
const cryptoService = Substitute.for<CryptoService>();
cryptoService.getOrgKey(null).resolves(null);
cryptoService.decryptToUtf8(encString, Arg.any()).throws("error");
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
(window as any).bitwardenContainerService = new ContainerService(
cryptoService,
encryptService
);
const decrypted = await encString.decrypt(null);
@@ -178,18 +188,35 @@ describe("EncString", () => {
});
});
it("passes along key", async () => {
const encString = new EncString(null);
const key = Substitute.for<SymmetricCryptoKey>();
const cryptoService = Substitute.for<CryptoService>();
cryptoService.getOrgKey(null).resolves(null);
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
it("uses provided key without depending on CryptoService", async () => {
const key = mock<SymmetricCryptoKey>();
await encString.decrypt(null, key);
cryptoService.received().decryptToUtf8(encString, key);
expect(cryptoService.getKeyForUserEncryption).not.toHaveBeenCalled();
expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encString, key);
});
it("gets an organization key if required", async () => {
const orgKey = mock<SymmetricCryptoKey>();
cryptoService.getOrgKey.calledWith("orgId").mockResolvedValue(orgKey);
await encString.decrypt("orgId", null);
expect(cryptoService.getOrgKey).toHaveBeenCalledWith("orgId");
expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encString, orgKey);
});
it("gets the user's decryption key if required", async () => {
const userKey = mock<SymmetricCryptoKey>();
cryptoService.getKeyForUserEncryption.mockResolvedValue(userKey);
await encString.decrypt(null, null);
expect(cryptoService.getKeyForUserEncryption).toHaveBeenCalledWith();
expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encString, userKey);
});
});

View File

@@ -1,5 +1,6 @@
import Substitute, { Arg, SubstituteOf } from "@fluffy-spoon/substitute";
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { SendType } from "@bitwarden/common/enums/sendType";
import { SendData } from "@bitwarden/common/models/data/sendData";
@@ -110,7 +111,9 @@ describe("Send", () => {
cryptoService.decryptToBytes(send.key, null).resolves(makeStaticByteArray(32));
cryptoService.makeSendKey(Arg.any()).resolves("cryptoKey" as any);
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
const encryptService = Substitute.for<AbstractEncryptService>();
(window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService);
const view = await send.decrypt();

View File

@@ -1,6 +1,7 @@
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { BehaviorSubject, firstValueFrom } from "rxjs";
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
@@ -15,6 +16,7 @@ describe("Folder Service", () => {
let folderService: FolderService;
let cryptoService: SubstituteOf<CryptoService>;
let encryptService: SubstituteOf<AbstractEncryptService>;
let i18nService: SubstituteOf<I18nService>;
let cipherService: SubstituteOf<CipherService>;
let stateService: SubstituteOf<StateService>;
@@ -23,6 +25,7 @@ describe("Folder Service", () => {
beforeEach(() => {
cryptoService = Substitute.for();
encryptService = Substitute.for();
i18nService = Substitute.for();
cipherService = Substitute.for();
stateService = Substitute.for();
@@ -34,7 +37,7 @@ describe("Folder Service", () => {
});
stateService.activeAccount$.returns(activeAccount);
stateService.activeAccountUnlocked$.returns(activeAccountUnlocked);
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
(window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService);
folderService = new FolderService(cryptoService, i18nService, cipherService, stateService);
});

View File

@@ -1,6 +1,7 @@
import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute";
import { BehaviorSubject, firstValueFrom } from "rxjs";
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { ContainerService } from "@bitwarden/common/services/container.service";
import { SettingsService } from "@bitwarden/common/services/settings.service";
@@ -10,12 +11,14 @@ describe("SettingsService", () => {
let settingsService: SettingsService;
let cryptoService: SubstituteOf<CryptoService>;
let encryptService: SubstituteOf<AbstractEncryptService>;
let stateService: SubstituteOf<StateService>;
let activeAccount: BehaviorSubject<string>;
let activeAccountUnlocked: BehaviorSubject<boolean>;
beforeEach(() => {
cryptoService = Substitute.for();
encryptService = Substitute.for();
stateService = Substitute.for();
activeAccount = new BehaviorSubject("123");
activeAccountUnlocked = new BehaviorSubject(true);
@@ -23,7 +26,7 @@ describe("SettingsService", () => {
stateService.getSettings().resolves({ equivalentDomains: [["test"], ["domains"]] });
stateService.activeAccount$.returns(activeAccount);
stateService.activeAccountUnlocked$.returns(activeAccountUnlocked);
(window as any).bitwardenContainerService = new ContainerService(cryptoService);
(window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService);
settingsService = new SettingsService(stateService);
});