diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index c5f4325526e..707fa7b670d 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -698,9 +698,7 @@ export default class MainBackground { this.masterPasswordService = new MasterPasswordService( this.stateProvider, - this.stateService, this.keyGenerationService, - this.encryptService, this.logService, this.cryptoFunctionService, this.accountService, diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index bd0add19661..3453e0cff70 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -455,9 +455,7 @@ export class ServiceContainer { this.kdfConfigService = new DefaultKdfConfigService(this.stateProvider); this.masterPasswordService = new MasterPasswordService( this.stateProvider, - this.stateService, this.keyGenerationService, - this.encryptService, this.logService, this.cryptoFunctionService, this.accountService, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index b07e0865783..954598de9ca 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1069,9 +1069,7 @@ const safeProviders: SafeProvider[] = [ useClass: MasterPasswordService, deps: [ StateProvider, - StateServiceAbstraction, KeyGenerationService, - EncryptService, LogService, CryptoFunctionServiceAbstraction, AccountServiceAbstraction, diff --git a/libs/common/src/key-management/master-password/services/master-password.service.spec.ts b/libs/common/src/key-management/master-password/services/master-password.service.spec.ts index f5fee3be4c5..e3d0bf51d67 100644 --- a/libs/common/src/key-management/master-password/services/master-password.service.spec.ts +++ b/libs/common/src/key-management/master-password/services/master-password.service.spec.ts @@ -6,22 +6,22 @@ import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk- import { Utils } from "@bitwarden/common/platform/misc/utils"; // eslint-disable-next-line no-restricted-imports import { Argon2KdfConfig, KdfConfig, KdfType, PBKDF2KdfConfig } from "@bitwarden/key-management"; +import { PureCrypto } from "@bitwarden/sdk-internal"; import { FakeAccountService, FakeStateProvider, + makeEncString, makeSymmetricCryptoKey, mockAccountServiceWith, } from "../../../../spec"; import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason"; import { LogService } from "../../../platform/abstractions/log.service"; -import { StateService } from "../../../platform/abstractions/state.service"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { UserId } from "../../../types/guid"; import { MasterKey, UserKey } from "../../../types/key"; import { KeyGenerationService } from "../../crypto"; import { CryptoFunctionService } from "../../crypto/abstractions/crypto-function.service"; -import { EncryptService } from "../../crypto/abstractions/encrypt.service"; import { EncString } from "../../crypto/models/enc-string"; import { MasterKeyWrappedUserKey, @@ -31,6 +31,7 @@ import { import { FORCE_SET_PASSWORD_REASON, + MASTER_KEY, MASTER_KEY_ENCRYPTED_USER_KEY, MASTER_PASSWORD_UNLOCK_KEY, MasterPasswordService, @@ -39,9 +40,7 @@ import { describe("MasterPasswordService", () => { let sut: MasterPasswordService; - let stateService: MockProxy; let keyGenerationService: MockProxy; - let encryptService: MockProxy; let logService: MockProxy; let cryptoFunctionService: MockProxy; let accountService: FakeAccountService; @@ -53,18 +52,12 @@ describe("MasterPasswordService", () => { const kdfArgon2: KdfConfig = new Argon2KdfConfig(4, 64, 3); const salt = "test@bitwarden.com" as MasterPasswordSalt; const userKey = makeSymmetricCryptoKey(64, 2) as UserKey; - const testUserKey: SymmetricCryptoKey = makeSymmetricCryptoKey(64, 1); - const testMasterKey: MasterKey = makeSymmetricCryptoKey(32, 2); - const testStretchedMasterKey: SymmetricCryptoKey = makeSymmetricCryptoKey(64, 3); const testMasterKeyEncryptedKey = "0.gbauOANURUHqvhLTDnva1A==|nSW+fPumiuTaDB/s12+JO88uemV6rhwRSR+YR1ZzGr5j6Ei3/h+XEli2Unpz652NlZ9NTuRpHxeOqkYYJtp7J+lPMoclgteXuAzUu9kqlRc="; - const testStretchedMasterKeyEncryptedKey = - "2.gbauOANURUHqvhLTDnva1A==|nSW+fPumiuTaDB/s12+JO88uemV6rhwRSR+YR1ZzGr5j6Ei3/h+XEli2Unpz652NlZ9NTuRpHxeOqkYYJtp7J+lPMoclgteXuAzUu9kqlRc=|DeUFkhIwgkGdZA08bDnDqMMNmZk21D+H5g8IostPKAY="; + const sdkLoadServiceReady = jest.fn(); beforeEach(() => { - stateService = mock(); keyGenerationService = mock(); - encryptService = mock(); logService = mock(); cryptoFunctionService = mock(); accountService = mockAccountServiceWith(userId); @@ -72,18 +65,18 @@ describe("MasterPasswordService", () => { sut = new MasterPasswordService( stateProvider, - stateService, keyGenerationService, - encryptService, logService, cryptoFunctionService, accountService, ); - encryptService.unwrapSymmetricKey.mockResolvedValue(makeSymmetricCryptoKey(64, 1)); keyGenerationService.stretchKey.mockResolvedValue(makeSymmetricCryptoKey(64, 3)); Object.defineProperty(SdkLoadService, "Ready", { - value: Promise.resolve(), + value: new Promise((resolve) => { + sdkLoadServiceReady(); + resolve(undefined); + }), configurable: true, }); }); @@ -159,41 +152,62 @@ describe("MasterPasswordService", () => { expect(state).toEqual(ForceSetPasswordReason.None); }); }); + describe("decryptUserKeyWithMasterKey", () => { - it("decrypts a userkey wrapped in AES256-CBC", async () => { - encryptService.unwrapSymmetricKey.mockResolvedValue(testUserKey); - await sut.decryptUserKeyWithMasterKey( - testMasterKey, - userId, - new EncString(testMasterKeyEncryptedKey), - ); - expect(encryptService.unwrapSymmetricKey).toHaveBeenCalledWith( - new EncString(testMasterKeyEncryptedKey), - testMasterKey, - ); + const masterKey = makeSymmetricCryptoKey(64, 0) as MasterKey; + const userKey = makeSymmetricCryptoKey(64, 1) as UserKey; + const masterKeyEncryptedUserKey = makeEncString("test-encrypted-user-key"); + + const decryptUserKeyWithMasterKeyMock = jest.spyOn( + PureCrypto, + "decrypt_user_key_with_master_key", + ); + + beforeEach(() => { + decryptUserKeyWithMasterKeyMock.mockReturnValue(userKey.toEncoded()); }); - it("decrypts a userkey wrapped in AES256-CBC-HMAC", async () => { - encryptService.unwrapSymmetricKey.mockResolvedValue(testUserKey); - keyGenerationService.stretchKey.mockResolvedValue(testStretchedMasterKey); - await sut.decryptUserKeyWithMasterKey( - testMasterKey, + + it("successfully decrypts", async () => { + const decryptedUserKey = await sut.decryptUserKeyWithMasterKey( + masterKey, userId, - new EncString(testStretchedMasterKeyEncryptedKey), + masterKeyEncryptedUserKey, + ); + + expect(decryptedUserKey).toEqual(new SymmetricCryptoKey(userKey.toEncoded())); + expect(sdkLoadServiceReady).toHaveBeenCalled(); + expect(PureCrypto.decrypt_user_key_with_master_key).toHaveBeenCalledWith( + masterKeyEncryptedUserKey.toSdk(), + masterKey.toEncoded(), ); - expect(encryptService.unwrapSymmetricKey).toHaveBeenCalledWith( - new EncString(testStretchedMasterKeyEncryptedKey), - testStretchedMasterKey, + expect(sdkLoadServiceReady.mock.invocationCallOrder[0]).toBeLessThan( + decryptUserKeyWithMasterKeyMock.mock.invocationCallOrder[0], ); - expect(keyGenerationService.stretchKey).toHaveBeenCalledWith(testMasterKey); }); - it("returns null if failed to decrypt", async () => { - encryptService.unwrapSymmetricKey.mockRejectedValue(new Error("Decryption failed")); - const result = await sut.decryptUserKeyWithMasterKey( - testMasterKey, + + it("returns null when failed to decrypt", async () => { + decryptUserKeyWithMasterKeyMock.mockImplementation(() => { + throw new Error("Decryption failed"); + }); + + const decryptedUserKey = await sut.decryptUserKeyWithMasterKey( + masterKey, userId, - new EncString(testStretchedMasterKeyEncryptedKey), + masterKeyEncryptedUserKey, ); - expect(result).toBeNull(); + expect(decryptedUserKey).toBeNull(); + }); + + it("returns error when master key is null", async () => { + stateProvider.singleUser.getFake(userId, MASTER_KEY).nextState(null); + + await expect( + sut.decryptUserKeyWithMasterKey( + null as unknown as MasterKey, + userId, + masterKeyEncryptedUserKey, + ), + ).rejects.toThrow("No master key found."); }); }); @@ -331,11 +345,11 @@ describe("MasterPasswordService", () => { it.each([kdfPBKDF2, kdfArgon2])( "sets the master password unlock data kdf %o in the state", async (kdfConfig) => { - const masterPasswordUnlockData = await sut.makeMasterPasswordUnlockData( - "test-password", - kdfConfig, + const masterKeyWrappedUserKey = makeEncString().toSdk() as MasterKeyWrappedUserKey; + const masterPasswordUnlockData = new MasterPasswordUnlockData( salt, - userKey, + kdfConfig, + masterKeyWrappedUserKey, ); await sut.setMasterPasswordUnlockData(masterPasswordUnlockData, userId); diff --git a/libs/common/src/key-management/master-password/services/master-password.service.ts b/libs/common/src/key-management/master-password/services/master-password.service.ts index 5cb6bb96a45..8012a9230e7 100644 --- a/libs/common/src/key-management/master-password/services/master-password.service.ts +++ b/libs/common/src/key-management/master-password/services/master-password.service.ts @@ -12,8 +12,6 @@ import { PureCrypto } from "@bitwarden/sdk-internal"; import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason"; import { LogService } from "../../../platform/abstractions/log.service"; -import { StateService } from "../../../platform/abstractions/state.service"; -import { EncryptionType } from "../../../platform/enums"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { MASTER_PASSWORD_DISK, @@ -26,7 +24,6 @@ import { UserId } from "../../../types/guid"; import { MasterKey, UserKey } from "../../../types/key"; import { KeyGenerationService } from "../../crypto"; import { CryptoFunctionService } from "../../crypto/abstractions/crypto-function.service"; -import { EncryptService } from "../../crypto/abstractions/encrypt.service"; import { EncryptedString, EncString } from "../../crypto/models/enc-string"; import { InternalMasterPasswordServiceAbstraction } from "../abstractions/master-password.service.abstraction"; import { @@ -38,7 +35,7 @@ import { } from "../types/master-password.types"; /** Memory since master key shouldn't be available on lock */ -const MASTER_KEY = new UserKeyDefinition(MASTER_PASSWORD_MEMORY, "masterKey", { +export const MASTER_KEY = new UserKeyDefinition(MASTER_PASSWORD_MEMORY, "masterKey", { deserializer: (masterKey) => SymmetricCryptoKey.fromJSON(masterKey) as MasterKey, clearOn: ["lock", "logout"], }); @@ -82,9 +79,7 @@ export const MASTER_PASSWORD_UNLOCK_KEY = new UserKeyDefinition