1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-19 01:33:33 +00:00

[PM-23619] Remove getPrivateKey from the key service and update consumers (#15784)

* remove getPrivateKey from keyService

* Update consumer code

* Increase unit test coverage
This commit is contained in:
Thomas Avery
2025-08-05 09:25:50 -05:00
committed by GitHub
parent 7145092889
commit 2a3e1ae1f5
7 changed files with 358 additions and 143 deletions

View File

@@ -494,77 +494,79 @@ describe("keyService", () => {
});
describe("userPrivateKey$", () => {
type SetupKeysParams = {
makeMasterKey: boolean;
makeUserKey: boolean;
};
let mockUserKey: UserKey;
let mockUserPrivateKey: Uint8Array;
let mockEncryptedPrivateKey: EncryptedString;
function setupKeys({
makeMasterKey,
makeUserKey,
}: SetupKeysParams): [UserKey | null, MasterKey | null] {
const userKeyState = stateProvider.singleUser.getFake(mockUserId, USER_KEY);
const fakeMasterKey = makeMasterKey ? makeSymmetricCryptoKey<MasterKey>(64) : null;
masterPasswordService.masterKeySubject.next(fakeMasterKey);
userKeyState.nextState(null);
const fakeUserKey = makeUserKey ? makeSymmetricCryptoKey<UserKey>(64) : null;
userKeyState.nextState(fakeUserKey);
return [fakeUserKey, fakeMasterKey];
}
beforeEach(() => {
mockUserKey = makeSymmetricCryptoKey<UserKey>(64);
mockEncryptedPrivateKey = makeEncString("encryptedPrivateKey").encryptedString!;
mockUserPrivateKey = makeStaticByteArray(10, 1);
stateProvider.singleUser.getFake(mockUserId, USER_KEY).nextState(mockUserKey);
stateProvider.singleUser
.getFake(mockUserId, USER_ENCRYPTED_PRIVATE_KEY)
.nextState(mockEncryptedPrivateKey);
encryptService.unwrapDecapsulationKey.mockResolvedValue(mockUserPrivateKey);
});
it("will return users decrypted private key when user has a user key and encrypted private key set", async () => {
const [userKey] = setupKeys({
makeMasterKey: false,
makeUserKey: true,
it("returns the unwrapped user private key when user key and encrypted private key are set", async () => {
const result = await firstValueFrom(keyService.userPrivateKey$(mockUserId));
expect(result).toEqual(mockUserPrivateKey);
expect(encryptService.unwrapDecapsulationKey).toHaveBeenCalledWith(
new EncString(mockEncryptedPrivateKey),
mockUserKey,
);
});
it("throws an error if unwrapping encrypted private key fails", async () => {
encryptService.unwrapDecapsulationKey.mockImplementationOnce(() => {
throw new Error("Unwrapping failed");
});
const userEncryptedPrivateKeyState = stateProvider.singleUser.getFake(
mockUserId,
USER_ENCRYPTED_PRIVATE_KEY,
await expect(firstValueFrom(keyService.userPrivateKey$(mockUserId))).rejects.toThrow(
"Unwrapping failed",
);
const fakeEncryptedUserPrivateKey = makeEncString("1");
userEncryptedPrivateKeyState.nextState(fakeEncryptedUserPrivateKey.encryptedString!);
// Decryption of the user private key
const fakeDecryptedUserPrivateKey = makeStaticByteArray(10, 1);
encryptService.unwrapDecapsulationKey.mockResolvedValue(fakeDecryptedUserPrivateKey);
const fakeUserPublicKey = makeStaticByteArray(10, 2);
cryptoFunctionService.rsaExtractPublicKey.mockResolvedValue(fakeUserPublicKey);
const userPrivateKey = await firstValueFrom(keyService.userPrivateKey$(mockUserId));
expect(encryptService.unwrapDecapsulationKey).toHaveBeenCalledWith(
fakeEncryptedUserPrivateKey,
userKey,
);
expect(userPrivateKey).toBe(fakeDecryptedUserPrivateKey);
});
it("returns null user private key when no user key is found", async () => {
setupKeys({ makeMasterKey: false, makeUserKey: false });
it("returns null if user key is not set", async () => {
stateProvider.singleUser.getFake(mockUserId, USER_KEY).nextState(null);
const userPrivateKey = await firstValueFrom(keyService.userPrivateKey$(mockUserId));
const result = await firstValueFrom(keyService.userPrivateKey$(mockUserId));
expect(result).toBeNull();
expect(encryptService.unwrapDecapsulationKey).not.toHaveBeenCalled();
expect(userPrivateKey).toBeFalsy();
});
it("returns null when user does not have a private key set", async () => {
setupKeys({ makeUserKey: true, makeMasterKey: false });
it("returns null if encrypted private key is not set", async () => {
stateProvider.singleUser.getFake(mockUserId, USER_ENCRYPTED_PRIVATE_KEY).nextState(null);
const encryptedUserPrivateKeyState = stateProvider.singleUser.getFake(
mockUserId,
USER_ENCRYPTED_PRIVATE_KEY,
);
encryptedUserPrivateKeyState.nextState(null);
const result = await firstValueFrom(keyService.userPrivateKey$(mockUserId));
const userPrivateKey = await firstValueFrom(keyService.userPrivateKey$(mockUserId));
expect(userPrivateKey).toBeFalsy();
expect(result).toBeNull();
expect(encryptService.unwrapDecapsulationKey).not.toHaveBeenCalled();
});
it("reacts to changes in user key or encrypted private key", async () => {
// Initial state: both set
let result = await firstValueFrom(keyService.userPrivateKey$(mockUserId));
expect(result).toEqual(mockUserPrivateKey);
// Change user key to null
stateProvider.singleUser.getFake(mockUserId, USER_KEY).nextState(null);
result = await firstValueFrom(keyService.userPrivateKey$(mockUserId));
expect(result).toBeNull();
// Restore user key, remove encrypted private key
stateProvider.singleUser.getFake(mockUserId, USER_KEY).nextState(mockUserKey);
stateProvider.singleUser.getFake(mockUserId, USER_ENCRYPTED_PRIVATE_KEY).nextState(null);
result = await firstValueFrom(keyService.userPrivateKey$(mockUserId));
expect(result).toBeNull();
});
});
@@ -1063,7 +1065,7 @@ describe("keyService", () => {
});
});
describe("userPrivateKey$", () => {
describe("userEncryptionKeyPair$", () => {
type SetupKeysParams = {
makeMasterKey: boolean;
makeUserKey: boolean;