1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-20 02:03:39 +00:00

Revert "[PM-5362]Create MP Service for state provider migration (#7623)" (#8617)

This reverts commit b1abfb0a5c.
This commit is contained in:
Jake Fink
2024-04-04 12:17:09 -04:00
committed by GitHub
parent e2e593c0fe
commit 775c8a1bbe
79 changed files with 498 additions and 1340 deletions

View File

@@ -5,7 +5,6 @@ import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-a
import { FakeActiveUserState, FakeSingleUserState } from "../../../spec/fake-state";
import { FakeStateProvider } from "../../../spec/fake-state-provider";
import { AuthenticationStatus } from "../../auth/enums/authentication-status";
import { FakeMasterPasswordService } from "../../auth/services/master-password/fake-master-password.service";
import { CsprngArray } from "../../types/csprng";
import { UserId } from "../../types/guid";
import { UserKey, MasterKey, PinKey } from "../../types/key";
@@ -41,15 +40,12 @@ describe("cryptoService", () => {
const mockUserId = Utils.newGuid() as UserId;
let accountService: FakeAccountService;
let masterPasswordService: FakeMasterPasswordService;
beforeEach(() => {
accountService = mockAccountServiceWith(mockUserId);
masterPasswordService = new FakeMasterPasswordService();
stateProvider = new FakeStateProvider(accountService);
cryptoService = new CryptoService(
masterPasswordService,
keyGenerationService,
cryptoFunctionService,
encryptService,
@@ -161,14 +157,14 @@ describe("cryptoService", () => {
describe("getUserKeyWithLegacySupport", () => {
let mockUserKey: UserKey;
let mockMasterKey: MasterKey;
let getMasterKey: jest.SpyInstance;
let stateSvcGetMasterKey: jest.SpyInstance;
beforeEach(() => {
const mockRandomBytes = new Uint8Array(64) as CsprngArray;
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
mockMasterKey = new SymmetricCryptoKey(new Uint8Array(64) as CsprngArray) as MasterKey;
getMasterKey = jest.spyOn(masterPasswordService, "masterKey$");
stateSvcGetMasterKey = jest.spyOn(stateService, "getMasterKey");
});
it("returns the User Key if available", async () => {
@@ -178,17 +174,17 @@ describe("cryptoService", () => {
const userKey = await cryptoService.getUserKeyWithLegacySupport(mockUserId);
expect(getKeySpy).toHaveBeenCalledWith(mockUserId);
expect(getMasterKey).not.toHaveBeenCalled();
expect(stateSvcGetMasterKey).not.toHaveBeenCalled();
expect(userKey).toEqual(mockUserKey);
});
it("returns the user's master key when User Key is not available", async () => {
masterPasswordService.masterKeySubject.next(mockMasterKey);
stateSvcGetMasterKey.mockResolvedValue(mockMasterKey);
const userKey = await cryptoService.getUserKeyWithLegacySupport(mockUserId);
expect(getMasterKey).toHaveBeenCalledWith(mockUserId);
expect(stateSvcGetMasterKey).toHaveBeenCalledWith({ userId: mockUserId });
expect(userKey).toEqual(mockMasterKey);
});
});

View File

@@ -6,7 +6,6 @@ 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";
@@ -83,7 +82,6 @@ export class CryptoService implements CryptoServiceAbstraction {
readonly everHadUserKey$: Observable<boolean>;
constructor(
protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
protected keyGenerationService: KeyGenerationService,
protected cryptoFunctionService: CryptoFunctionService,
protected encryptService: EncryptService,
@@ -183,16 +181,12 @@ export class CryptoService implements CryptoServiceAbstraction {
}
async isLegacyUser(masterKey?: MasterKey, userId?: UserId): Promise<boolean> {
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
masterKey ??= await firstValueFrom(this.masterPasswordService.masterKey$(userId));
return await this.validateUserKey(masterKey as unknown as UserKey);
return await this.validateUserKey(
(masterKey ?? (await this.getMasterKey(userId))) 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;
@@ -200,8 +194,7 @@ 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.
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
return masterKey as unknown as UserKey;
return (await this.getMasterKey(userId)) as unknown as UserKey;
}
async getUserKeyFromStorage(keySuffix: KeySuffixOptions, userId?: UserId): Promise<UserKey> {
@@ -240,10 +233,7 @@ export class CryptoService implements CryptoServiceAbstraction {
}
async makeUserKey(masterKey: MasterKey): Promise<[UserKey, EncString]> {
if (!masterKey) {
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
}
masterKey ||= await this.getMasterKey();
if (masterKey == null) {
throw new Error("No Master Key found.");
}
@@ -281,16 +271,28 @@ export class CryptoService implements CryptoServiceAbstraction {
}
async setMasterKeyEncryptedUserKey(userKeyMasterKey: string, userId?: UserId): Promise<void> {
await this.masterPasswordService.setMasterKeyEncryptedUserKey(
new EncString(userKeyMasterKey),
userId,
);
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;
}
// TODO: Move to MasterPasswordService
async getOrDeriveMasterKey(password: string, userId?: UserId) {
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
let masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
let masterKey = await this.getMasterKey(userId);
return (masterKey ||= await this.makeMasterKey(
password,
await this.stateService.getEmail({ userId: userId }),
@@ -304,7 +306,6 @@ 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,
@@ -320,6 +321,10 @@ 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,
@@ -328,31 +333,32 @@ 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> {
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
masterKey ??= await firstValueFrom(this.masterPasswordService.masterKey$(userId));
masterKey ||= await this.getMasterKey(userId);
if (masterKey == null) {
throw new Error("No master key found.");
}
if (userKey == null) {
let userKey = await this.masterPasswordService.getMasterKeyEncryptedUserKey(userId);
if (!userKey) {
let masterKeyEncryptedUserKey = await this.stateService.getMasterKeyEncryptedUserKey({
userId: userId,
});
// Try one more way to get the user key if it still wasn't found.
if (userKey == null) {
const deprecatedKey = await this.stateService.getEncryptedCryptoSymmetricKey({
if (masterKeyEncryptedUserKey == null) {
masterKeyEncryptedUserKey = 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;
@@ -371,16 +377,12 @@ 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> {
if (!key) {
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
key = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
}
key ||= await this.getMasterKey();
if (password == null || key == null) {
throw new Error("Invalid parameters.");
@@ -391,12 +393,20 @@ export class CryptoService implements CryptoServiceAbstraction {
return Utils.fromBufferToB64(hash);
}
// TODO: move to MasterPasswordService
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 });
}
async compareAndUpdateKeyHash(masterPassword: string, masterKey: MasterKey): Promise<boolean> {
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
const storedPasswordHash = await firstValueFrom(
this.masterPasswordService.masterKeyHash$(userId),
);
const storedPasswordHash = await this.getMasterKeyHash();
if (masterPassword != null && storedPasswordHash != null) {
const localKeyHash = await this.hashMasterKey(
masterPassword,
@@ -414,7 +424,7 @@ export class CryptoService implements CryptoServiceAbstraction {
HashPurpose.ServerAuthorization,
);
if (serverKeyHash != null && storedPasswordHash === serverKeyHash) {
await this.masterPasswordService.setMasterKeyHash(localKeyHash, userId);
await this.setMasterKeyHash(localKeyHash);
return true;
}
}
@@ -471,7 +481,7 @@ export class CryptoService implements CryptoServiceAbstraction {
}
async clearOrgKeys(memoryOnly?: boolean, userId?: UserId): Promise<void> {
const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$);
const activeUserId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
const userIdIsActive = userId == null || userId === activeUserId;
if (!memoryOnly) {
@@ -517,7 +527,7 @@ export class CryptoService implements CryptoServiceAbstraction {
}
async clearProviderKeys(memoryOnly?: boolean, userId?: UserId): Promise<void> {
const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$);
const activeUserId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
const userIdIsActive = userId == null || userId === activeUserId;
if (!memoryOnly) {
@@ -588,7 +598,7 @@ export class CryptoService implements CryptoServiceAbstraction {
}
async clearKeyPair(memoryOnly?: boolean, userId?: UserId): Promise<void[]> {
const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$);
const activeUserId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
const userIdIsActive = userId == null || userId === activeUserId;
if (!memoryOnly) {
@@ -671,10 +681,8 @@ 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);
@@ -1029,8 +1037,7 @@ 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.
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
await this.masterPasswordService.setMasterKey(masterKey, userId);
await this.setMasterKey(masterKey, userId);
return;
}
const encryptedUserKey = await this.stateService.getEncryptedCryptoSymmetricKey({

View File

@@ -5,12 +5,14 @@ import { AccountService } from "../../auth/abstractions/account.service";
import { TokenService } from "../../auth/abstractions/token.service";
import { AuthenticationStatus } from "../../auth/enums/authentication-status";
import { AdminAuthRequestStorable } from "../../auth/models/domain/admin-auth-req-storable";
import { ForceSetPasswordReason } from "../../auth/models/domain/force-set-password-reason";
import { KdfConfig } from "../../auth/models/domain/kdf-config";
import { BiometricKey } from "../../auth/types/biometric-key";
import { GeneratorOptions } from "../../tools/generator/generator-options";
import { GeneratedPasswordHistory, PasswordGeneratorOptions } from "../../tools/generator/password";
import { UsernameGeneratorOptions } from "../../tools/generator/username";
import { UserId } from "../../types/guid";
import { MasterKey } from "../../types/key";
import { CipherData } from "../../vault/models/data/cipher.data";
import { LocalData } from "../../vault/models/data/local.data";
import { CipherView } from "../../vault/models/view/cipher.view";
@@ -33,6 +35,7 @@ import { EncString } from "../models/domain/enc-string";
import { GlobalState } from "../models/domain/global-state";
import { State } from "../models/domain/state";
import { StorageOptions } from "../models/domain/storage-options";
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
import { MigrationRunner } from "./migration-runner";
@@ -270,6 +273,65 @@ export class StateService<
);
}
/**
* @deprecated Do not save the Master Key. Use the User Symmetric Key instead
*/
async getCryptoMasterKey(options?: StorageOptions): Promise<SymmetricCryptoKey> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions()),
);
return account?.keys?.cryptoMasterKey;
}
/**
* User's master key derived from MP, saved only if we decrypted with MP
*/
async getMasterKey(options?: StorageOptions): Promise<MasterKey> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions()),
);
return account?.keys?.masterKey;
}
/**
* User's master key derived from MP, saved only if we decrypted with MP
*/
async setMasterKey(value: MasterKey, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions()),
);
account.keys.masterKey = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultInMemoryOptions()),
);
}
/**
* The master key encrypted User symmetric key, saved on every auth
* so we can unlock with MP offline
*/
async getMasterKeyEncryptedUserKey(options?: StorageOptions): Promise<string> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.keys.masterKeyEncryptedUserKey;
}
/**
* The master key encrypted User symmetric key, saved on every auth
* so we can unlock with MP offline
*/
async setMasterKeyEncryptedUserKey(value: string, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
account.keys.masterKeyEncryptedUserKey = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
}
/**
* user key when using the "never" option of vault timeout
*/
@@ -761,6 +823,30 @@ export class StateService<
);
}
async getForceSetPasswordReason(options?: StorageOptions): Promise<ForceSetPasswordReason> {
return (
(
await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()),
)
)?.profile?.forceSetPasswordReason ?? ForceSetPasswordReason.None
);
}
async setForceSetPasswordReason(
value: ForceSetPasswordReason,
options?: StorageOptions,
): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()),
);
account.profile.forceSetPasswordReason = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()),
);
}
async getIsAuthenticated(options?: StorageOptions): Promise<boolean> {
return (
(await this.tokenService.getAccessToken(options?.userId as UserId)) != null &&
@@ -811,6 +897,23 @@ export class StateService<
);
}
async getKeyHash(options?: StorageOptions): Promise<string> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.profile?.keyHash;
}
async setKeyHash(value: string, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
account.profile.keyHash = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskOptions()),
);
}
async getLastActive(options?: StorageOptions): Promise<number> {
options = this.reconcileOptions(options, await this.defaultOnDiskOptions());