diff --git a/libs/key-management/src/abstractions/key.service.ts b/libs/key-management/src/abstractions/key.service.ts index c6c751bf25c..48724f06fcd 100644 --- a/libs/key-management/src/abstractions/key.service.ts +++ b/libs/key-management/src/abstractions/key.service.ts @@ -22,12 +22,6 @@ import { import { KdfConfig } from "../models/kdf-config"; -export class UserPrivateKeyDecryptionFailedError extends Error { - constructor() { - super("Failed to decrypt the user's private key."); - } -} - /** * An object containing all the users key needed to decrypt a users personal and organization vaults. */ @@ -67,20 +61,6 @@ export abstract class KeyService { * @param userId The desired user */ abstract setUserKey(key: UserKey, userId: UserId): Promise; - /** - * Sets the provided user keys and stores any other necessary versions - * (such as auto, biometrics, or pin). - * Also sets the user's encrypted private key in storage and - * clears the decrypted private key from memory - * Note: does not clear the private key if null is provided - * - * @throws Error when userKey, encPrivateKey or userId is null - * @throws UserPrivateKeyDecryptionFailedError when the userKey cannot decrypt encPrivateKey - * @param userKey The user key to set - * @param encPrivateKey An encrypted private key - * @param userId The desired user - */ - abstract setUserKeys(userKey: UserKey, encPrivateKey: string, userId: UserId): Promise; /** * Gets the user key from memory and sets it again, * kicking off a refresh of any additional keys @@ -139,13 +119,6 @@ export abstract class KeyService { * @returns A new user key */ abstract makeUserKeyV1(): Promise; - /** - * Clears the user's stored version of the user key - * @param keySuffix The desired version of the key to clear - * @param userId The desired user - * @throws Error when userId is null or undefined. - */ - abstract clearStoredUserKey(keySuffix: KeySuffixOptions, userId: string): Promise; /** * Retrieves the user's master key if it is in state, or derives it from the provided password * @param password The user's master password that will be used to derive a master key if one isn't found @@ -295,28 +268,6 @@ export abstract class KeyService { */ abstract userEncryptedPrivateKey$(userId: UserId): Observable; - /** - * Gets an observable stream of the given users decrypted private key and public key, guaranteed to be consistent. - * Will emit null if the user doesn't have a userkey to decrypt the encrypted private key, or null if the user doesn't have a private key - * at all. - * - * @param userId The user id of the user to get the data for. - */ - abstract userEncryptionKeyPair$( - userId: UserId, - ): Observable<{ privateKey: UserPrivateKey; publicKey: UserPublicKey } | null>; - - /** - * Gets an observable stream of the given users decrypted private key and public key, guaranteed to be consistent. - * Will emit null if the user doesn't have a userkey to decrypt the encrypted private key, or null if the user doesn't have a private key - * at all. - * - * @param userId The user id of the user to get the data for. - */ - abstract userEncryptionKeyPair$( - userId: UserId, - ): Observable<{ privateKey: UserPrivateKey; publicKey: UserPublicKey } | null>; - /** * Generates a fingerprint phrase for the public key provided. * diff --git a/libs/key-management/src/key.service.spec.ts b/libs/key-management/src/key.service.spec.ts index 52fecf26c71..81d7a7f0f95 100644 --- a/libs/key-management/src/key.service.spec.ts +++ b/libs/key-management/src/key.service.spec.ts @@ -48,7 +48,6 @@ import { } from "@bitwarden/common/types/key"; import { KdfConfigService } from "./abstractions/kdf-config.service"; -import { UserPrivateKeyDecryptionFailedError } from "./abstractions/key.service"; import { DefaultKeyService } from "./key.service"; import { KdfConfig } from "./models/kdf-config"; @@ -304,70 +303,6 @@ describe("keyService", () => { }); }); - describe("setUserKeys", () => { - let mockUserKey: UserKey; - let mockEncPrivateKey: EncryptedString; - let everHadUserKeyState: FakeSingleUserState; - - beforeEach(() => { - const mockRandomBytes = new Uint8Array(64) as CsprngArray; - mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey; - mockEncPrivateKey = new SymmetricCryptoKey(mockRandomBytes).toString() as EncryptedString; - everHadUserKeyState = stateProvider.singleUser.getFake(mockUserId, USER_EVER_HAD_USER_KEY); - - // Initialize storage - everHadUserKeyState.nextState(null); - - // Mock private key decryption - encryptService.unwrapDecapsulationKey.mockResolvedValue(mockRandomBytes); - }); - - it("throws if userKey is null", async () => { - await expect( - keyService.setUserKeys(null as unknown as UserKey, mockEncPrivateKey, mockUserId), - ).rejects.toThrow("No userKey provided."); - }); - - it("throws if encPrivateKey is null", async () => { - await expect( - keyService.setUserKeys(mockUserKey, null as unknown as EncryptedString, mockUserId), - ).rejects.toThrow("No encPrivateKey provided."); - }); - - it("throws if userId is null", async () => { - await expect( - keyService.setUserKeys(mockUserKey, mockEncPrivateKey, null as unknown as UserId), - ).rejects.toThrow("No userId provided."); - }); - - it("throws if encPrivateKey cannot be decrypted with the userKey", async () => { - encryptService.unwrapDecapsulationKey.mockResolvedValue(null); - - await expect( - keyService.setUserKeys(mockUserKey, mockEncPrivateKey, mockUserId), - ).rejects.toThrow(UserPrivateKeyDecryptionFailedError); - }); - - // We already have tests for setUserKey, so we just need to test that the correct methods are called - it("calls setUserKey with the userKey and userId", async () => { - const setUserKeySpy = jest.spyOn(keyService, "setUserKey"); - - await keyService.setUserKeys(mockUserKey, mockEncPrivateKey, mockUserId); - - expect(setUserKeySpy).toHaveBeenCalledWith(mockUserKey, mockUserId); - }); - - // We already have tests for setPrivateKey, so we just need to test that the correct methods are called - // TODO: Move those tests into here since `setPrivateKey` will be converted to a private method - it("calls setPrivateKey with the encPrivateKey and userId", async () => { - const setEncryptedPrivateKeySpy = jest.spyOn(keyService, "setPrivateKey"); - - await keyService.setUserKeys(mockUserKey, mockEncPrivateKey, mockUserId); - - expect(setEncryptedPrivateKeySpy).toHaveBeenCalledWith(mockEncPrivateKey, mockUserId); - }); - }); - describe("makeSendKey", () => { const mockRandomBytes = new Uint8Array(16) as CsprngArray; it("calls keyGenerationService with expected hard coded parameters", async () => { @@ -381,45 +316,6 @@ describe("keyService", () => { }); }); - describe("clearStoredUserKey", () => { - describe("input validation", () => { - const invalidUserIdTestCases = [ - { keySuffix: KeySuffixOptions.Auto, userId: null as unknown as UserId }, - { keySuffix: KeySuffixOptions.Auto, userId: undefined as unknown as UserId }, - { keySuffix: KeySuffixOptions.Pin, userId: null as unknown as UserId }, - { keySuffix: KeySuffixOptions.Pin, userId: undefined as unknown as UserId }, - ]; - test.each(invalidUserIdTestCases)( - "throws when keySuffix is $keySuffix and userId is $userId", - async ({ keySuffix, userId }) => { - await expect(keyService.clearStoredUserKey(keySuffix, userId)).rejects.toThrow( - "UserId is required", - ); - }, - ); - }); - - describe("with Auto key suffix", () => { - it("UserKeyAutoUnlock is cleared and pin keys are not cleared", async () => { - await keyService.clearStoredUserKey(KeySuffixOptions.Auto, mockUserId); - - expect(stateService.setUserKeyAutoUnlock).toHaveBeenCalledWith(null, { - userId: mockUserId, - }); - expect(pinService.clearPinKeyEncryptedUserKeyEphemeral).not.toHaveBeenCalled(); - }); - }); - - describe("with PIN key suffix", () => { - it("pin keys are cleared and user key auto unlock not", async () => { - await keyService.clearStoredUserKey(KeySuffixOptions.Pin, mockUserId); - - expect(stateService.setUserKeyAutoUnlock).not.toHaveBeenCalled(); - expect(pinService.clearPinKeyEncryptedUserKeyEphemeral).toHaveBeenCalledWith(mockUserId); - }); - }); - }); - describe("clearKeys", () => { test.each([null as unknown as UserId, undefined as unknown as UserId])( "throws when the provided userId is %s", @@ -1095,53 +991,6 @@ describe("keyService", () => { }); }); - describe("userEncryptionKeyPair$", () => { - type SetupKeysParams = { - makeMasterKey: boolean; - makeUserKey: boolean; - }; - - function setupKeys({ makeMasterKey, makeUserKey }: SetupKeysParams): [UserKey, MasterKey] { - const userKeyState = stateProvider.singleUser.getFake(mockUserId, USER_KEY); - const fakeMasterKey = makeMasterKey ? makeSymmetricCryptoKey(64) : null; - masterPasswordService.masterKeySubject.next(fakeMasterKey); - userKeyState.nextState(null); - const fakeUserKey = makeUserKey ? makeSymmetricCryptoKey(64) : null; - userKeyState.nextState(fakeUserKey); - return [fakeUserKey, fakeMasterKey]; - } - - it("returns null when private key is null", async () => { - setupKeys({ makeMasterKey: false, makeUserKey: false }); - - keyService.userPrivateKey$ = jest.fn().mockReturnValue(new BehaviorSubject(null)); - const key = await firstValueFrom(keyService.userEncryptionKeyPair$(mockUserId)); - expect(key).toEqual(null); - }); - - it("returns null when private key is undefined", async () => { - setupKeys({ makeUserKey: true, makeMasterKey: false }); - - keyService.userPrivateKey$ = jest.fn().mockReturnValue(new BehaviorSubject(undefined)); - const key = await firstValueFrom(keyService.userEncryptionKeyPair$(mockUserId)); - expect(key).toEqual(null); - }); - - it("returns keys when private key is defined", async () => { - setupKeys({ makeUserKey: false, makeMasterKey: true }); - - keyService.userPrivateKey$ = jest.fn().mockReturnValue(new BehaviorSubject("private key")); - cryptoFunctionService.rsaExtractPublicKey.mockResolvedValue( - Utils.fromUtf8ToArray("public key"), - ); - const key = await firstValueFrom(keyService.userEncryptionKeyPair$(mockUserId)); - expect(key).toEqual({ - privateKey: "private key", - publicKey: Utils.fromUtf8ToArray("public key"), - }); - }); - }); - describe("getUserKeyFromStorage", () => { let mockUserKey: UserKey; let validateUserKeySpy: jest.SpyInstance; diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index cdf7d594f28..27a3cc23d63 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -62,7 +62,6 @@ import { KdfConfigService } from "./abstractions/kdf-config.service"; import { CipherDecryptionKeys, KeyService as KeyServiceAbstraction, - UserPrivateKeyDecryptionFailedError, } from "./abstractions/key.service"; import { KdfConfig } from "./models/kdf-config"; @@ -105,30 +104,6 @@ export class DefaultKeyService implements KeyServiceAbstraction { await this.storeAdditionalKeys(key, userId); } - async setUserKeys( - userKey: UserKey, - encPrivateKey: EncryptedString, - userId: UserId, - ): Promise { - if (userKey == null) { - throw new Error("No userKey provided. Lock the user to clear the key"); - } - if (encPrivateKey == null) { - throw new Error("No encPrivateKey provided."); - } - if (userId == null) { - throw new Error("No userId provided."); - } - - const decryptedPrivateKey = await this.decryptPrivateKey(encPrivateKey, userKey); - if (decryptedPrivateKey == null) { - throw new UserPrivateKeyDecryptionFailedError(); - } - - await this.setUserKey(userKey, userId); - await this.setPrivateKey(encPrivateKey, userId); - } - async refreshAdditionalKeys(userId: UserId): Promise { if (userId == null) { throw new Error("UserId is required."); @@ -221,19 +196,6 @@ export class DefaultKeyService implements KeyServiceAbstraction { await this.clearAllStoredUserKeys(userId); } - async clearStoredUserKey(keySuffix: KeySuffixOptions, userId: UserId): Promise { - if (userId == null) { - throw new Error("UserId is required"); - } - - if (keySuffix === KeySuffixOptions.Auto) { - await this.stateService.setUserKeyAutoUnlock(null, { userId: userId }); - } - if (keySuffix === KeySuffixOptions.Pin) { - await this.pinService.clearPinKeyEncryptedUserKeyEphemeral(userId); - } - } - /** * @deprecated Please use `makeMasterPasswordAuthenticationData`, `unwrapUserKeyFromMasterPasswordUnlockData` or `makeMasterPasswordUnlockData` in @link MasterPasswordService instead. */ @@ -577,7 +539,7 @@ export class DefaultKeyService implements KeyServiceAbstraction { } // ---HELPERS--- - async validateUserKey(key: UserKey | MasterKey | null, userId: UserId): Promise { + async validateUserKey(key: UserKey, userId: UserId): Promise { if (key == null) { return false; } @@ -799,21 +761,6 @@ export class DefaultKeyService implements KeyServiceAbstraction { return this.userPrivateKeyHelper$(userId).pipe(map((keys) => keys?.userPrivateKey ?? null)); } - userEncryptionKeyPair$( - userId: UserId, - ): Observable<{ privateKey: UserPrivateKey; publicKey: UserPublicKey } | null> { - return this.userPrivateKey$(userId).pipe( - switchMap(async (privateKey) => { - if (privateKey == null) { - return null; - } - - const publicKey = (await this.derivePublicKey(privateKey))!; - return { privateKey, publicKey }; - }), - ); - } - userEncryptedPrivateKey$(userId: UserId): Observable { return this.stateProvider.getUser(userId, USER_ENCRYPTED_PRIVATE_KEY).state$; }