1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 13:53:34 +00:00

[PM-22611] Require userid for masterKey methods on the key service (#15663)

* Require userId on targeted methods.

* update method consumers

* unit tests
This commit is contained in:
Thomas Avery
2025-07-25 09:37:04 -05:00
committed by GitHub
parent b358d5663d
commit 2db31d1228
6 changed files with 233 additions and 88 deletions

View File

@@ -428,7 +428,8 @@ export class LoginCommand {
); );
const request = new PasswordRequest(); const request = new PasswordRequest();
request.masterPasswordHash = await this.keyService.hashMasterKey(currentPassword, null); const masterKey = await this.keyService.getOrDeriveMasterKey(currentPassword, userId);
request.masterPasswordHash = await this.keyService.hashMasterKey(currentPassword, masterKey);
request.masterPasswordHint = hint; request.masterPasswordHint = hint;
request.newMasterPasswordHash = newPasswordHash; request.newMasterPasswordHash = newPasswordHash;
request.key = newUserKey[1].encryptedString; request.key = newUserKey[1].encryptedString;

View File

@@ -89,7 +89,7 @@ describe("ChangeEmailComponent", () => {
}); });
keyService.getOrDeriveMasterKey keyService.getOrDeriveMasterKey
.calledWith("password", "UserId") .calledWith("password", "UserId" as UserId)
.mockResolvedValue("getOrDeriveMasterKey" as any); .mockResolvedValue("getOrDeriveMasterKey" as any);
keyService.hashMasterKey keyService.hashMasterKey
.calledWith("password", "getOrDeriveMasterKey" as any) .calledWith("password", "getOrDeriveMasterKey" as any)

View File

