mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +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:
@@ -294,16 +294,6 @@ export abstract class KeyService {
|
||||
* @param encPrivateKey An encrypted private key
|
||||
*/
|
||||
abstract setPrivateKey(encPrivateKey: string, userId: UserId): Promise<void>;
|
||||
/**
|
||||
* Returns the private key from memory. If not available, decrypts it
|
||||
* from storage and stores it in memory
|
||||
* @returns The user's private key
|
||||
*
|
||||
* @throws Error when no active user
|
||||
*
|
||||
* @deprecated Use {@link userPrivateKey$} instead.
|
||||
*/
|
||||
abstract getPrivateKey(): Promise<Uint8Array | null>;
|
||||
|
||||
/**
|
||||
* Gets an observable stream of the given users decrypted private key, will emit null if the user
|
||||
@@ -311,6 +301,8 @@ export abstract class KeyService {
|
||||
* encrypted private key at all.
|
||||
*
|
||||
* @param userId The user id of the user to get the data for.
|
||||
* @returns An observable stream of the decrypted private key or null.
|
||||
* @throws Error when decryption of the encrypted private key fails.
|
||||
*/
|
||||
abstract userPrivateKey$(userId: UserId): Observable<UserPrivateKey | null>;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -501,16 +501,6 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
.update(() => encPrivateKey);
|
||||
}
|
||||
|
||||
async getPrivateKey(): Promise<Uint8Array | null> {
|
||||
const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
|
||||
if (activeUserId == null) {
|
||||
throw new Error("User must be active while attempting to retrieve private key.");
|
||||
}
|
||||
|
||||
return await firstValueFrom(this.userPrivateKey$(activeUserId));
|
||||
}
|
||||
|
||||
// TODO: Make public key required
|
||||
async getFingerprint(fingerprintMaterial: string, publicKey?: Uint8Array): Promise<string[]> {
|
||||
if (publicKey == null) {
|
||||
|
||||
Reference in New Issue
Block a user