mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 08:13:42 +00:00
[PM-5362]Create MP Service for state provider migration (#7623)
* create mp and kdf service * update mp service interface to not rely on active user * rename observable methods * update crypto service with new MP service * add master password service to login strategies - make fake service for easier testing - fix crypto service tests * update auth service and finish strategies * auth request refactors * more service refactors and constructor updates * setMasterKey refactors * remove master key methods from crypto service * remove master key and hash from state service * missed fixes * create migrations and fix references * fix master key imports * default force set password reason to none * add password reset reason observable factory to service * remove kdf changes and migrate only disk data * update migration number * fix sync service deps * use disk for force set password state * fix desktop migration * fix sso test * fix tests * fix more tests * fix even more tests * fix even more tests * fix cli * remove kdf service abstraction * add missing deps for browser * fix merge conflicts * clear reset password reason on lock or logout * fix tests * fix other tests * add jsdocs to abstraction * use state provider in crypto service * inverse master password service factory * add clearOn to master password service * add parameter validation to master password service * add component level userId * add missed userId * migrate key hash * fix login strategy service * delete crypto master key from account * migrate master key encrypted user key * rename key hash to master key hash * use mp service for getMasterKeyEncryptedUserKey * fix tests
This commit is contained in:
@@ -6,6 +6,7 @@ import { ProfileOrganizationResponse } from "../../admin-console/models/response
|
||||
import { ProfileProviderOrganizationResponse } from "../../admin-console/models/response/profile-provider-organization.response";
|
||||
import { ProfileProviderResponse } from "../../admin-console/models/response/profile-provider.response";
|
||||
import { AccountService } from "../../auth/abstractions/account.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "../../auth/abstractions/master-password.service.abstraction";
|
||||
import { AuthenticationStatus } from "../../auth/enums/authentication-status";
|
||||
import { KdfConfig } from "../../auth/models/domain/kdf-config";
|
||||
import { Utils } from "../../platform/misc/utils";
|
||||
@@ -82,6 +83,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
readonly everHadUserKey$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
protected keyGenerationService: KeyGenerationService,
|
||||
protected cryptoFunctionService: CryptoFunctionService,
|
||||
protected encryptService: EncryptService,
|
||||
@@ -181,12 +183,16 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
|
||||
async isLegacyUser(masterKey?: MasterKey, userId?: UserId): Promise<boolean> {
|
||||
return await this.validateUserKey(
|
||||
(masterKey ?? (await this.getMasterKey(userId))) as unknown as UserKey,
|
||||
);
|
||||
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
masterKey ??= await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
||||
|
||||
return await this.validateUserKey(masterKey as unknown as UserKey);
|
||||
}
|
||||
|
||||
// TODO: legacy support for user key is no longer needed since we require users to migrate on login
|
||||
async getUserKeyWithLegacySupport(userId?: UserId): Promise<UserKey> {
|
||||
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
|
||||
const userKey = await this.getUserKey(userId);
|
||||
if (userKey) {
|
||||
return userKey;
|
||||
@@ -194,7 +200,8 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
|
||||
// Legacy support: encryption used to be done with the master key (derived from master password).
|
||||
// Users who have not migrated will have a null user key and must use the master key instead.
|
||||
return (await this.getMasterKey(userId)) as unknown as UserKey;
|
||||
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
||||
return masterKey as unknown as UserKey;
|
||||
}
|
||||
|
||||
async getUserKeyFromStorage(keySuffix: KeySuffixOptions, userId?: UserId): Promise<UserKey> {
|
||||
@@ -233,7 +240,10 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
|
||||
async makeUserKey(masterKey: MasterKey): Promise<[UserKey, EncString]> {
|
||||
masterKey ||= await this.getMasterKey();
|
||||
if (!masterKey) {
|
||||
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
||||
}
|
||||
if (masterKey == null) {
|
||||
throw new Error("No Master Key found.");
|
||||
}
|
||||
@@ -271,28 +281,16 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
|
||||
async setMasterKeyEncryptedUserKey(userKeyMasterKey: string, userId?: UserId): Promise<void> {
|
||||
await this.stateService.setMasterKeyEncryptedUserKey(userKeyMasterKey, { userId: userId });
|
||||
}
|
||||
|
||||
async setMasterKey(key: MasterKey, userId?: UserId): Promise<void> {
|
||||
await this.stateService.setMasterKey(key, { userId: userId });
|
||||
}
|
||||
|
||||
async getMasterKey(userId?: UserId): Promise<MasterKey> {
|
||||
let masterKey = await this.stateService.getMasterKey({ userId: userId });
|
||||
if (!masterKey) {
|
||||
masterKey = (await this.stateService.getCryptoMasterKey({ userId: userId })) as MasterKey;
|
||||
// if master key was null/undefined and getCryptoMasterKey also returned null/undefined,
|
||||
// don't set master key as it is unnecessary
|
||||
if (masterKey) {
|
||||
await this.setMasterKey(masterKey, userId);
|
||||
}
|
||||
}
|
||||
return masterKey;
|
||||
await this.masterPasswordService.setMasterKeyEncryptedUserKey(
|
||||
new EncString(userKeyMasterKey),
|
||||
userId,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Move to MasterPasswordService
|
||||
async getOrDeriveMasterKey(password: string, userId?: UserId) {
|
||||
let masterKey = await this.getMasterKey(userId);
|
||||
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
let masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
||||
return (masterKey ||= await this.makeMasterKey(
|
||||
password,
|
||||
await this.stateService.getEmail({ userId: userId }),
|
||||
@@ -306,6 +304,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
*
|
||||
* @remarks
|
||||
* 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,
|
||||
@@ -321,10 +320,6 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
)) as MasterKey;
|
||||
}
|
||||
|
||||
async clearMasterKey(userId?: UserId): Promise<void> {
|
||||
await this.stateService.setMasterKey(null, { userId: userId });
|
||||
}
|
||||
|
||||
async encryptUserKeyWithMasterKey(
|
||||
masterKey: MasterKey,
|
||||
userKey?: UserKey,
|
||||
@@ -333,32 +328,31 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return await this.buildProtectedSymmetricKey(masterKey, userKey.key);
|
||||
}
|
||||
|
||||
// TODO: move to master password service
|
||||
async decryptUserKeyWithMasterKey(
|
||||
masterKey: MasterKey,
|
||||
userKey?: EncString,
|
||||
userId?: UserId,
|
||||
): Promise<UserKey> {
|
||||
masterKey ||= await this.getMasterKey(userId);
|
||||
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
masterKey ??= await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
||||
if (masterKey == null) {
|
||||
throw new Error("No master key found.");
|
||||
}
|
||||
|
||||
if (!userKey) {
|
||||
let masterKeyEncryptedUserKey = await this.stateService.getMasterKeyEncryptedUserKey({
|
||||
userId: userId,
|
||||
});
|
||||
if (userKey == null) {
|
||||
let userKey = await this.masterPasswordService.getMasterKeyEncryptedUserKey(userId);
|
||||
|
||||
// Try one more way to get the user key if it still wasn't found.
|
||||
if (masterKeyEncryptedUserKey == null) {
|
||||
masterKeyEncryptedUserKey = await this.stateService.getEncryptedCryptoSymmetricKey({
|
||||
if (userKey == null) {
|
||||
const deprecatedKey = await this.stateService.getEncryptedCryptoSymmetricKey({
|
||||
userId: userId,
|
||||
});
|
||||
if (deprecatedKey == null) {
|
||||
throw new Error("No encrypted user key found.");
|
||||
}
|
||||
userKey = new EncString(deprecatedKey);
|
||||
}
|
||||
|
||||
if (masterKeyEncryptedUserKey == null) {
|
||||
throw new Error("No encrypted user key found.");
|
||||
}
|
||||
userKey = new EncString(masterKeyEncryptedUserKey);
|
||||
}
|
||||
|
||||
let decUserKey: Uint8Array;
|
||||
@@ -377,12 +371,16 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return new SymmetricCryptoKey(decUserKey) as UserKey;
|
||||
}
|
||||
|
||||
// TODO: move to MasterPasswordService
|
||||
async hashMasterKey(
|
||||
password: string,
|
||||
key: MasterKey,
|
||||
hashPurpose?: HashPurpose,
|
||||
): Promise<string> {
|
||||
key ||= await this.getMasterKey();
|
||||
if (!key) {
|
||||
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
key = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
||||
}
|
||||
|
||||
if (password == null || key == null) {
|
||||
throw new Error("Invalid parameters.");
|
||||
@@ -393,20 +391,12 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return Utils.fromBufferToB64(hash);
|
||||
}
|
||||
|
||||
async setMasterKeyHash(keyHash: string): Promise<void> {
|
||||
await this.stateService.setKeyHash(keyHash);
|
||||
}
|
||||
|
||||
async getMasterKeyHash(): Promise<string> {
|
||||
return await this.stateService.getKeyHash();
|
||||
}
|
||||
|
||||
async clearMasterKeyHash(userId?: UserId): Promise<void> {
|
||||
return await this.stateService.setKeyHash(null, { userId: userId });
|
||||
}
|
||||
|
||||
// TODO: move to MasterPasswordService
|
||||
async compareAndUpdateKeyHash(masterPassword: string, masterKey: MasterKey): Promise<boolean> {
|
||||
const storedPasswordHash = await this.getMasterKeyHash();
|
||||
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
const storedPasswordHash = await firstValueFrom(
|
||||
this.masterPasswordService.masterKeyHash$(userId),
|
||||
);
|
||||
if (masterPassword != null && storedPasswordHash != null) {
|
||||
const localKeyHash = await this.hashMasterKey(
|
||||
masterPassword,
|
||||
@@ -424,7 +414,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
HashPurpose.ServerAuthorization,
|
||||
);
|
||||
if (serverKeyHash != null && storedPasswordHash === serverKeyHash) {
|
||||
await this.setMasterKeyHash(localKeyHash);
|
||||
await this.masterPasswordService.setMasterKeyHash(localKeyHash, userId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -481,7 +471,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
|
||||
async clearOrgKeys(memoryOnly?: boolean, userId?: UserId): Promise<void> {
|
||||
const activeUserId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
const userIdIsActive = userId == null || userId === activeUserId;
|
||||
|
||||
if (!memoryOnly) {
|
||||
@@ -527,7 +517,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
|
||||
async clearProviderKeys(memoryOnly?: boolean, userId?: UserId): Promise<void> {
|
||||
const activeUserId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
const userIdIsActive = userId == null || userId === activeUserId;
|
||||
|
||||
if (!memoryOnly) {
|
||||
@@ -598,7 +588,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
|
||||
async clearKeyPair(memoryOnly?: boolean, userId?: UserId): Promise<void[]> {
|
||||
const activeUserId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
const userIdIsActive = userId == null || userId === activeUserId;
|
||||
|
||||
if (!memoryOnly) {
|
||||
@@ -681,8 +671,10 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
|
||||
async clearKeys(userId?: UserId): Promise<any> {
|
||||
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
await this.masterPasswordService.setMasterKeyHash(null, userId);
|
||||
|
||||
await this.clearUserKey(true, userId);
|
||||
await this.clearMasterKeyHash(userId);
|
||||
await this.clearOrgKeys(false, userId);
|
||||
await this.clearProviderKeys(false, userId);
|
||||
await this.clearKeyPair(false, userId);
|
||||
@@ -1037,7 +1029,8 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
if (await this.isLegacyUser(masterKey, userId)) {
|
||||
// Legacy users don't have a user key, so no need to migrate.
|
||||
// Instead, set the master key for additional isLegacyUser checks that will log the user out.
|
||||
await this.setMasterKey(masterKey, userId);
|
||||
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
await this.masterPasswordService.setMasterKey(masterKey, userId);
|
||||
return;
|
||||
}
|
||||
const encryptedUserKey = await this.stateService.getEncryptedCryptoSymmetricKey({
|
||||
|
||||
Reference in New Issue
Block a user