1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 08:13:42 +00:00

[PM-13673] Require UserId In CompareHash Method (#11568)

* Require UserId In CompareHash Method

* Throw on null-ish 'masterKey'

* Update Test
This commit is contained in:
Justin Baur
2024-11-04 15:11:59 -05:00
committed by GitHub
parent 008e928d0a
commit f41365ce48
6 changed files with 117 additions and 33 deletions

View File

@@ -204,14 +204,18 @@ export abstract class KeyService {
hashPurpose?: HashPurpose,
): Promise<string>;
/**
* Compares the provided master password to the stored password hash and server password hash.
* Updates the stored hash if outdated.
* Compares the provided master password to the stored password hash.
* @param masterPassword The user's master password
* @param key The user's master key
* @param userId The id of the user to do the operation for.
* @returns True if the provided master password matches either the stored
* key hash or the server key hash
*/
abstract compareAndUpdateKeyHash(masterPassword: string, masterKey: MasterKey): Promise<boolean>;
abstract compareKeyHash(
masterPassword: string,
masterKey: MasterKey,
userId: UserId,
): Promise<boolean>;
/**
* Stores the encrypted organization keys and clears any decrypted
* organization keys currently in memory

View File

@@ -733,4 +733,63 @@ describe("keyService", () => {
});
});
});
describe("compareKeyHash", () => {
type TestCase = {
masterKey: MasterKey;
masterPassword: string | null;
storedMasterKeyHash: string;
mockReturnedHash: string;
expectedToMatch: boolean;
};
const data: TestCase[] = [
{
masterKey: makeSymmetricCryptoKey(64),
masterPassword: "my_master_password",
storedMasterKeyHash: "bXlfaGFzaA==",
mockReturnedHash: "bXlfaGFzaA==",
expectedToMatch: true,
},
{
masterKey: makeSymmetricCryptoKey(64),
masterPassword: null,
storedMasterKeyHash: "bXlfaGFzaA==",
mockReturnedHash: "bXlfaGFzaA==",
expectedToMatch: false,
},
{
masterKey: makeSymmetricCryptoKey(64),
masterPassword: null,
storedMasterKeyHash: null,
mockReturnedHash: "bXlfaGFzaA==",
expectedToMatch: false,
},
];
it.each(data)(
"returns expected match value when calculated hash equals stored hash",
async ({
masterKey,
masterPassword,
storedMasterKeyHash,
mockReturnedHash,
expectedToMatch,
}) => {
masterPasswordService.masterKeyHashSubject.next(storedMasterKeyHash);
cryptoFunctionService.pbkdf2
.calledWith(masterKey.key, masterPassword, "sha256", 2)
.mockResolvedValue(Utils.fromB64ToArray(mockReturnedHash));
const actualDidMatch = await keyService.compareKeyHash(
masterPassword,
masterKey,
mockUserId,
);
expect(actualDidMatch).toBe(expectedToMatch);
},
);
});
});

View File

@@ -319,34 +319,43 @@ export class DefaultKeyService implements KeyServiceAbstraction {
}
// TODO: move to MasterPasswordService
async compareAndUpdateKeyHash(masterPassword: string, masterKey: MasterKey): Promise<boolean> {
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
async compareKeyHash(
masterPassword: string,
masterKey: MasterKey,
userId: UserId,
): Promise<boolean> {
if (masterKey == null) {
throw new Error("'masterKey' is required to be non-null.");
}
if (masterPassword == null) {
// If they don't give us a master password, we can't hash it, and therefore
// it will never match what we have stored.
return false;
}
// Retrieve the current password hash
const storedPasswordHash = await firstValueFrom(
this.masterPasswordService.masterKeyHash$(userId),
);
if (masterPassword != null && storedPasswordHash != null) {
const localKeyHash = await this.hashMasterKey(
masterPassword,
masterKey,
HashPurpose.LocalAuthorization,
);
if (localKeyHash != null && storedPasswordHash === localKeyHash) {
return true;
}
// TODO: remove serverKeyHash check in 1-2 releases after everyone's keyHash has been updated
const serverKeyHash = await this.hashMasterKey(
masterPassword,
masterKey,
HashPurpose.ServerAuthorization,
);
if (serverKeyHash != null && storedPasswordHash === serverKeyHash) {
await this.masterPasswordService.setMasterKeyHash(localKeyHash, userId);
return true;
}
if (storedPasswordHash == null) {
return false;
}
return false;
// Hash the key for local use
const localKeyHash = await this.hashMasterKey(
masterPassword,
masterKey,
HashPurpose.LocalAuthorization,
);
// Check if the stored hash is already equal to the hash we create locally
if (localKeyHash == null || storedPasswordHash !== localKeyHash) {
return false;
}
return true;
}
async setOrgKeys(