mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 22:03:36 +00:00
Remove decrypt with key from EncString, domain-base (#15702)
This commit is contained in:
@@ -4,7 +4,7 @@ import { mock, MockProxy } from "jest-mock-extended";
|
|||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { KeyService } from "@bitwarden/key-management";
|
import { KeyService } from "@bitwarden/key-management";
|
||||||
|
|
||||||
import { makeEncString, makeStaticByteArray } from "../../../../spec";
|
import { makeStaticByteArray } from "../../../../spec";
|
||||||
import { EncryptionType } from "../../../platform/enums";
|
import { EncryptionType } from "../../../platform/enums";
|
||||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||||
import { ContainerService } from "../../../platform/services/container.service";
|
import { ContainerService } from "../../../platform/services/container.service";
|
||||||
@@ -114,67 +114,6 @@ describe("EncString", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("decryptWithKey", () => {
|
|
||||||
const encString = new EncString(EncryptionType.Rsa2048_OaepSha256_B64, "data");
|
|
||||||
|
|
||||||
const keyService = mock<KeyService>();
|
|
||||||
const encryptService = mock<EncryptService>();
|
|
||||||
encryptService.decryptString
|
|
||||||
.calledWith(encString, expect.anything())
|
|
||||||
.mockResolvedValue("decrypted");
|
|
||||||
|
|
||||||
function setupEncryption() {
|
|
||||||
encryptService.encryptString.mockImplementation(async (data, key) => {
|
|
||||||
return makeEncString(data);
|
|
||||||
});
|
|
||||||
encryptService.decryptString.mockImplementation(async (encString, key) => {
|
|
||||||
return encString.data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
(window as any).bitwardenContainerService = new ContainerService(keyService, encryptService);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("decrypts using the provided key and encryptService", async () => {
|
|
||||||
setupEncryption();
|
|
||||||
|
|
||||||
const key = new SymmetricCryptoKey(makeStaticByteArray(32));
|
|
||||||
await encString.decryptWithKey(key, encryptService);
|
|
||||||
|
|
||||||
expect(encryptService.decryptString).toHaveBeenCalledWith(encString, key);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("fails to decrypt when key is null", async () => {
|
|
||||||
const decrypted = await encString.decryptWithKey(null, encryptService);
|
|
||||||
|
|
||||||
expect(decrypted).toBe("[error: cannot decrypt]");
|
|
||||||
expect(encString.decryptedValue).toBe("[error: cannot decrypt]");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("fails to decrypt when encryptService is null", async () => {
|
|
||||||
const decrypted = await encString.decryptWithKey(
|
|
||||||
new SymmetricCryptoKey(makeStaticByteArray(32)),
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(decrypted).toBe("[error: cannot decrypt]");
|
|
||||||
expect(encString.decryptedValue).toBe("[error: cannot decrypt]");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("fails to decrypt when encryptService throws", async () => {
|
|
||||||
encryptService.decryptString.mockRejectedValue("error");
|
|
||||||
|
|
||||||
const decrypted = await encString.decryptWithKey(
|
|
||||||
new SymmetricCryptoKey(makeStaticByteArray(32)),
|
|
||||||
encryptService,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(decrypted).toBe("[error: cannot decrypt]");
|
|
||||||
expect(encString.decryptedValue).toBe("[error: cannot decrypt]");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("AesCbc256_B64", () => {
|
describe("AesCbc256_B64", () => {
|
||||||
it("constructor", () => {
|
it("constructor", () => {
|
||||||
const encString = new EncString(EncryptionType.AesCbc256_B64, "data", "iv");
|
const encString = new EncString(EncryptionType.AesCbc256_B64, "data", "iv");
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { EncryptionType, EXPECTED_NUM_PARTS_BY_ENCRYPTION_TYPE } from "../../../
|
|||||||
import { Encrypted } from "../../../platform/interfaces/encrypted";
|
import { Encrypted } from "../../../platform/interfaces/encrypted";
|
||||||
import { Utils } from "../../../platform/misc/utils";
|
import { Utils } from "../../../platform/misc/utils";
|
||||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||||
import { EncryptService } from "../abstractions/encrypt.service";
|
|
||||||
|
|
||||||
export const DECRYPT_ERROR = "[error: cannot decrypt]";
|
export const DECRYPT_ERROR = "[error: cannot decrypt]";
|
||||||
|
|
||||||
@@ -184,25 +183,6 @@ export class EncString implements Encrypted {
|
|||||||
return this.decryptedValue;
|
return this.decryptedValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
async decryptWithKey(
|
|
||||||
key: SymmetricCryptoKey,
|
|
||||||
encryptService: EncryptService,
|
|
||||||
decryptTrace: string = "domain-withkey",
|
|
||||||
): Promise<string> {
|
|
||||||
try {
|
|
||||||
if (key == null) {
|
|
||||||
throw new Error("No key to decrypt EncString");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.decryptedValue = await encryptService.decryptString(this, key);
|
|
||||||
// FIXME: Remove when updating file. Eslint update
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
} catch (e) {
|
|
||||||
this.decryptedValue = DECRYPT_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.decryptedValue;
|
|
||||||
}
|
|
||||||
private async getKeyForDecryption(orgId: string) {
|
private async getKeyForDecryption(orgId: string) {
|
||||||
const keyService = Utils.getContainerService().getKeyService();
|
const keyService = Utils.getContainerService().getKeyService();
|
||||||
return orgId != null
|
return orgId != null
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
import { mock, MockProxy } from "jest-mock-extended";
|
|
||||||
|
|
||||||
import { makeEncString, makeSymmetricCryptoKey } from "../../../../spec";
|
|
||||||
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
|
|
||||||
import { EncString } from "../../../key-management/crypto/models/enc-string";
|
|
||||||
|
|
||||||
import Domain from "./domain-base";
|
|
||||||
|
|
||||||
class TestDomain extends Domain {
|
|
||||||
plainText: string;
|
|
||||||
encToString: EncString;
|
|
||||||
encString2: EncString;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("DomainBase", () => {
|
|
||||||
let encryptService: MockProxy<EncryptService>;
|
|
||||||
const key = makeSymmetricCryptoKey(64);
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
encryptService = mock<EncryptService>();
|
|
||||||
});
|
|
||||||
|
|
||||||
function setUpCryptography() {
|
|
||||||
encryptService.encryptString.mockImplementation((value) =>
|
|
||||||
Promise.resolve(makeEncString(value)),
|
|
||||||
);
|
|
||||||
|
|
||||||
encryptService.decryptString.mockImplementation((value) => {
|
|
||||||
return Promise.resolve(value.data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("decryptWithKey", () => {
|
|
||||||
it("domain property types are decryptable", async () => {
|
|
||||||
const domain = new TestDomain();
|
|
||||||
|
|
||||||
await domain["decryptObjWithKey"](
|
|
||||||
// @ts-expect-error -- clear is not of type EncString
|
|
||||||
["plainText"],
|
|
||||||
makeSymmetricCryptoKey(64),
|
|
||||||
mock<EncryptService>(),
|
|
||||||
);
|
|
||||||
|
|
||||||
await domain["decryptObjWithKey"](
|
|
||||||
// @ts-expect-error -- Clear is not of type EncString
|
|
||||||
["encToString", "encString2", "plainText"],
|
|
||||||
makeSymmetricCryptoKey(64),
|
|
||||||
mock<EncryptService>(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const decrypted = await domain["decryptObjWithKey"](
|
|
||||||
["encToString"],
|
|
||||||
makeSymmetricCryptoKey(64),
|
|
||||||
mock<EncryptService>(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// @ts-expect-error -- encString2 was not decrypted
|
|
||||||
// FIXME: Remove when updating file. Eslint update
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
||||||
decrypted as { encToString: string; encString2: string; plainText: string };
|
|
||||||
|
|
||||||
// encString2 was not decrypted, so it's still an EncString
|
|
||||||
// FIXME: Remove when updating file. Eslint update
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
||||||
decrypted as { encToString: string; encString2: EncString; plainText: string };
|
|
||||||
});
|
|
||||||
|
|
||||||
it("decrypts the encrypted properties", async () => {
|
|
||||||
setUpCryptography();
|
|
||||||
|
|
||||||
const domain = new TestDomain();
|
|
||||||
|
|
||||||
domain.encToString = await encryptService.encryptString("string", key);
|
|
||||||
|
|
||||||
const decrypted = await domain["decryptObjWithKey"](["encToString"], key, encryptService);
|
|
||||||
|
|
||||||
expect(decrypted).toEqual({
|
|
||||||
encToString: "string",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("decrypts multiple encrypted properties", async () => {
|
|
||||||
setUpCryptography();
|
|
||||||
|
|
||||||
const domain = new TestDomain();
|
|
||||||
|
|
||||||
domain.encToString = await encryptService.encryptString("string", key);
|
|
||||||
domain.encString2 = await encryptService.encryptString("string2", key);
|
|
||||||
|
|
||||||
const decrypted = await domain["decryptObjWithKey"](
|
|
||||||
["encToString", "encString2"],
|
|
||||||
key,
|
|
||||||
encryptService,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(decrypted).toEqual({
|
|
||||||
encToString: "string",
|
|
||||||
encString2: "string2",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not decrypt properties that are not encrypted", async () => {
|
|
||||||
const domain = new TestDomain();
|
|
||||||
domain.plainText = "clear";
|
|
||||||
|
|
||||||
const decrypted = await domain["decryptObjWithKey"]([], key, encryptService);
|
|
||||||
|
|
||||||
expect(decrypted).toEqual({
|
|
||||||
plainText: "clear",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not decrypt properties that were not requested to be decrypted", async () => {
|
|
||||||
setUpCryptography();
|
|
||||||
|
|
||||||
const domain = new TestDomain();
|
|
||||||
|
|
||||||
domain.plainText = "clear";
|
|
||||||
domain.encToString = makeEncString("string");
|
|
||||||
domain.encString2 = makeEncString("string2");
|
|
||||||
|
|
||||||
const decrypted = await domain["decryptObjWithKey"]([], key, encryptService);
|
|
||||||
|
|
||||||
expect(decrypted).toEqual({
|
|
||||||
plainText: "clear",
|
|
||||||
encToString: makeEncString("string"),
|
|
||||||
encString2: makeEncString("string2"),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { ConditionalExcept, ConditionalKeys, Constructor } from "type-fest";
|
import { ConditionalExcept, ConditionalKeys } from "type-fest";
|
||||||
|
|
||||||
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
|
|
||||||
import { EncString } from "../../../key-management/crypto/models/enc-string";
|
import { EncString } from "../../../key-management/crypto/models/enc-string";
|
||||||
import { View } from "../../../models/view/view";
|
import { View } from "../../../models/view/view";
|
||||||
|
|
||||||
@@ -89,66 +88,4 @@ export default class Domain {
|
|||||||
|
|
||||||
return viewModel as V;
|
return viewModel as V;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypts the requested properties of the domain object with the provided key and encrypt service.
|
|
||||||
*
|
|
||||||
* If a property is null, the result will be null.
|
|
||||||
* @see {@link EncString.decryptWithKey} for more details on decryption behavior.
|
|
||||||
*
|
|
||||||
* @param encryptedProperties The properties to decrypt. Type restricted to EncString properties of the domain object.
|
|
||||||
* @param key The key to use for decryption.
|
|
||||||
* @param encryptService The encryption service to use for decryption.
|
|
||||||
* @param _ The constructor of the domain object. Used for type inference if the domain object is not automatically inferred.
|
|
||||||
* @returns An object with the requested properties decrypted and the rest of the domain object untouched.
|
|
||||||
*/
|
|
||||||
protected async decryptObjWithKey<
|
|
||||||
TThis extends Domain,
|
|
||||||
const TEncryptedKeys extends EncStringKeys<TThis>,
|
|
||||||
>(
|
|
||||||
this: TThis,
|
|
||||||
encryptedProperties: TEncryptedKeys[],
|
|
||||||
key: SymmetricCryptoKey,
|
|
||||||
encryptService: EncryptService,
|
|
||||||
_: Constructor<TThis> = this.constructor as Constructor<TThis>,
|
|
||||||
objectContext: string = "No Domain Context",
|
|
||||||
): Promise<DecryptedObject<TThis, TEncryptedKeys>> {
|
|
||||||
const decryptedObjects = [];
|
|
||||||
|
|
||||||
for (const prop of encryptedProperties) {
|
|
||||||
const value = this[prop] as EncString;
|
|
||||||
const decrypted = await this.decryptProperty(
|
|
||||||
prop,
|
|
||||||
value,
|
|
||||||
key,
|
|
||||||
encryptService,
|
|
||||||
`Property: ${prop.toString()}; ObjectContext: ${objectContext}`,
|
|
||||||
);
|
|
||||||
decryptedObjects.push(decrypted);
|
|
||||||
}
|
|
||||||
|
|
||||||
const decryptedObject = decryptedObjects.reduce(
|
|
||||||
(acc, obj) => {
|
|
||||||
return { ...acc, ...obj };
|
|
||||||
},
|
|
||||||
{ ...this },
|
|
||||||
);
|
|
||||||
return decryptedObject as DecryptedObject<TThis, TEncryptedKeys>;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async decryptProperty<const TEncryptedKeys extends EncStringKeys<this>>(
|
|
||||||
propertyKey: TEncryptedKeys,
|
|
||||||
value: EncString,
|
|
||||||
key: SymmetricCryptoKey,
|
|
||||||
encryptService: EncryptService,
|
|
||||||
decryptTrace: string,
|
|
||||||
) {
|
|
||||||
let decrypted: string | null = null;
|
|
||||||
if (value) {
|
|
||||||
decrypted = await value.decryptWithKey(key, encryptService, decryptTrace);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
[propertyKey]: decrypted,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { mock, MockProxy } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
|
||||||
|
|
||||||
import { makeEncString, makeSymmetricCryptoKey, mockEnc, mockFromJson } from "../../../../spec";
|
import { makeEncString, makeSymmetricCryptoKey, mockEnc, mockFromJson } from "../../../../spec";
|
||||||
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
|
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
|
||||||
import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string";
|
import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string";
|
||||||
@@ -72,15 +70,7 @@ describe("Folder", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
encryptService = mock<EncryptService>();
|
encryptService = mock<EncryptService>();
|
||||||
// Platform code is not migrated yet
|
encryptService.decryptString.mockResolvedValue("encName");
|
||||||
encryptService.decryptToUtf8.mockImplementation(
|
|
||||||
(value: EncString, key: SymmetricCryptoKey, decryptTrace: string) => {
|
|
||||||
return Promise.resolve(value.data);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
encryptService.decryptString.mockImplementation((value) => {
|
|
||||||
return Promise.resolve(value.data);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("decrypts the name", async () => {
|
it("decrypts the name", async () => {
|
||||||
|
|||||||
@@ -47,11 +47,11 @@ export class Folder extends Domain {
|
|||||||
key: SymmetricCryptoKey,
|
key: SymmetricCryptoKey,
|
||||||
encryptService: EncryptService,
|
encryptService: EncryptService,
|
||||||
): Promise<FolderView> {
|
): Promise<FolderView> {
|
||||||
const decrypted = await this.decryptObjWithKey(["name"], key, encryptService, Folder);
|
const folderView = new FolderView();
|
||||||
|
folderView.id = this.id;
|
||||||
const view = new FolderView(decrypted);
|
folderView.revisionDate = this.revisionDate;
|
||||||
view.name = decrypted.name;
|
folderView.name = await encryptService.decryptString(this.name, key);
|
||||||
return view;
|
return folderView;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJSON(obj: Jsonify<Folder>) {
|
static fromJSON(obj: Jsonify<Folder>) {
|
||||||
|
|||||||
Reference in New Issue
Block a user