1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 13:23:34 +00:00

[PM-21001] Move vault code to new encrypt service interface (#14546)

* Move vault code to new encrypt service interface

* Fix tests
This commit is contained in:
Bernd Schoolmann
2025-05-06 23:24:53 +02:00
committed by GitHub
parent 1486cee8b9
commit 744c1b1b49
14 changed files with 76 additions and 45 deletions

View File

@@ -227,7 +227,7 @@ export class CreateCommand {
(u) => new SelectionReadOnlyRequest(u.id, u.readOnly, u.hidePasswords, u.manage), (u) => new SelectionReadOnlyRequest(u.id, u.readOnly, u.hidePasswords, u.manage),
); );
const request = new CollectionRequest(); const request = new CollectionRequest();
request.name = (await this.encryptService.encrypt(req.name, orgKey)).encryptedString; request.name = (await this.encryptService.encryptString(req.name, orgKey)).encryptedString;
request.externalId = req.externalId; request.externalId = req.externalId;
request.groups = groups; request.groups = groups;
request.users = users; request.users = users;

View File

@@ -202,7 +202,7 @@ export class AttachmentsComponent implements OnInit {
attachment.key != null attachment.key != null
? attachment.key ? attachment.key
: await this.keyService.getOrgKey(this.cipher.organizationId); : await this.keyService.getOrgKey(this.cipher.organizationId);
const decBuf = await this.encryptService.decryptToBytes(encBuf, key); const decBuf = await this.encryptService.decryptFileData(encBuf, key);
this.fileDownloadService.download({ this.fileDownloadService.download({
fileName: attachment.fileName, fileName: attachment.fileName,
blobData: decBuf, blobData: decBuf,
@@ -281,7 +281,7 @@ export class AttachmentsComponent implements OnInit {
attachment.key != null attachment.key != null
? attachment.key ? attachment.key
: await this.keyService.getOrgKey(this.cipher.organizationId); : await this.keyService.getOrgKey(this.cipher.organizationId);
const decBuf = await this.encryptService.decryptToBytes(encBuf, key); const decBuf = await this.encryptService.decryptFileData(encBuf, key);
const activeUserId = await firstValueFrom( const activeUserId = await firstValueFrom(
this.accountService.activeAccount$.pipe(getUserId), this.accountService.activeAccount$.pipe(getUserId),
); );

View File

@@ -463,7 +463,7 @@ export class ViewComponent implements OnDestroy, OnInit {
attachment.key != null attachment.key != null
? attachment.key ? attachment.key
: await this.keyService.getOrgKey(this.cipher.organizationId); : await this.keyService.getOrgKey(this.cipher.organizationId);
const decBuf = await this.encryptService.decryptToBytes(encBuf, key); const decBuf = await this.encryptService.decryptFileData(encBuf, key);
this.fileDownloadService.download({ this.fileDownloadService.download({
fileName: attachment.fileName, fileName: attachment.fileName,
blobData: decBuf, blobData: decBuf,

View File

@@ -77,7 +77,10 @@ describe("Attachment", () => {
attachment.key = mockEnc("key"); attachment.key = mockEnc("key");
attachment.fileName = mockEnc("fileName"); attachment.fileName = mockEnc("fileName");
encryptService.decryptToBytes.mockResolvedValue(makeStaticByteArray(32)); encryptService.decryptFileData.mockResolvedValue(makeStaticByteArray(32));
encryptService.unwrapSymmetricKey.mockResolvedValue(
new SymmetricCryptoKey(makeStaticByteArray(64)),
);
const view = await attachment.decrypt(null); const view = await attachment.decrypt(null);
@@ -105,7 +108,7 @@ describe("Attachment", () => {
await attachment.decrypt(null, "", providedKey); await attachment.decrypt(null, "", providedKey);
expect(keyService.getUserKeyWithLegacySupport).not.toHaveBeenCalled(); expect(keyService.getUserKeyWithLegacySupport).not.toHaveBeenCalled();
expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, providedKey); expect(encryptService.unwrapSymmetricKey).toHaveBeenCalledWith(attachment.key, providedKey);
}); });
it("gets an organization key if required", async () => { it("gets an organization key if required", async () => {
@@ -115,7 +118,7 @@ describe("Attachment", () => {
await attachment.decrypt("orgId", "", null); await attachment.decrypt("orgId", "", null);
expect(keyService.getOrgKey).toHaveBeenCalledWith("orgId"); expect(keyService.getOrgKey).toHaveBeenCalledWith("orgId");
expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, orgKey); expect(encryptService.unwrapSymmetricKey).toHaveBeenCalledWith(attachment.key, orgKey);
}); });
it("gets the user's decryption key if required", async () => { it("gets the user's decryption key if required", async () => {
@@ -125,7 +128,7 @@ describe("Attachment", () => {
await attachment.decrypt(null, "", null); await attachment.decrypt(null, "", null);
expect(keyService.getUserKeyWithLegacySupport).toHaveBeenCalled(); expect(keyService.getUserKeyWithLegacySupport).toHaveBeenCalled();
expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, userKey); expect(encryptService.unwrapSymmetricKey).toHaveBeenCalledWith(attachment.key, userKey);
}); });
}); });
}); });

View File

@@ -66,8 +66,8 @@ export class Attachment extends Domain {
} }
const encryptService = Utils.getContainerService().getEncryptService(); const encryptService = Utils.getContainerService().getEncryptService();
const decValue = await encryptService.decryptToBytes(this.key, encKey); const decValue = await encryptService.unwrapSymmetricKey(this.key, encKey);
return new SymmetricCryptoKey(decValue); return decValue;
// FIXME: Remove when updating file. Eslint update // FIXME: Remove when updating file. Eslint update
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) { } catch (e) {

View File

@@ -1,6 +1,7 @@
import { mock } from "jest-mock-extended"; import { mock } from "jest-mock-extended";
import { Jsonify } from "type-fest"; import { Jsonify } from "type-fest";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { KeyService } from "@bitwarden/key-management"; import { KeyService } from "@bitwarden/key-management";
import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec/utils"; import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec/utils";
@@ -246,7 +247,9 @@ describe("Cipher DTO", () => {
const encryptService = mock<EncryptService>(); const encryptService = mock<EncryptService>();
const cipherService = mock<CipherService>(); const cipherService = mock<CipherService>();
encryptService.decryptToBytes.mockResolvedValue(makeStaticByteArray(64)); encryptService.unwrapSymmetricKey.mockResolvedValue(
new SymmetricCryptoKey(makeStaticByteArray(64)),
);
(window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService);
@@ -367,7 +370,9 @@ describe("Cipher DTO", () => {
const encryptService = mock<EncryptService>(); const encryptService = mock<EncryptService>();
const cipherService = mock<CipherService>(); const cipherService = mock<CipherService>();
encryptService.decryptToBytes.mockResolvedValue(makeStaticByteArray(64)); encryptService.unwrapSymmetricKey.mockResolvedValue(
new SymmetricCryptoKey(makeStaticByteArray(64)),
);
(window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService);
@@ -506,7 +511,9 @@ describe("Cipher DTO", () => {
const encryptService = mock<EncryptService>(); const encryptService = mock<EncryptService>();
const cipherService = mock<CipherService>(); const cipherService = mock<CipherService>();
encryptService.decryptToBytes.mockResolvedValue(makeStaticByteArray(64)); encryptService.unwrapSymmetricKey.mockResolvedValue(
new SymmetricCryptoKey(makeStaticByteArray(64)),
);
(window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService);
@@ -669,7 +676,9 @@ describe("Cipher DTO", () => {
const encryptService = mock<EncryptService>(); const encryptService = mock<EncryptService>();
const cipherService = mock<CipherService>(); const cipherService = mock<CipherService>();
encryptService.decryptToBytes.mockResolvedValue(makeStaticByteArray(64)); encryptService.unwrapSymmetricKey.mockResolvedValue(
new SymmetricCryptoKey(makeStaticByteArray(64)),
);
(window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService);

View File

@@ -143,17 +143,13 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
if (this.key != null) { if (this.key != null) {
const encryptService = Utils.getContainerService().getEncryptService(); const encryptService = Utils.getContainerService().getEncryptService();
const keyBytes = await encryptService.decryptToBytes( const cipherKey = await encryptService.unwrapSymmetricKey(this.key, encKey);
this.key, if (cipherKey == null) {
encKey,
`Cipher Id: ${this.id}; Content: CipherKey; IsEncryptedByOrgKey: ${this.organizationId != null}`,
);
if (keyBytes == null) {
model.name = "[error: cannot decrypt]"; model.name = "[error: cannot decrypt]";
model.decryptionFailure = true; model.decryptionFailure = true;
return model; return model;
} }
encKey = new SymmetricCryptoKey(keyBytes); encKey = cipherKey;
bypassValidation = false; bypassValidation = false;
} }

View File

@@ -1,5 +1,7 @@
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 "../../../platform/models/domain/enc-string"; import { EncryptedString, EncString } from "../../../platform/models/domain/enc-string";
@@ -70,7 +72,13 @@ describe("Folder", () => {
beforeEach(() => { beforeEach(() => {
encryptService = mock<EncryptService>(); encryptService = mock<EncryptService>();
encryptService.decryptToUtf8.mockImplementation((value) => { // Platform code is not migrated yet
encryptService.decryptToUtf8.mockImplementation(
(value: EncString, key: SymmetricCryptoKey, decryptTrace: string) => {
return Promise.resolve(value.data);
},
);
encryptService.decryptString.mockImplementation((value) => {
return Promise.resolve(value.data); return Promise.resolve(value.data);
}); });
}); });

View File

@@ -131,8 +131,8 @@ describe("Cipher Service", () => {
let cipherObj: Cipher; let cipherObj: Cipher;
beforeEach(() => { beforeEach(() => {
encryptService.encryptToBytes.mockReturnValue(Promise.resolve(ENCRYPTED_BYTES)); encryptService.encryptFileData.mockReturnValue(Promise.resolve(ENCRYPTED_BYTES));
encryptService.encrypt.mockReturnValue(Promise.resolve(new EncString(ENCRYPTED_TEXT))); encryptService.encryptString.mockReturnValue(Promise.resolve(new EncString(ENCRYPTED_TEXT)));
(window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService);
@@ -274,12 +274,14 @@ describe("Cipher Service", () => {
cipherView = new CipherView(); cipherView = new CipherView();
cipherView.type = CipherType.Login; cipherView.type = CipherType.Login;
encryptService.decryptToBytes.mockReturnValue(Promise.resolve(makeStaticByteArray(64))); encryptService.unwrapSymmetricKey.mockResolvedValue(
new SymmetricCryptoKey(makeStaticByteArray(64)),
);
configService.checkServerMeetsVersionRequirement$.mockReturnValue(of(true)); configService.checkServerMeetsVersionRequirement$.mockReturnValue(of(true));
keyService.makeCipherKey.mockReturnValue( keyService.makeCipherKey.mockReturnValue(
Promise.resolve(new SymmetricCryptoKey(makeStaticByteArray(64)) as CipherKey), Promise.resolve(new SymmetricCryptoKey(makeStaticByteArray(64)) as CipherKey),
); );
encryptService.encrypt.mockImplementation(encryptText); encryptService.encryptString.mockImplementation(encryptText);
encryptService.wrapSymmetricKey.mockResolvedValue(new EncString("Re-encrypted Cipher Key")); encryptService.wrapSymmetricKey.mockResolvedValue(new EncString("Re-encrypted Cipher Key"));
jest.spyOn(cipherService as any, "getAutofillOnPageLoadDefault").mockResolvedValue(true); jest.spyOn(cipherService as any, "getAutofillOnPageLoadDefault").mockResolvedValue(true);
@@ -435,7 +437,9 @@ describe("Cipher Service", () => {
.spyOn(cipherService, "failedToDecryptCiphers$") .spyOn(cipherService, "failedToDecryptCiphers$")
.mockImplementation((userId: UserId) => failedCiphers); .mockImplementation((userId: UserId) => failedCiphers);
encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(32)); encryptService.unwrapSymmetricKey.mockResolvedValue(
new SymmetricCryptoKey(new Uint8Array(32)),
);
encryptedKey = new EncString("Re-encrypted Cipher Key"); encryptedKey = new EncString("Re-encrypted Cipher Key");
encryptService.wrapSymmetricKey.mockResolvedValue(encryptedKey); encryptService.wrapSymmetricKey.mockResolvedValue(encryptedKey);

View File

@@ -877,9 +877,7 @@ export class CipherService implements CipherServiceAbstraction {
const cipherEncKey = const cipherEncKey =
cipherKeyEncryptionEnabled && cipher.key != null cipherKeyEncryptionEnabled && cipher.key != null
? (new SymmetricCryptoKey( ? ((await this.encryptService.unwrapSymmetricKey(cipher.key, encKey)) as UserKey)
await this.encryptService.decryptToBytes(cipher.key, encKey),
) as UserKey)
: encKey; : encKey;
//if cipher key encryption is disabled but the item has an individual key, //if cipher key encryption is disabled but the item has an individual key,
@@ -891,10 +889,10 @@ export class CipherService implements CipherServiceAbstraction {
await this.updateWithServer(cipher); await this.updateWithServer(cipher);
} }
const encFileName = await this.encryptService.encrypt(filename, cipherEncKey); const encFileName = await this.encryptService.encryptString(filename, cipherEncKey);
const dataEncKey = await this.keyService.makeDataEncKey(cipherEncKey); const dataEncKey = await this.keyService.makeDataEncKey(cipherEncKey);
const encData = await this.encryptService.encryptToBytes(new Uint8Array(data), dataEncKey[0]); const encData = await this.encryptService.encryptFileData(new Uint8Array(data), dataEncKey[0]);
const response = await this.cipherFileUploadService.upload( const response = await this.cipherFileUploadService.upload(
cipher, cipher,
@@ -1490,7 +1488,7 @@ export class CipherService implements CipherServiceAbstraction {
const encBuf = await EncArrayBuffer.fromResponse(attachmentResponse); const encBuf = await EncArrayBuffer.fromResponse(attachmentResponse);
const activeUserId = await firstValueFrom(this.accountService.activeAccount$); const activeUserId = await firstValueFrom(this.accountService.activeAccount$);
const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId.id); const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId.id);
const decBuf = await this.encryptService.decryptToBytes(encBuf, userKey); const decBuf = await this.encryptService.decryptFileData(encBuf, userKey);
let encKey: UserKey | OrgKey; let encKey: UserKey | OrgKey;
encKey = await this.keyService.getOrgKey(organizationId); encKey = await this.keyService.getOrgKey(organizationId);
@@ -1498,8 +1496,11 @@ export class CipherService implements CipherServiceAbstraction {
const dataEncKey = await this.keyService.makeDataEncKey(encKey); const dataEncKey = await this.keyService.makeDataEncKey(encKey);
const encFileName = await this.encryptService.encrypt(attachmentView.fileName, encKey); const encFileName = await this.encryptService.encryptString(attachmentView.fileName, encKey);
const encData = await this.encryptService.encryptToBytes(new Uint8Array(decBuf), dataEncKey[0]); const encData = await this.encryptService.encryptFileData(
new Uint8Array(decBuf),
dataEncKey[0],
);
const fd = new FormData(); const fd = new FormData();
try { try {
@@ -1554,7 +1555,7 @@ export class CipherService implements CipherServiceAbstraction {
.then(() => { .then(() => {
const modelProp = (model as any)[map[theProp] || theProp]; const modelProp = (model as any)[map[theProp] || theProp];
if (modelProp && modelProp !== "") { if (modelProp && modelProp !== "") {
return self.encryptService.encrypt(modelProp, key); return self.encryptService.encryptString(modelProp, key);
} }
return null; return null;
}) })
@@ -1600,7 +1601,7 @@ export class CipherService implements CipherServiceAbstraction {
key, key,
); );
const uriHash = await this.encryptService.hash(model.login.uris[i].uri, "sha256"); const uriHash = await this.encryptService.hash(model.login.uris[i].uri, "sha256");
loginUri.uriChecksum = await this.encryptService.encrypt(uriHash, key); loginUri.uriChecksum = await this.encryptService.encryptString(uriHash, key);
cipher.login.uris.push(loginUri); cipher.login.uris.push(loginUri);
} }
} }
@@ -1627,8 +1628,11 @@ export class CipherService implements CipherServiceAbstraction {
}, },
key, key,
); );
domainKey.counter = await this.encryptService.encrypt(String(viewKey.counter), key); domainKey.counter = await this.encryptService.encryptString(
domainKey.discoverable = await this.encryptService.encrypt( String(viewKey.counter),
key,
);
domainKey.discoverable = await this.encryptService.encryptString(
String(viewKey.discoverable), String(viewKey.discoverable),
key, key,
); );
@@ -1814,8 +1818,9 @@ export class CipherService implements CipherServiceAbstraction {
if (cipher.key == null) { if (cipher.key == null) {
decryptedCipherKey = await this.keyService.makeCipherKey(); decryptedCipherKey = await this.keyService.makeCipherKey();
} else { } else {
decryptedCipherKey = new SymmetricCryptoKey( decryptedCipherKey = await this.encryptService.unwrapSymmetricKey(
await this.encryptService.decryptToBytes(cipher.key, keyForCipherKeyDecryption), cipher.key,
keyForCipherKeyDecryption,
); );
} }

View File

@@ -46,6 +46,7 @@ describe("Folder Service", () => {
i18nService.t.mockReturnValue("No Folder"); i18nService.t.mockReturnValue("No Folder");
keyService.userKey$.mockReturnValue(new BehaviorSubject("mockOriginalUserKey" as any)); keyService.userKey$.mockReturnValue(new BehaviorSubject("mockOriginalUserKey" as any));
encryptService.decryptString.mockResolvedValue("DEC");
encryptService.decryptToUtf8.mockResolvedValue("DEC"); encryptService.decryptToUtf8.mockResolvedValue("DEC");
folderService = new FolderService( folderService = new FolderService(
@@ -110,7 +111,7 @@ describe("Folder Service", () => {
model.id = "2"; model.id = "2";
model.name = "Test Folder"; model.name = "Test Folder";
encryptService.encrypt.mockResolvedValue(new EncString("ENC")); encryptService.encryptString.mockResolvedValue(new EncString("ENC"));
const result = await folderService.encrypt(model, null); const result = await folderService.encrypt(model, null);
@@ -211,7 +212,7 @@ describe("Folder Service", () => {
beforeEach(() => { beforeEach(() => {
encryptedKey = new EncString("Re-encrypted Folder"); encryptedKey = new EncString("Re-encrypted Folder");
encryptService.encrypt.mockResolvedValue(encryptedKey); encryptService.encryptString.mockResolvedValue(encryptedKey);
}); });
it("returns re-encrypted user folders", async () => { it("returns re-encrypted user folders", async () => {

View File

@@ -84,7 +84,7 @@ export class FolderService implements InternalFolderServiceAbstraction {
async encrypt(model: FolderView, key: SymmetricCryptoKey): Promise<Folder> { async encrypt(model: FolderView, key: SymmetricCryptoKey): Promise<Folder> {
const folder = new Folder(); const folder = new Folder();
folder.id = model.id; folder.id = model.id;
folder.name = await this.encryptService.encrypt(model.name, key); folder.name = await this.encryptService.encryptString(model.name, key);
return folder; return folder;
} }

View File

@@ -59,6 +59,7 @@ function setupUserKeyValidation(
cipher.key = mockEnc("EncKey"); cipher.key = mockEnc("EncKey");
cipherService.getAll.mockResolvedValue([cipher]); cipherService.getAll.mockResolvedValue([cipher]);
encryptService.decryptToBytes.mockResolvedValue(makeStaticByteArray(64)); encryptService.decryptToBytes.mockResolvedValue(makeStaticByteArray(64));
encryptService.decryptString.mockResolvedValue("mockDecryptedString");
(window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService);
} }
@@ -280,6 +281,8 @@ describe("regenerateIfNeeded", () => {
setupVerificationResponse(mockVerificationResponse, sdkService); setupVerificationResponse(mockVerificationResponse, sdkService);
setupUserKeyValidation(cipherService, keyService, encryptService); setupUserKeyValidation(cipherService, keyService, encryptService);
encryptService.decryptToBytes.mockRejectedValue(new Error("error")); encryptService.decryptToBytes.mockRejectedValue(new Error("error"));
encryptService.decryptString.mockRejectedValue(new Error("error"));
encryptService.unwrapSymmetricKey.mockRejectedValue(new Error("error"));
await sut.regenerateIfNeeded(userId); await sut.regenerateIfNeeded(userId);
@@ -329,6 +332,8 @@ describe("regenerateIfNeeded", () => {
setupVerificationResponse(mockVerificationResponse, sdkService); setupVerificationResponse(mockVerificationResponse, sdkService);
setupUserKeyValidation(cipherService, keyService, encryptService); setupUserKeyValidation(cipherService, keyService, encryptService);
encryptService.decryptToBytes.mockRejectedValue(new Error("error")); encryptService.decryptToBytes.mockRejectedValue(new Error("error"));
encryptService.decryptString.mockRejectedValue(new Error("error"));
encryptService.unwrapSymmetricKey.mockRejectedValue(new Error("error"));
await sut.regenerateIfNeeded(userId); await sut.regenerateIfNeeded(userId);

View File

@@ -102,7 +102,7 @@ export class DownloadAttachmentComponent {
try { try {
const encBuf = await EncArrayBuffer.fromResponse(response); const encBuf = await EncArrayBuffer.fromResponse(response);
const key = this.attachment.key != null ? this.attachment.key : this.orgKey; const key = this.attachment.key != null ? this.attachment.key : this.orgKey;
const decBuf = await this.encryptService.decryptToBytes(encBuf, key); const decBuf = await this.encryptService.decryptFileData(encBuf, key);
this.fileDownloadService.download({ this.fileDownloadService.download({
fileName: this.attachment.fileName, fileName: this.attachment.fileName,
blobData: decBuf, blobData: decBuf,