mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 00:03:56 +00:00
* 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 * fix user key decryption logic * add clear methods to mp service * fix circular dep and encryption issue * fix test * remove extra account service call * use EncString in state provider * fix tests * return to using encrypted string for serialization
This commit is contained in:
@@ -105,18 +105,6 @@ export abstract class CryptoService {
|
||||
* @param userId The desired user
|
||||
*/
|
||||
abstract setMasterKeyEncryptedUserKey(UserKeyMasterKey: string, userId?: string): Promise<void>;
|
||||
/**
|
||||
* Sets the user's master key
|
||||
* @param key The user's master key to set
|
||||
* @param userId The desired user
|
||||
*/
|
||||
abstract setMasterKey(key: MasterKey, userId?: string): Promise<void>;
|
||||
/**
|
||||
* @param userId The desired user
|
||||
* @returns The user's master key
|
||||
*/
|
||||
abstract getMasterKey(userId?: string): Promise<MasterKey>;
|
||||
|
||||
/**
|
||||
* @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
|
||||
@@ -136,11 +124,6 @@ export abstract class CryptoService {
|
||||
kdf: KdfType,
|
||||
KdfConfig: KdfConfig,
|
||||
): Promise<MasterKey>;
|
||||
/**
|
||||
* Clears the user's master key
|
||||
* @param userId The desired user
|
||||
*/
|
||||
abstract clearMasterKey(userId?: string): Promise<void>;
|
||||
/**
|
||||
* Encrypts the existing (or provided) user key with the
|
||||
* provided master key
|
||||
@@ -178,20 +161,6 @@ export abstract class CryptoService {
|
||||
key: MasterKey,
|
||||
hashPurpose?: HashPurpose,
|
||||
): Promise<string>;
|
||||
/**
|
||||
* Sets the user's master password hash
|
||||
* @param keyHash The user's master password hash to set
|
||||
*/
|
||||
abstract setMasterKeyHash(keyHash: string): Promise<void>;
|
||||
/**
|
||||
* @returns The user's master password hash
|
||||
*/
|
||||
abstract getMasterKeyHash(): Promise<string>;
|
||||
/**
|
||||
* Clears the user's stored master password hash
|
||||
* @param userId The desired user
|
||||
*/
|
||||
abstract clearMasterKeyHash(userId?: string): Promise<void>;
|
||||
/**
|
||||
* Compares the provided master password to the stored password hash and server password hash.
|
||||
* Updates the stored hash if outdated.
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
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";
|
||||
@@ -17,7 +15,6 @@ import { KdfType } from "../enums";
|
||||
import { Account } from "../models/domain/account";
|
||||
import { EncString } from "../models/domain/enc-string";
|
||||
import { StorageOptions } from "../models/domain/storage-options";
|
||||
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
||||
|
||||
/**
|
||||
* Options for customizing the initiation behavior.
|
||||
@@ -48,22 +45,6 @@ export abstract class StateService<T extends Account = Account> {
|
||||
|
||||
getAddEditCipherInfo: (options?: StorageOptions) => Promise<AddEditCipherInfo>;
|
||||
setAddEditCipherInfo: (value: AddEditCipherInfo, options?: StorageOptions) => Promise<void>;
|
||||
/**
|
||||
* Gets the user's master key
|
||||
*/
|
||||
getMasterKey: (options?: StorageOptions) => Promise<MasterKey>;
|
||||
/**
|
||||
* Sets the user's master key
|
||||
*/
|
||||
setMasterKey: (value: MasterKey, options?: StorageOptions) => Promise<void>;
|
||||
/**
|
||||
* Gets the user key encrypted by the master key
|
||||
*/
|
||||
getMasterKeyEncryptedUserKey: (options?: StorageOptions) => Promise<string>;
|
||||
/**
|
||||
* Sets the user key encrypted by the master key
|
||||
*/
|
||||
setMasterKeyEncryptedUserKey: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
/**
|
||||
* Gets the user's auto key
|
||||
*/
|
||||
@@ -108,10 +89,6 @@ export abstract class StateService<T extends Account = Account> {
|
||||
* @deprecated For migration purposes only, use getUserKeyMasterKey instead
|
||||
*/
|
||||
getEncryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise<string>;
|
||||
/**
|
||||
* @deprecated For legacy purposes only, use getMasterKey instead
|
||||
*/
|
||||
getCryptoMasterKey: (options?: StorageOptions) => Promise<SymmetricCryptoKey>;
|
||||
/**
|
||||
* @deprecated For migration purposes only, use getUserKeyAuto instead
|
||||
*/
|
||||
@@ -189,18 +166,11 @@ export abstract class StateService<T extends Account = Account> {
|
||||
setEncryptedPinProtected: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getEverBeenUnlocked: (options?: StorageOptions) => Promise<boolean>;
|
||||
setEverBeenUnlocked: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||
getForceSetPasswordReason: (options?: StorageOptions) => Promise<ForceSetPasswordReason>;
|
||||
setForceSetPasswordReason: (
|
||||
value: ForceSetPasswordReason,
|
||||
options?: StorageOptions,
|
||||
) => Promise<void>;
|
||||
getIsAuthenticated: (options?: StorageOptions) => Promise<boolean>;
|
||||
getKdfConfig: (options?: StorageOptions) => Promise<KdfConfig>;
|
||||
setKdfConfig: (kdfConfig: KdfConfig, options?: StorageOptions) => Promise<void>;
|
||||
getKdfType: (options?: StorageOptions) => Promise<KdfType>;
|
||||
setKdfType: (value: KdfType, options?: StorageOptions) => Promise<void>;
|
||||
getKeyHash: (options?: StorageOptions) => Promise<string>;
|
||||
setKeyHash: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
getLastActive: (options?: StorageOptions) => Promise<number>;
|
||||
setLastActive: (value: number, options?: StorageOptions) => Promise<void>;
|
||||
getLastSync: (options?: StorageOptions) => Promise<string>;
|
||||
|
||||
@@ -2,7 +2,6 @@ import { makeStaticByteArray } from "../../../../spec";
|
||||
import { Utils } from "../../misc/utils";
|
||||
|
||||
import { AccountKeys, EncryptionPair } from "./account";
|
||||
import { SymmetricCryptoKey } from "./symmetric-crypto-key";
|
||||
|
||||
describe("AccountKeys", () => {
|
||||
describe("toJSON", () => {
|
||||
@@ -32,12 +31,6 @@ describe("AccountKeys", () => {
|
||||
expect(keys.publicKey).toEqual(Utils.fromByteStringToArray("hello"));
|
||||
});
|
||||
|
||||
it("should deserialize cryptoMasterKey", () => {
|
||||
const spy = jest.spyOn(SymmetricCryptoKey, "fromJSON");
|
||||
AccountKeys.fromJSON({} as any);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should deserialize privateKey", () => {
|
||||
const spy = jest.spyOn(EncryptionPair, "fromJSON");
|
||||
AccountKeys.fromJSON({
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { AdminAuthRequestStorable } from "../../../auth/models/domain/admin-auth-req-storable";
|
||||
import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason";
|
||||
import { UriMatchStrategySetting } from "../../../models/domain/domain-service";
|
||||
import { GeneratorOptions } from "../../../tools/generator/generator-options";
|
||||
import {
|
||||
@@ -10,7 +9,6 @@ import {
|
||||
} from "../../../tools/generator/password";
|
||||
import { UsernameGeneratorOptions } from "../../../tools/generator/username/username-generation-options";
|
||||
import { DeepJsonify } from "../../../types/deep-jsonify";
|
||||
import { MasterKey } from "../../../types/key";
|
||||
import { CipherData } from "../../../vault/models/data/cipher.data";
|
||||
import { CipherView } from "../../../vault/models/view/cipher.view";
|
||||
import { AddEditCipherInfo } from "../../../vault/types/add-edit-cipher-info";
|
||||
@@ -90,12 +88,8 @@ export class AccountData {
|
||||
}
|
||||
|
||||
export class AccountKeys {
|
||||
masterKey?: MasterKey;
|
||||
masterKeyEncryptedUserKey?: string;
|
||||
publicKey?: Uint8Array;
|
||||
|
||||
/** @deprecated July 2023, left for migration purposes*/
|
||||
cryptoMasterKey?: SymmetricCryptoKey;
|
||||
/** @deprecated July 2023, left for migration purposes*/
|
||||
cryptoMasterKeyAuto?: string;
|
||||
/** @deprecated July 2023, left for migration purposes*/
|
||||
@@ -120,8 +114,6 @@ export class AccountKeys {
|
||||
return null;
|
||||
}
|
||||
return Object.assign(new AccountKeys(), obj, {
|
||||
masterKey: SymmetricCryptoKey.fromJSON(obj?.masterKey),
|
||||
cryptoMasterKey: SymmetricCryptoKey.fromJSON(obj?.cryptoMasterKey),
|
||||
cryptoSymmetricKey: EncryptionPair.fromJSON(
|
||||
obj?.cryptoSymmetricKey,
|
||||
SymmetricCryptoKey.fromJSON,
|
||||
@@ -150,10 +142,8 @@ export class AccountProfile {
|
||||
email?: string;
|
||||
emailVerified?: boolean;
|
||||
everBeenUnlocked?: boolean;
|
||||
forceSetPasswordReason?: ForceSetPasswordReason;
|
||||
lastSync?: string;
|
||||
userId?: string;
|
||||
keyHash?: string;
|
||||
kdfIterations?: number;
|
||||
kdfMemory?: number;
|
||||
kdfParallelism?: number;
|
||||
|
||||
@@ -5,6 +5,7 @@ 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,12 +42,15 @@ 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,
|
||||
@@ -158,14 +162,14 @@ describe("cryptoService", () => {
|
||||
describe("getUserKeyWithLegacySupport", () => {
|
||||
let mockUserKey: UserKey;
|
||||
let mockMasterKey: MasterKey;
|
||||
let stateSvcGetMasterKey: jest.SpyInstance;
|
||||
let getMasterKey: 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;
|
||||
|
||||
stateSvcGetMasterKey = jest.spyOn(stateService, "getMasterKey");
|
||||
getMasterKey = jest.spyOn(masterPasswordService, "masterKey$");
|
||||
});
|
||||
|
||||
it("returns the User Key if available", async () => {
|
||||
@@ -175,17 +179,17 @@ describe("cryptoService", () => {
|
||||
const userKey = await cryptoService.getUserKeyWithLegacySupport(mockUserId);
|
||||
|
||||
expect(getKeySpy).toHaveBeenCalledWith(mockUserId);
|
||||
expect(stateSvcGetMasterKey).not.toHaveBeenCalled();
|
||||
expect(getMasterKey).not.toHaveBeenCalled();
|
||||
|
||||
expect(userKey).toEqual(mockUserKey);
|
||||
});
|
||||
|
||||
it("returns the user's master key when User Key is not available", async () => {
|
||||
stateSvcGetMasterKey.mockResolvedValue(mockMasterKey);
|
||||
masterPasswordService.masterKeySubject.next(mockMasterKey);
|
||||
|
||||
const userKey = await cryptoService.getUserKeyWithLegacySupport(mockUserId);
|
||||
|
||||
expect(stateSvcGetMasterKey).toHaveBeenCalledWith({ userId: mockUserId });
|
||||
expect(getMasterKey).toHaveBeenCalledWith(mockUserId);
|
||||
expect(userKey).toEqual(mockMasterKey);
|
||||
});
|
||||
});
|
||||
@@ -340,9 +344,7 @@ describe("cryptoService", () => {
|
||||
describe("clearKeys", () => {
|
||||
it("resolves active user id when called with no user id", async () => {
|
||||
let callCount = 0;
|
||||
accountService.activeAccount$ = accountService.activeAccountSubject.pipe(
|
||||
tap(() => callCount++),
|
||||
);
|
||||
stateProvider.activeUserId$ = stateProvider.activeUserId$.pipe(tap(() => callCount++));
|
||||
|
||||
await cryptoService.clearKeys(null);
|
||||
expect(callCount).toBe(1);
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
@@ -277,28 +287,17 @@ 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;
|
||||
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
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 }),
|
||||
@@ -312,6 +311,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,
|
||||
@@ -327,10 +327,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,
|
||||
@@ -339,32 +335,28 @@ 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$);
|
||||
userKey ??= await this.masterPasswordService.getMasterKeyEncryptedUserKey(userId);
|
||||
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({
|
||||
// Try one more way to get the user key if it still wasn't found.
|
||||
if (userKey == null) {
|
||||
const deprecatedKey = await this.stateService.getEncryptedCryptoSymmetricKey({
|
||||
userId: userId,
|
||||
});
|
||||
|
||||
// Try one more way to get the user key if it still wasn't found.
|
||||
if (masterKeyEncryptedUserKey == null) {
|
||||
masterKeyEncryptedUserKey = await this.stateService.getEncryptedCryptoSymmetricKey({
|
||||
userId: userId,
|
||||
});
|
||||
}
|
||||
|
||||
if (masterKeyEncryptedUserKey == null) {
|
||||
if (deprecatedKey == null) {
|
||||
throw new Error("No encrypted user key found.");
|
||||
}
|
||||
userKey = new EncString(masterKeyEncryptedUserKey);
|
||||
userKey = new EncString(deprecatedKey);
|
||||
}
|
||||
|
||||
let decUserKey: Uint8Array;
|
||||
@@ -383,12 +375,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.");
|
||||
@@ -399,20 +395,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,
|
||||
@@ -430,7 +418,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;
|
||||
}
|
||||
}
|
||||
@@ -652,14 +640,14 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
|
||||
async clearKeys(userId?: UserId): Promise<any> {
|
||||
userId ||= (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
|
||||
if (userId == null) {
|
||||
throw new Error("Cannot clear keys, no user Id resolved.");
|
||||
}
|
||||
|
||||
await this.masterPasswordService.clearMasterKeyHash(userId);
|
||||
await this.clearUserKey(userId);
|
||||
await this.clearMasterKeyHash(userId);
|
||||
await this.clearOrgKeys(userId);
|
||||
await this.clearProviderKeys(userId);
|
||||
await this.clearKeyPair(userId);
|
||||
@@ -1014,7 +1002,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({
|
||||
|
||||
@@ -5,14 +5,12 @@ 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";
|
||||
@@ -35,7 +33,6 @@ 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";
|
||||
|
||||
@@ -273,65 +270,6 @@ 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
|
||||
*/
|
||||
@@ -823,30 +761,6 @@ 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 &&
|
||||
@@ -897,23 +811,6 @@ 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());
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ export const BILLING_DISK = new StateDefinition("billing", "disk");
|
||||
|
||||
export const KEY_CONNECTOR_DISK = new StateDefinition("keyConnector", "disk");
|
||||
export const ACCOUNT_MEMORY = new StateDefinition("account", "memory");
|
||||
export const MASTER_PASSWORD_MEMORY = new StateDefinition("masterPassword", "memory");
|
||||
export const MASTER_PASSWORD_DISK = new StateDefinition("masterPassword", "disk");
|
||||
export const AVATAR_DISK = new StateDefinition("avatar", "disk", { web: "disk-local" });
|
||||
export const ROUTER_DISK = new StateDefinition("router", "disk");
|
||||
export const LOGIN_EMAIL_DISK = new StateDefinition("loginEmail", "disk", {
|
||||
|
||||
Reference in New Issue
Block a user