@@ -2,14 +2,13 @@
// @ts-strict-ignore // @ts-strict-ignore
import { Component, Inject } from "@angular/core"; import { Component, Inject } from "@angular/core";
import { FormGroup, FormControl, Validators } from "@angular/forms"; import { FormGroup, FormControl, Validators } from "@angular/forms";
import { firstValueFrom, map } from "rxjs"; import { firstValueFrom } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { KdfRequest } from "@bitwarden/common/models/request/kdf.request"; import { KdfRequest } from "@bitwarden/common/models/request/kdf.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DIALOG_DATA, ToastService } from "@bitwarden/components"; import { DIALOG_DATA, ToastService } from "@bitwarden/components";
import { KdfConfig, KdfType, KeyService } from "@bitwarden/key-management"; import { KdfConfig, KdfType, KeyService } from "@bitwarden/key-management";
@@ -31,7 +30,6 @@ export class ChangeKdfConfirmationComponent {
constructor( constructor(
private apiService: ApiService, private apiService: ApiService,
private i18nService: I18nService, private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,
private keyService: KeyService, private keyService: KeyService,
private messagingService: MessagingService, private messagingService: MessagingService,
@Inject(DIALOG_DATA) params: { kdf: KdfType; kdfConfig: KdfConfig }, @Inject(DIALOG_DATA) params: { kdf: KdfType; kdfConfig: KdfConfig },
@@ -58,6 +56,10 @@ export class ChangeKdfConfirmationComponent {
}; };
private async makeKeyAndSaveAsync() { private async makeKeyAndSaveAsync() {
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
if (activeAccount == null) {
throw new Error("No active account found.");
}
const masterPassword = this.form.value.masterPassword; const masterPassword = this.form.value.masterPassword;
// Ensure the KDF config is valid. // Ensure the KDF config is valid.
@@ -70,13 +72,14 @@ export class ChangeKdfConfirmationComponent {
request.kdfMemory = this.kdfConfig.memory; request.kdfMemory = this.kdfConfig.memory;
request.kdfParallelism = this.kdfConfig.parallelism; request.kdfParallelism = this.kdfConfig.parallelism;
} }
const masterKey = await this.keyService.getOrDeriveMasterKey(masterPassword); const masterKey = await this.keyService.getOrDeriveMasterKey(masterPassword, activeAccount.id);
request.masterPasswordHash = await this.keyService.hashMasterKey(masterPassword, masterKey); request.masterPasswordHash = await this.keyService.hashMasterKey(masterPassword, masterKey);
const email = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.email)),
);
const newMasterKey = await this.keyService.makeMasterKey(masterPassword, email, this.kdfConfig); const newMasterKey = await this.keyService.makeMasterKey(
masterPassword,
activeAccount.email,
this.kdfConfig,
);
request.newMasterPasswordHash = await this.keyService.hashMasterKey( request.newMasterPasswordHash = await this.keyService.hashMasterKey(
masterPassword, masterPassword,
newMasterKey, newMasterKey,

View File

@@ -163,11 +163,14 @@ export abstract class KeyService {
*/ */
abstract clearStoredUserKey(keySuffix: KeySuffixOptions, userId: string): Promise<void>; abstract clearStoredUserKey(keySuffix: KeySuffixOptions, userId: string): Promise<void>;
/** /**
* @throws Error when userId is null and no active user * 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 * @param password The user's master password that will be used to derive a master key if one isn't found
* @param userId The desired user * @param userId The desired user
* @throws Error when userId is null/undefined.
* @throws Error when email or Kdf configuration cannot be found for the user.
* @returns The user's master key if it exists, or a newly derived master key.
*/ */
abstract getOrDeriveMasterKey(password: string, userId?: string): Promise<MasterKey>; abstract getOrDeriveMasterKey(password: string, userId: UserId): Promise<MasterKey>;
/** /**
* Generates a master key from the provided password * Generates a master key from the provided password
* @param password The user's master password * @param password The user's master password
@@ -175,7 +178,7 @@ export abstract class KeyService {
* @param KdfConfig The user's key derivation function configuration * @param KdfConfig The user's key derivation function configuration
* @returns A master key derived from the provided password * @returns A master key derived from the provided password
*/ */
abstract makeMasterKey(password: string, email: string, KdfConfig: KdfConfig): Promise<MasterKey>; abstract makeMasterKey(password: string, email: string, kdfConfig: KdfConfig): Promise<MasterKey>;
/** /**
* Encrypts the existing (or provided) user key with the * Encrypts the existing (or provided) user key with the
* provided master key * provided master key
@@ -191,24 +194,25 @@ export abstract class KeyService {
* Creates a master password hash from the user's master password. Can * Creates a master password hash from the user's master password. Can
* be used for local authentication or for server authentication depending * be used for local authentication or for server authentication depending
* on the hashPurpose provided. * on the hashPurpose provided.
* @throws Error when password is null or key is null and no active user or active user have no master key
* @param password The user's master password * @param password The user's master password
* @param key The user's master key or active's user master key. * @param key The user's master key or active's user master key.
* @param hashPurpose The iterations to use for the hash * @param hashPurpose The iterations to use for the hash. Defaults to {@link HashPurpose.ServerAuthorization}.
* @throws Error when password is null/undefined or key is null/undefined.
* @returns The user's master password hash * @returns The user's master password hash
*/ */
abstract hashMasterKey( abstract hashMasterKey(
password: string, password: string,
key: MasterKey | null, key: MasterKey,
hashPurpose?: HashPurpose, hashPurpose?: HashPurpose,
): Promise<string>; ): Promise<string>;
/** /**
* Compares the provided master password to the stored password hash. * Compares the provided master password to the stored password hash.
* @param masterPassword The user's master password * @param masterPassword The user's master password
* @param key The user's master key * @param masterKey The user's master key
* @param userId The id of the user to do the operation for. * @param userId The id of the user to do the operation for.
* @returns True if the provided master password matches either the stored * @throws Error when master key is null/undefined.
* key hash or the server key hash * @returns True if the derived master password hash matches the stored
* key hash, false otherwise.
*/ */
abstract compareKeyHash( abstract compareKeyHash(
masterPassword: string, masterPassword: string,

View File

@@ -18,7 +18,7 @@ import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/ke
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { KeySuffixOptions } from "@bitwarden/common/platform/enums"; import { HashPurpose, KeySuffixOptions } from "@bitwarden/common/platform/enums";
import { Encrypted } from "@bitwarden/common/platform/interfaces/encrypted"; import { Encrypted } from "@bitwarden/common/platform/interfaces/encrypted";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
@@ -47,6 +47,7 @@ import { UserKey, MasterKey } from "@bitwarden/common/types/key";
import { KdfConfigService } from "./abstractions/kdf-config.service"; import { KdfConfigService } from "./abstractions/kdf-config.service";
import { UserPrivateKeyDecryptionFailedError } from "./abstractions/key.service"; import { UserPrivateKeyDecryptionFailedError } from "./abstractions/key.service";
import { DefaultKeyService } from "./key.service"; import { DefaultKeyService } from "./key.service";
import { KdfConfig } from "./models/kdf-config";
describe("keyService", () => { describe("keyService", () => {
let keyService: DefaultKeyService; let keyService: DefaultKeyService;
@@ -817,55 +818,160 @@ describe("keyService", () => {
}); });
describe("getOrDeriveMasterKey", () => { describe("getOrDeriveMasterKey", () => {
beforeEach(() => {
masterPasswordService.masterKeySubject.next(null);
});
test.each([null as unknown as UserId, undefined as unknown as UserId])(
"throws when the provided userId is %s",
async (userId) => {
await expect(keyService.getOrDeriveMasterKey("password", userId)).rejects.toThrow(
"User ID is required.",
);
},
);
it("returns the master key if it is already available", async () => { it("returns the master key if it is already available", async () => {
const getMasterKey = jest const masterKey = makeSymmetricCryptoKey(32) as MasterKey;
.spyOn(masterPasswordService, "masterKey$") masterPasswordService.masterKeySubject.next(masterKey);
.mockReturnValue(of("masterKey" as any));
const result = await keyService.getOrDeriveMasterKey("password", mockUserId); const result = await keyService.getOrDeriveMasterKey("password", mockUserId);
expect(getMasterKey).toHaveBeenCalledWith(mockUserId); expect(kdfConfigService.getKdfConfig$).not.toHaveBeenCalledWith(mockUserId);
expect(result).toEqual("masterKey"); expect(result).toEqual(masterKey);
}); });
it("derives the master key if it is not available", async () => { it("throws an error if user's email is not available", async () => {
const getMasterKey = jest accountService.accounts$ = of({});
.spyOn(masterPasswordService, "masterKey$")
.mockReturnValue(of(null as any));
const deriveKeyFromPassword = jest await expect(keyService.getOrDeriveMasterKey("password", mockUserId)).rejects.toThrow(
.spyOn(keyGenerationService, "deriveKeyFromPassword") "No email found for user " + mockUserId,
.mockResolvedValue("mockMasterKey" as any); );
expect(kdfConfigService.getKdfConfig$).not.toHaveBeenCalled();
kdfConfigService.getKdfConfig$.mockReturnValue(of("mockKdfConfig" as any));
const result = await keyService.getOrDeriveMasterKey("password", mockUserId);
expect(getMasterKey).toHaveBeenCalledWith(mockUserId);
expect(deriveKeyFromPassword).toHaveBeenCalledWith("password", "email", "mockKdfConfig");
expect(result).toEqual("mockMasterKey");
});
it("throws an error if no user is found", async () => {
accountService.activeAccountSubject.next(null);
await expect(keyService.getOrDeriveMasterKey("password")).rejects.toThrow("No user found");
}); });
it("throws an error if no kdf config is found", async () => { it("throws an error if no kdf config is found", async () => {
jest.spyOn(masterPasswordService, "masterKey$").mockReturnValue(of(null as any));
kdfConfigService.getKdfConfig$.mockReturnValue(of(null)); kdfConfigService.getKdfConfig$.mockReturnValue(of(null));
await expect(keyService.getOrDeriveMasterKey("password", mockUserId)).rejects.toThrow( await expect(keyService.getOrDeriveMasterKey("password", mockUserId)).rejects.toThrow(
"No kdf found for user", "No kdf found for user",
); );
}); });
it("derives the master key if it is not available", async () => {
keyGenerationService.deriveKeyFromPassword.mockReturnValue("mockMasterKey" as any);
kdfConfigService.getKdfConfig$.mockReturnValue(of("mockKdfConfig" as any));
const result = await keyService.getOrDeriveMasterKey("password", mockUserId);
expect(kdfConfigService.getKdfConfig$).toHaveBeenCalledWith(mockUserId);
expect(keyGenerationService.deriveKeyFromPassword).toHaveBeenCalledWith(
"password",
"email",
"mockKdfConfig",
);
expect(result).toEqual("mockMasterKey");
});
});
describe("makeMasterKey", () => {
const password = "testPassword";
let email = "test@example.com";
const masterKey = makeSymmetricCryptoKey(32) as MasterKey;
const kdfConfig = mock<KdfConfig>();
it("derives a master key from password and email", async () => {
keyGenerationService.deriveKeyFromPassword.mockResolvedValue(masterKey);
const result = await keyService.makeMasterKey(password, email, kdfConfig);
expect(result).toEqual(masterKey);
});
it("trims and lowercases the email for key generation call", async () => {
keyGenerationService.deriveKeyFromPassword.mockResolvedValue(masterKey);
email = "TEST@EXAMPLE.COM";
await keyService.makeMasterKey(password, email, kdfConfig);
expect(keyGenerationService.deriveKeyFromPassword).toHaveBeenCalledWith(
password,
email.trim().toLowerCase(),
kdfConfig,
);
});
it("should log the time taken to derive the master key", async () => {
keyGenerationService.deriveKeyFromPassword.mockResolvedValue(masterKey);
jest.spyOn(Date.prototype, "getTime").mockReturnValueOnce(1000).mockReturnValueOnce(1500);
await keyService.makeMasterKey(password, email, kdfConfig);
expect(logService.info).toHaveBeenCalledWith("[KeyService] Deriving master key took 500ms");
});
});
describe("hashMasterKey", () => {
const password = "testPassword";
const masterKey = makeSymmetricCryptoKey(32) as MasterKey;
test.each([null as unknown as string, undefined as unknown as string])(
"throws when the provided password is %s",
async (password) => {
await expect(keyService.hashMasterKey(password, masterKey)).rejects.toThrow(
"password is required.",
);
},
);
test.each([null as unknown as MasterKey, undefined as unknown as MasterKey])(
"throws when the provided key is %s",
async (key) => {
await expect(keyService.hashMasterKey("password", key)).rejects.toThrow("key is required.");
},
);
it("hashes master key with default iterations when no hashPurpose is provided", async () => {
const mockReturnedHashB64 = "bXlfaGFzaA==";
cryptoFunctionService.pbkdf2.mockResolvedValue(Utils.fromB64ToArray(mockReturnedHashB64));
const result = await keyService.hashMasterKey(password, masterKey);
expect(cryptoFunctionService.pbkdf2).toHaveBeenCalledWith(
masterKey.inner().encryptionKey,
password,
"sha256",
1,
);
expect(result).toBe(mockReturnedHashB64);
});
test.each([
[2, HashPurpose.LocalAuthorization],
[1, HashPurpose.ServerAuthorization],
])(
"hashes master key with %s iterations when hashPurpose is %s",
async (expectedIterations, hashPurpose) => {
const mockReturnedHashB64 = "bXlfaGFzaA==";
cryptoFunctionService.pbkdf2.mockResolvedValue(Utils.fromB64ToArray(mockReturnedHashB64));
const result = await keyService.hashMasterKey(password, masterKey, hashPurpose);
expect(cryptoFunctionService.pbkdf2).toHaveBeenCalledWith(
masterKey.inner().encryptionKey,
password,
"sha256",
expectedIterations,
);
expect(result).toBe(mockReturnedHashB64);
},
);
}); });
describe("compareKeyHash", () => { describe("compareKeyHash", () => {
type TestCase = { type TestCase = {
masterKey: MasterKey; masterKey: MasterKey;
masterPassword: string | null; masterPassword: string;
storedMasterKeyHash: string | null; storedMasterKeyHash: string | null;
mockReturnedHash: string; mockReturnedHash: string;
expectedToMatch: boolean; expectedToMatch: boolean;
@@ -873,26 +979,33 @@ describe("keyService", () => {
const data: TestCase[] = [ const data: TestCase[] = [
{ {
masterKey: makeSymmetricCryptoKey(64), masterKey: makeSymmetricCryptoKey(32),
masterPassword: "my_master_password", masterPassword: "my_master_password",
storedMasterKeyHash: "bXlfaGFzaA==", storedMasterKeyHash: "bXlfaGFzaA==",
mockReturnedHash: "bXlfaGFzaA==", mockReturnedHash: "bXlfaGFzaA==",
expectedToMatch: true, expectedToMatch: true,
}, },
{ {
masterKey: makeSymmetricCryptoKey(64), masterKey: makeSymmetricCryptoKey(32),
masterPassword: null, masterPassword: null as unknown as string,
storedMasterKeyHash: "bXlfaGFzaA==", storedMasterKeyHash: "bXlfaGFzaA==",
mockReturnedHash: "bXlfaGFzaA==", mockReturnedHash: "bXlfaGFzaA==",
expectedToMatch: false, expectedToMatch: false,
}, },
{ {
masterKey: makeSymmetricCryptoKey(64), masterKey: makeSymmetricCryptoKey(32),
masterPassword: null, masterPassword: null as unknown as string,
storedMasterKeyHash: null, storedMasterKeyHash: null,
mockReturnedHash: "bXlfaGFzaA==", mockReturnedHash: "bXlfaGFzaA==",
expectedToMatch: false, expectedToMatch: false,
}, },
{
masterKey: makeSymmetricCryptoKey(32),
masterPassword: "my_master_password",
storedMasterKeyHash: "bXlfaGFzaA==",
mockReturnedHash: "zxccbXlfaGFzaA==",
expectedToMatch: false,
},
]; ];
it.each(data)( it.each(data)(
@@ -907,7 +1020,7 @@ describe("keyService", () => {
masterPasswordService.masterKeyHashSubject.next(storedMasterKeyHash); masterPasswordService.masterKeyHashSubject.next(storedMasterKeyHash);
cryptoFunctionService.pbkdf2 cryptoFunctionService.pbkdf2
.calledWith(masterKey.inner().encryptionKey, masterPassword as string, "sha256", 2) .calledWith(masterKey.inner().encryptionKey, masterPassword, "sha256", 2)
.mockResolvedValue(Utils.fromB64ToArray(mockReturnedHash)); .mockResolvedValue(Utils.fromB64ToArray(mockReturnedHash));
const actualDidMatch = await keyService.compareKeyHash( const actualDidMatch = await keyService.compareKeyHash(
@@ -919,6 +1032,38 @@ describe("keyService", () => {
expect(actualDidMatch).toBe(expectedToMatch); expect(actualDidMatch).toBe(expectedToMatch);
}, },
); );
test.each([null as unknown as MasterKey, undefined as unknown as MasterKey])(
"throws an error if masterKey is %s",
async (masterKey) => {
await expect(
keyService.compareKeyHash("my_master_password", masterKey, mockUserId),
).rejects.toThrow("'masterKey' is required to be non-null.");
},
);
test.each([null as unknown as string, undefined as unknown as string])(
"returns false when masterPassword is %s",
async (masterPassword) => {
const result = await keyService.compareKeyHash(
masterPassword,
makeSymmetricCryptoKey(32),
mockUserId,
);
expect(result).toBe(false);
},
);
it("returns false when storedMasterKeyHash is null", async () => {
masterPasswordService.masterKeyHashSubject.next(null);
const result = await keyService.compareKeyHash(
"my_master_password",
makeSymmetricCryptoKey(32),
mockUserId,
);
expect(result).toBe(false);
});
}); });
describe("userPrivateKey$", () => { describe("userPrivateKey$", () => {

View File

@@ -259,28 +259,28 @@ export class DefaultKeyService implements KeyServiceAbstraction {
} }
} }
// TODO: Move to MasterPasswordService async getOrDeriveMasterKey(password: string, userId: UserId): Promise<MasterKey> {
async getOrDeriveMasterKey(password: string, userId?: UserId) { if (userId == null) {
const [resolvedUserId, email] = await firstValueFrom( throw new Error("User ID is required.");
combineLatest([this.accountService.activeAccount$, this.accountService.accounts$]).pipe( }
map(([activeAccount, accounts]) => {
userId ??= activeAccount?.id; const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
if (userId == null || accounts[userId] == null) {
throw new Error("No user found");
}
return [userId, accounts[userId].email];
}),
),
);
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(resolvedUserId));
if (masterKey != null) { if (masterKey != null) {
return masterKey; return masterKey;
} }
const kdf = await firstValueFrom(this.kdfConfigService.getKdfConfig$(resolvedUserId)); const email = await firstValueFrom(
if (kdf == null) { this.accountService.accounts$.pipe(map((accounts) => accounts[userId]?.email)),
throw new Error("No kdf found for user"); );
if (email == null) {
throw new Error("No email found for user " + userId);
} }
const kdf = await firstValueFrom(this.kdfConfigService.getKdfConfig$(userId));
if (kdf == null) {
throw new Error("No kdf found for user " + userId);
}
return await this.makeMasterKey(password, email, kdf); return await this.makeMasterKey(password, email, kdf);
} }
@@ -289,14 +289,14 @@ export class DefaultKeyService implements KeyServiceAbstraction {
* *
* @remarks * @remarks
* Does not validate the kdf config to ensure it satisfies the minimum requirements for the given kdf type. * Does not validate the kdf config to ensure it satisfies the minimum requirements for the given kdf type.
* TODO: Move to MasterPasswordService
*/ */
async makeMasterKey(password: string, email: string, KdfConfig: KdfConfig): Promise<MasterKey> { async makeMasterKey(password: string, email: string, kdfConfig: KdfConfig): Promise<MasterKey> {
const start = new Date().getTime(); const start = new Date().getTime();
email = email.trim().toLowerCase();
const masterKey = (await this.keyGenerationService.deriveKeyFromPassword( const masterKey = (await this.keyGenerationService.deriveKeyFromPassword(
password, password,
email, email,
KdfConfig, kdfConfig,
)) as MasterKey; )) as MasterKey;
const end = new Date().getTime(); const end = new Date().getTime();
this.logService.info(`[KeyService] Deriving master key took ${end - start}ms`); this.logService.info(`[KeyService] Deriving master key took ${end - start}ms`);
@@ -312,23 +312,16 @@ export class DefaultKeyService implements KeyServiceAbstraction {
return await this.buildProtectedSymmetricKey(masterKey, userKey); return await this.buildProtectedSymmetricKey(masterKey, userKey);
} }
// TODO: move to MasterPasswordService
async hashMasterKey( async hashMasterKey(
password: string, password: string,
key: MasterKey | null, key: MasterKey,
hashPurpose?: HashPurpose, hashPurpose?: HashPurpose,
): Promise<string> { ): Promise<string> {
if (key == null) { if (password == null) {
const userId = await firstValueFrom(this.stateProvider.activeUserId$); throw new Error("password is required.");
if (userId == null) {
throw new Error("No active user found.");
}
key = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
} }
if (key == null) {
if (password == null || key == null) { throw new Error("key is required.");
throw new Error("Invalid parameters.");
} }
const iterations = hashPurpose === HashPurpose.LocalAuthorization ? 2 : 1; const iterations = hashPurpose === HashPurpose.LocalAuthorization ? 2 : 1;
@@ -341,9 +334,8 @@ export class DefaultKeyService implements KeyServiceAbstraction {
return Utils.fromBufferToB64(hash); return Utils.fromBufferToB64(hash);
} }
// TODO: move to MasterPasswordService
async compareKeyHash( async compareKeyHash(
masterPassword: string | null, masterPassword: string,
masterKey: MasterKey, masterKey: MasterKey,
userId: UserId, userId: UserId,
): Promise<boolean> { ): Promise<boolean> {