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

[PM-21001] Move KM usage of encrypt service (#14541)

* Add new encrypt service functions

* Undo changes

* Cleanup

* Fix build

* Fix comments

* Move KM usage of encrypt service

* Fix build
This commit is contained in:
Bernd Schoolmann
2025-05-12 11:41:45 +02:00
committed by GitHub
parent 2282a74abd
commit 5408a62b7d
10 changed files with 59 additions and 56 deletions

View File

@@ -357,7 +357,7 @@ export class NativeMessagingBackground {
await this.secureCommunication(); await this.secureCommunication();
} }
return await this.encryptService.encrypt( return await this.encryptService.encryptString(
JSON.stringify(message), JSON.stringify(message),
this.secureChannel!.sharedSecret!, this.secureChannel!.sharedSecret!,
); );
@@ -401,10 +401,9 @@ export class NativeMessagingBackground {
return; return;
} }
message = JSON.parse( message = JSON.parse(
await this.encryptService.decryptToUtf8( await this.encryptService.decryptString(
rawMessage as EncString, rawMessage as EncString,
this.secureChannel.sharedSecret, this.secureChannel.sharedSecret,
"ipc-desktop-ipc-channel-key",
), ),
); );
} else { } else {

View File

@@ -347,7 +347,7 @@ describe("BiometricMessageHandlerService", () => {
trusted: false, trusted: false,
}), }),
); );
encryptService.decryptToUtf8.mockResolvedValue( encryptService.decryptString.mockResolvedValue(
JSON.stringify({ JSON.stringify({
command: "biometricUnlock", command: "biometricUnlock",
messageId: 0, messageId: 0,
@@ -382,7 +382,7 @@ describe("BiometricMessageHandlerService", () => {
ngZone.run.mockReturnValue({ ngZone.run.mockReturnValue({
closed: of(true), closed: of(true),
}); });
encryptService.decryptToUtf8.mockResolvedValue( encryptService.decryptString.mockResolvedValue(
JSON.stringify({ JSON.stringify({
command: BiometricsCommands.UnlockWithBiometricsForUser, command: BiometricsCommands.UnlockWithBiometricsForUser,
messageId: 0, messageId: 0,
@@ -433,7 +433,7 @@ describe("BiometricMessageHandlerService", () => {
ngZone.run.mockReturnValue({ ngZone.run.mockReturnValue({
closed: of(false), closed: of(false),
}); });
encryptService.decryptToUtf8.mockResolvedValue( encryptService.decryptString.mockResolvedValue(
JSON.stringify({ JSON.stringify({
command: BiometricsCommands.UnlockWithBiometricsForUser, command: BiometricsCommands.UnlockWithBiometricsForUser,
messageId: 0, messageId: 0,
@@ -480,7 +480,7 @@ describe("BiometricMessageHandlerService", () => {
trusted: true, trusted: true,
}), }),
); );
encryptService.decryptToUtf8.mockResolvedValue( encryptService.decryptString.mockResolvedValue(
JSON.stringify({ JSON.stringify({
command: BiometricsCommands.UnlockWithBiometricsForUser, command: BiometricsCommands.UnlockWithBiometricsForUser,
messageId: 0, messageId: 0,

View File

@@ -175,7 +175,7 @@ export class BiometricMessageHandlerService {
} }
const message: LegacyMessage = JSON.parse( const message: LegacyMessage = JSON.parse(
await this.encryptService.decryptToUtf8( await this.encryptService.decryptString(
rawMessage as EncString, rawMessage as EncString,
SymmetricCryptoKey.fromString(sessionSecret), SymmetricCryptoKey.fromString(sessionSecret),
), ),
@@ -365,7 +365,7 @@ export class BiometricMessageHandlerService {
throw new Error("Session secret is missing"); throw new Error("Session secret is missing");
} }
const encrypted = await this.encryptService.encrypt( const encrypted = await this.encryptService.encryptString(
JSON.stringify(message), JSON.stringify(message),
SymmetricCryptoKey.fromString(sessionSecret), SymmetricCryptoKey.fromString(sessionSecret),
); );

View File

@@ -49,6 +49,7 @@ export abstract class EncryptService {
key: SymmetricCryptoKey, key: SymmetricCryptoKey,
decryptTrace?: string, decryptTrace?: string,
): Promise<Uint8Array | null>; ): Promise<Uint8Array | null>;
/** /**
* @deprecated Replaced by BulkEncryptService, remove once the feature is tested and the featureflag PM-4154-multi-worker-encryption-service is removed * @deprecated Replaced by BulkEncryptService, remove once the feature is tested and the featureflag PM-4154-multi-worker-encryption-service is removed
* @param items The items to decrypt * @param items The items to decrypt

View File

@@ -209,7 +209,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction {
devices.data devices.data
.filter((device) => device.isTrusted) .filter((device) => device.isTrusted)
.map(async (device) => { .map(async (device) => {
const publicKey = await this.encryptService.decryptToBytes( const publicKey = await this.encryptService.unwrapEncapsulationKey(
new EncString(device.encryptedPublicKey), new EncString(device.encryptedPublicKey),
oldUserKey, oldUserKey,
); );
@@ -220,7 +220,10 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction {
return null; return null;
} }
const newEncryptedPublicKey = await this.encryptService.encrypt(publicKey, newUserKey); const newEncryptedPublicKey = await this.encryptService.wrapEncapsulationKey(
publicKey,
newUserKey,
);
const newEncryptedUserKey = await this.encryptService.encapsulateKeyUnsigned( const newEncryptedUserKey = await this.encryptService.encapsulateKeyUnsigned(
newUserKey, newUserKey,
publicKey, publicKey,
@@ -278,7 +281,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction {
const currentDeviceKeys = await this.devicesApiService.getDeviceKeys(deviceIdentifier); const currentDeviceKeys = await this.devicesApiService.getDeviceKeys(deviceIdentifier);
// Decrypt the existing device public key with the old user key // Decrypt the existing device public key with the old user key
const decryptedDevicePublicKey = await this.encryptService.decryptToBytes( const decryptedDevicePublicKey = await this.encryptService.unwrapEncapsulationKey(
currentDeviceKeys.encryptedPublicKey, currentDeviceKeys.encryptedPublicKey,
oldUserKey, oldUserKey,
); );
@@ -394,7 +397,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction {
try { try {
// attempt to decrypt encryptedDevicePrivateKey with device key // attempt to decrypt encryptedDevicePrivateKey with device key
const devicePrivateKey = await this.encryptService.decryptToBytes( const devicePrivateKey = await this.encryptService.unwrapDecapsulationKey(
encryptedDevicePrivateKey, encryptedDevicePrivateKey,
deviceKey, deviceKey,
); );

View File

@@ -623,9 +623,9 @@ describe("deviceTrustService", () => {
}); });
it("successfully returns the user key when provided keys (including device key) can decrypt it", async () => { it("successfully returns the user key when provided keys (including device key) can decrypt it", async () => {
const decryptToBytesSpy = jest const unwrapDecapsulationKeySpy = jest
.spyOn(encryptService, "decryptToBytes") .spyOn(encryptService, "unwrapDecapsulationKey")
.mockResolvedValue(new Uint8Array(userKeyBytesLength)); .mockResolvedValue(new Uint8Array(2048));
const rsaDecryptSpy = jest const rsaDecryptSpy = jest
.spyOn(encryptService, "decapsulateKeyUnsigned") .spyOn(encryptService, "decapsulateKeyUnsigned")
.mockResolvedValue(new SymmetricCryptoKey(new Uint8Array(userKeyBytesLength))); .mockResolvedValue(new SymmetricCryptoKey(new Uint8Array(userKeyBytesLength)));
@@ -638,13 +638,13 @@ describe("deviceTrustService", () => {
); );
expect(result).toEqual(mockUserKey); expect(result).toEqual(mockUserKey);
expect(decryptToBytesSpy).toHaveBeenCalledTimes(1); expect(unwrapDecapsulationKeySpy).toHaveBeenCalledTimes(1);
expect(rsaDecryptSpy).toHaveBeenCalledTimes(1); expect(rsaDecryptSpy).toHaveBeenCalledTimes(1);
}); });
it("returns null and removes device key when the decryption fails", async () => { it("returns null and removes device key when the decryption fails", async () => {
const decryptToBytesSpy = jest const unwrapDecapsulationKeySpy = jest
.spyOn(encryptService, "decryptToBytes") .spyOn(encryptService, "unwrapDecapsulationKey")
.mockRejectedValue(new Error("Decryption error")); .mockRejectedValue(new Error("Decryption error"));
const setDeviceKeySpy = jest.spyOn(deviceTrustService as any, "setDeviceKey"); const setDeviceKeySpy = jest.spyOn(deviceTrustService as any, "setDeviceKey");
@@ -656,7 +656,7 @@ describe("deviceTrustService", () => {
); );
expect(result).toBeNull(); expect(result).toBeNull();
expect(decryptToBytesSpy).toHaveBeenCalledTimes(1); expect(unwrapDecapsulationKeySpy).toHaveBeenCalledTimes(1);
expect(setDeviceKeySpy).toHaveBeenCalledTimes(1); expect(setDeviceKeySpy).toHaveBeenCalledTimes(1);
expect(setDeviceKeySpy).toHaveBeenCalledWith(mockUserId, null); expect(setDeviceKeySpy).toHaveBeenCalledWith(mockUserId, null);
}); });
@@ -704,8 +704,8 @@ describe("deviceTrustService", () => {
DeviceResponse, DeviceResponse,
), ),
); );
encryptService.decryptToBytes.mockResolvedValue(null); encryptService.decryptBytes.mockResolvedValue(null);
encryptService.encrypt.mockResolvedValue(new EncString("test_encrypted_data")); encryptService.encryptString.mockResolvedValue(new EncString("test_encrypted_data"));
encryptService.encapsulateKeyUnsigned.mockResolvedValue( encryptService.encapsulateKeyUnsigned.mockResolvedValue(
new EncString("test_encrypted_data"), new EncString("test_encrypted_data"),
); );
@@ -752,9 +752,11 @@ describe("deviceTrustService", () => {
DeviceResponse, DeviceResponse,
), ),
); );
encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(64)); encryptService.unwrapEncapsulationKey.mockResolvedValue(new Uint8Array(64));
encryptService.encrypt.mockResolvedValue(new EncString("test_encrypted_data")); encryptService.wrapEncapsulationKey.mockResolvedValue(new EncString("test_encrypted_data"));
encryptService.rsaEncrypt.mockResolvedValue(new EncString("test_encrypted_data")); encryptService.encapsulateKeyUnsigned.mockResolvedValue(
new EncString("test_encrypted_data"),
);
const protectedDeviceResponse = new ProtectedDeviceResponse({ const protectedDeviceResponse = new ProtectedDeviceResponse({
id: "", id: "",
@@ -862,13 +864,15 @@ describe("deviceTrustService", () => {
}); });
// Mock the decryption of the public key with the old user key // Mock the decryption of the public key with the old user key
encryptService.decryptToBytes.mockImplementationOnce((_encValue, privateKeyValue) => { encryptService.unwrapEncapsulationKey.mockImplementationOnce(
expect(privateKeyValue.inner().type).toBe(EncryptionType.AesCbc256_HmacSha256_B64); (_encValue, privateKeyValue) => {
expect(new Uint8Array(privateKeyValue.toEncoded())[0]).toBe(FakeOldUserKeyMarker); expect(privateKeyValue.inner().type).toBe(EncryptionType.AesCbc256_HmacSha256_B64);
const data = new Uint8Array(250); expect(new Uint8Array(privateKeyValue.toEncoded())[0]).toBe(FakeOldUserKeyMarker);
data.fill(FakeDecryptedPublicKeyMarker, 0, 1); const data = new Uint8Array(250);
return Promise.resolve(data); data.fill(FakeDecryptedPublicKeyMarker, 0, 1);
}); return Promise.resolve(data);
},
);
// Mock the encryption of the new user key with the decrypted public key // Mock the encryption of the new user key with the decrypted public key
encryptService.encapsulateKeyUnsigned.mockImplementationOnce((data, publicKey) => { encryptService.encapsulateKeyUnsigned.mockImplementationOnce((data, publicKey) => {

View File

@@ -174,21 +174,13 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr
throw new Error("No master key found."); throw new Error("No master key found.");
} }
let decUserKey: Uint8Array; let decUserKey: SymmetricCryptoKey;
if (userKey.encryptionType === EncryptionType.AesCbc256_B64) { if (userKey.encryptionType === EncryptionType.AesCbc256_B64) {
decUserKey = await this.encryptService.decryptToBytes( decUserKey = await this.encryptService.unwrapSymmetricKey(userKey, masterKey);
userKey,
masterKey,
"Content: User Key; Encrypting Key: Master Key",
);
} else if (userKey.encryptionType === EncryptionType.AesCbc256_HmacSha256_B64) { } else if (userKey.encryptionType === EncryptionType.AesCbc256_HmacSha256_B64) {
const newKey = await this.keyGenerationService.stretchKey(masterKey); const newKey = await this.keyGenerationService.stretchKey(masterKey);
decUserKey = await this.encryptService.decryptToBytes( decUserKey = await this.encryptService.unwrapSymmetricKey(userKey, newKey);
userKey,
newKey,
"Content: User Key; Encrypting Key: Stretched Master Key",
);
} else { } else {
throw new Error("Unsupported encryption type."); throw new Error("Unsupported encryption type.");
} }
@@ -198,6 +190,6 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr
return null; return null;
} }
return new SymmetricCryptoKey(decUserKey) as UserKey; return decUserKey as UserKey;
} }
} }

View File

@@ -330,7 +330,7 @@ describe("keyService", () => {
everHadUserKeyState.nextState(null); everHadUserKeyState.nextState(null);
// Mock private key decryption // Mock private key decryption
encryptService.decryptToBytes.mockResolvedValue(mockRandomBytes); encryptService.unwrapDecapsulationKey.mockResolvedValue(mockRandomBytes);
}); });
it("throws if userKey is null", async () => { it("throws if userKey is null", async () => {
@@ -352,7 +352,7 @@ describe("keyService", () => {
}); });
it("throws if encPrivateKey cannot be decrypted with the userKey", async () => { it("throws if encPrivateKey cannot be decrypted with the userKey", async () => {
encryptService.decryptToBytes.mockResolvedValue(null); encryptService.unwrapDecapsulationKey.mockResolvedValue(null);
await expect( await expect(
keyService.setUserKeys(mockUserKey, mockEncPrivateKey, mockUserId), keyService.setUserKeys(mockUserKey, mockEncPrivateKey, mockUserId),
@@ -452,17 +452,16 @@ describe("keyService", () => {
// Decryption of the user private key // Decryption of the user private key
const fakeDecryptedUserPrivateKey = makeStaticByteArray(10, 1); const fakeDecryptedUserPrivateKey = makeStaticByteArray(10, 1);
encryptService.decryptToBytes.mockResolvedValue(fakeDecryptedUserPrivateKey); encryptService.unwrapDecapsulationKey.mockResolvedValue(fakeDecryptedUserPrivateKey);
const fakeUserPublicKey = makeStaticByteArray(10, 2); const fakeUserPublicKey = makeStaticByteArray(10, 2);
cryptoFunctionService.rsaExtractPublicKey.mockResolvedValue(fakeUserPublicKey); cryptoFunctionService.rsaExtractPublicKey.mockResolvedValue(fakeUserPublicKey);
const userPrivateKey = await firstValueFrom(keyService.userPrivateKey$(mockUserId)); const userPrivateKey = await firstValueFrom(keyService.userPrivateKey$(mockUserId));
expect(encryptService.decryptToBytes).toHaveBeenCalledWith( expect(encryptService.unwrapDecapsulationKey).toHaveBeenCalledWith(
fakeEncryptedUserPrivateKey, fakeEncryptedUserPrivateKey,
userKey, userKey,
"Content: Encrypted Private Key",
); );
expect(userPrivateKey).toBe(fakeDecryptedUserPrivateKey); expect(userPrivateKey).toBe(fakeDecryptedUserPrivateKey);
@@ -473,7 +472,7 @@ describe("keyService", () => {
const userPrivateKey = await firstValueFrom(keyService.userPrivateKey$(mockUserId)); const userPrivateKey = await firstValueFrom(keyService.userPrivateKey$(mockUserId));
expect(encryptService.decryptToBytes).not.toHaveBeenCalled(); expect(encryptService.unwrapDecapsulationKey).not.toHaveBeenCalled();
expect(userPrivateKey).toBeFalsy(); expect(userPrivateKey).toBeFalsy();
}); });
@@ -552,10 +551,12 @@ describe("keyService", () => {
providerKeysState.nextState(keys.providerKeys!); providerKeysState.nextState(keys.providerKeys!);
} }
encryptService.decryptToBytes.mockImplementation((encryptedPrivateKey, userKey) => { encryptService.unwrapDecapsulationKey.mockImplementation((encryptedPrivateKey, userKey) => {
// TOOD: Branch between provider and private key?
return Promise.resolve(fakePrivateKeyDecryption(encryptedPrivateKey, userKey)); return Promise.resolve(fakePrivateKeyDecryption(encryptedPrivateKey, userKey));
}); });
encryptService.unwrapSymmetricKey.mockImplementation((encryptedPrivateKey, userKey) => {
return Promise.resolve(new SymmetricCryptoKey(new Uint8Array(64)));
});
encryptService.decapsulateKeyUnsigned.mockImplementation((data, privateKey) => { encryptService.decapsulateKeyUnsigned.mockImplementation((data, privateKey) => {
return Promise.resolve(new SymmetricCryptoKey(fakeOrgKeyDecryption(data, privateKey))); return Promise.resolve(new SymmetricCryptoKey(fakeOrgKeyDecryption(data, privateKey)));
@@ -617,6 +618,7 @@ describe("keyService", () => {
}); });
it("returns decryption keys when some of the org keys are providers", async () => { it("returns decryption keys when some of the org keys are providers", async () => {
encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(64));
const org2Id = "org2Id" as OrganizationId; const org2Id = "org2Id" as OrganizationId;
updateKeys({ updateKeys({
userKey: makeSymmetricCryptoKey<UserKey>(64), userKey: makeSymmetricCryptoKey<UserKey>(64),
@@ -647,7 +649,7 @@ describe("keyService", () => {
const org2Key = decryptionKeys!.orgKeys![org2Id]; const org2Key = decryptionKeys!.orgKeys![org2Id];
expect(org2Key).not.toBeNull(); expect(org2Key).not.toBeNull();
expect(org2Key.keyB64).toContain("provider1Key"); expect(org2Key.toEncoded()).toHaveLength(64);
}); });
it("returns a stream that pays attention to updates of all data", async () => { it("returns a stream that pays attention to updates of all data", async () => {

View File

@@ -738,7 +738,7 @@ export class DefaultKeyService implements KeyServiceAbstraction {
const storePin = await this.shouldStoreKey(KeySuffixOptions.Pin, userId); const storePin = await this.shouldStoreKey(KeySuffixOptions.Pin, userId);
if (storePin) { if (storePin) {
// Decrypt userKeyEncryptedPin with user key // Decrypt userKeyEncryptedPin with user key
const pin = await this.encryptService.decryptToUtf8( const pin = await this.encryptService.decryptString(
(await this.pinService.getUserKeyEncryptedPin(userId))!, (await this.pinService.getUserKeyEncryptedPin(userId))!,
key, key,
); );
@@ -945,10 +945,9 @@ export class DefaultKeyService implements KeyServiceAbstraction {
return null; return null;
} }
return (await this.encryptService.decryptToBytes( return (await this.encryptService.unwrapDecapsulationKey(
new EncString(encryptedPrivateKey), new EncString(encryptedPrivateKey),
key, key,
"Content: Encrypted Private Key",
)) as UserPrivateKey; )) as UserPrivateKey;
} }

View File

@@ -58,6 +58,9 @@ function setupUserKeyValidation(
cipher.notes = mockEnc("EncryptedString"); cipher.notes = mockEnc("EncryptedString");
cipher.key = mockEnc("EncKey"); cipher.key = mockEnc("EncKey");
cipherService.getAll.mockResolvedValue([cipher]); cipherService.getAll.mockResolvedValue([cipher]);
encryptService.unwrapSymmetricKey.mockResolvedValue(
new SymmetricCryptoKey(makeStaticByteArray(64)),
);
encryptService.decryptToBytes.mockResolvedValue(makeStaticByteArray(64)); encryptService.decryptToBytes.mockResolvedValue(makeStaticByteArray(64));
encryptService.decryptString.mockResolvedValue("mockDecryptedString"); encryptService.decryptString.mockResolvedValue("mockDecryptedString");
(window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService);