mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 16:23:44 +00:00
Add state for everHadUserKey (#7208)
* Migrate ever had user key * Add DI for state providers * Add state for everHadUserKey * Use ever had user key migrator Co-authored-by: SmithThe4th <gsmithwalter@gmail.com> Co-authored-by: Carlos Gonçalves <LRNcardozoWDF@users.noreply.github.com> Co-authored-by: Jason Ng <Jcory.ng@gmail.com> * Fix test from merge * Prefer stored observables to getters getters create a new observable every time they're called, whereas one set in the constructor is created only once. * Fix another merge issue * Fix cli background build --------- Co-authored-by: SmithThe4th <gsmithwalter@gmail.com> Co-authored-by: Carlos Gonçalves <LRNcardozoWDF@users.noreply.github.com> Co-authored-by: Jason Ng <Jcory.ng@gmail.com>
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
import * as bigInt from "big-integer";
|
||||
import { firstValueFrom, map } from "rxjs";
|
||||
|
||||
import { EncryptedOrganizationKeyData } from "../../admin-console/models/data/encrypted-organization-key.data";
|
||||
import { BaseEncryptedOrganizationKey } from "../../admin-console/models/domain/encrypted-organization-key";
|
||||
import { ProfileOrganizationResponse } from "../../admin-console/models/response/profile-organization.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 { KdfConfig } from "../../auth/models/domain/kdf-config";
|
||||
import { Utils } from "../../platform/misc/utils";
|
||||
import { UserId } from "../../types/guid";
|
||||
import { CryptoFunctionService } from "../abstractions/crypto-function.service";
|
||||
import { CryptoService as CryptoServiceAbstraction } from "../abstractions/crypto.service";
|
||||
import { EncryptService } from "../abstractions/encrypt.service";
|
||||
@@ -36,34 +39,48 @@ import {
|
||||
SymmetricCryptoKey,
|
||||
UserKey,
|
||||
} from "../models/domain/symmetric-crypto-key";
|
||||
import { ActiveUserState, CRYPTO_DISK, KeyDefinition, StateProvider } from "../state";
|
||||
|
||||
export const USER_EVER_HAD_USER_KEY = new KeyDefinition<boolean>(CRYPTO_DISK, "everHadUserKey", {
|
||||
deserializer: (obj) => obj,
|
||||
});
|
||||
|
||||
export class CryptoService implements CryptoServiceAbstraction {
|
||||
private activeUserEverHadUserKey: ActiveUserState<boolean>;
|
||||
|
||||
readonly everHadUserKey$;
|
||||
|
||||
constructor(
|
||||
protected cryptoFunctionService: CryptoFunctionService,
|
||||
protected encryptService: EncryptService,
|
||||
protected platformUtilService: PlatformUtilsService,
|
||||
protected logService: LogService,
|
||||
protected stateService: StateService,
|
||||
) {}
|
||||
protected accountService: AccountService,
|
||||
protected stateProvider: StateProvider,
|
||||
) {
|
||||
this.activeUserEverHadUserKey = stateProvider.getActive(USER_EVER_HAD_USER_KEY);
|
||||
|
||||
async setUserKey(key: UserKey, userId?: string): Promise<void> {
|
||||
this.everHadUserKey$ = this.activeUserEverHadUserKey.state$.pipe(map((x) => x ?? false));
|
||||
}
|
||||
|
||||
async setUserKey(key: UserKey, userId?: UserId): Promise<void> {
|
||||
// TODO: make this non-nullable in signature
|
||||
userId ??= (await firstValueFrom(this.accountService.activeAccount$))?.id;
|
||||
if (key != null) {
|
||||
await this.stateService.setEverHadUserKey(true, { userId: userId });
|
||||
// Key should never be null anyway
|
||||
this.stateProvider.getUser(userId, USER_EVER_HAD_USER_KEY).update(() => true);
|
||||
}
|
||||
await this.stateService.setUserKey(key, { userId: userId });
|
||||
await this.storeAdditionalKeys(key, userId);
|
||||
}
|
||||
|
||||
async getEverHadUserKey(userId?: string): Promise<boolean> {
|
||||
return await this.stateService.getEverHadUserKey({ userId: userId });
|
||||
}
|
||||
|
||||
async refreshAdditionalKeys(): Promise<void> {
|
||||
const key = await this.getUserKey();
|
||||
await this.setUserKey(key);
|
||||
}
|
||||
|
||||
async getUserKey(userId?: string): Promise<UserKey> {
|
||||
async getUserKey(userId?: UserId): Promise<UserKey> {
|
||||
let userKey = await this.stateService.getUserKey({ userId: userId });
|
||||
if (userKey) {
|
||||
return userKey;
|
||||
@@ -79,13 +96,13 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
}
|
||||
|
||||
async isLegacyUser(masterKey?: MasterKey, userId?: string): Promise<boolean> {
|
||||
async isLegacyUser(masterKey?: MasterKey, userId?: UserId): Promise<boolean> {
|
||||
return await this.validateUserKey(
|
||||
(masterKey ?? (await this.getMasterKey(userId))) as unknown as UserKey,
|
||||
);
|
||||
}
|
||||
|
||||
async getUserKeyWithLegacySupport(userId?: string): Promise<UserKey> {
|
||||
async getUserKeyWithLegacySupport(userId?: UserId): Promise<UserKey> {
|
||||
const userKey = await this.getUserKey(userId);
|
||||
if (userKey) {
|
||||
return userKey;
|
||||
@@ -96,7 +113,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return (await this.getMasterKey(userId)) as unknown as UserKey;
|
||||
}
|
||||
|
||||
async getUserKeyFromStorage(keySuffix: KeySuffixOptions, userId?: string): Promise<UserKey> {
|
||||
async getUserKeyFromStorage(keySuffix: KeySuffixOptions, userId?: UserId): Promise<UserKey> {
|
||||
const userKey = await this.getKeyFromStorage(keySuffix, userId);
|
||||
if (userKey) {
|
||||
if (!(await this.validateUserKey(userKey))) {
|
||||
@@ -113,11 +130,11 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
);
|
||||
}
|
||||
|
||||
async hasUserKeyInMemory(userId?: string): Promise<boolean> {
|
||||
async hasUserKeyInMemory(userId?: UserId): Promise<boolean> {
|
||||
return (await this.stateService.getUserKey({ userId: userId })) != null;
|
||||
}
|
||||
|
||||
async hasUserKeyStored(keySuffix: KeySuffixOptions, userId?: string): Promise<boolean> {
|
||||
async hasUserKeyStored(keySuffix: KeySuffixOptions, userId?: UserId): Promise<boolean> {
|
||||
return (await this.getKeyFromStorage(keySuffix, userId)) != null;
|
||||
}
|
||||
|
||||
@@ -131,14 +148,14 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return this.buildProtectedSymmetricKey(masterKey, newUserKey);
|
||||
}
|
||||
|
||||
async clearUserKey(clearStoredKeys = true, userId?: string): Promise<void> {
|
||||
async clearUserKey(clearStoredKeys = true, userId?: UserId): Promise<void> {
|
||||
await this.stateService.setUserKey(null, { userId: userId });
|
||||
if (clearStoredKeys) {
|
||||
await this.clearAllStoredUserKeys(userId);
|
||||
}
|
||||
}
|
||||
|
||||
async clearStoredUserKey(keySuffix: KeySuffixOptions, userId?: string): Promise<void> {
|
||||
async clearStoredUserKey(keySuffix: KeySuffixOptions, userId?: UserId): Promise<void> {
|
||||
if (keySuffix === KeySuffixOptions.Auto) {
|
||||
this.stateService.setUserKeyAutoUnlock(null, { userId: userId });
|
||||
this.clearDeprecatedKeys(KeySuffixOptions.Auto, userId);
|
||||
@@ -149,15 +166,15 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
}
|
||||
|
||||
async setMasterKeyEncryptedUserKey(userKeyMasterKey: string, userId?: string): Promise<void> {
|
||||
async setMasterKeyEncryptedUserKey(userKeyMasterKey: string, userId?: UserId): Promise<void> {
|
||||
await this.stateService.setMasterKeyEncryptedUserKey(userKeyMasterKey, { userId: userId });
|
||||
}
|
||||
|
||||
async setMasterKey(key: MasterKey, userId?: string): Promise<void> {
|
||||
async setMasterKey(key: MasterKey, userId?: UserId): Promise<void> {
|
||||
await this.stateService.setMasterKey(key, { userId: userId });
|
||||
}
|
||||
|
||||
async getMasterKey(userId?: string): Promise<MasterKey> {
|
||||
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;
|
||||
@@ -170,7 +187,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return masterKey;
|
||||
}
|
||||
|
||||
async getOrDeriveMasterKey(password: string, userId?: string) {
|
||||
async getOrDeriveMasterKey(password: string, userId?: UserId) {
|
||||
let masterKey = await this.getMasterKey(userId);
|
||||
return (masterKey ||= await this.makeMasterKey(
|
||||
password,
|
||||
@@ -195,7 +212,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return (await this.makeKey(password, email, kdf, KdfConfig)) as MasterKey;
|
||||
}
|
||||
|
||||
async clearMasterKey(userId?: string): Promise<void> {
|
||||
async clearMasterKey(userId?: UserId): Promise<void> {
|
||||
await this.stateService.setMasterKey(null, { userId: userId });
|
||||
}
|
||||
|
||||
@@ -210,7 +227,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
async decryptUserKeyWithMasterKey(
|
||||
masterKey: MasterKey,
|
||||
userKey?: EncString,
|
||||
userId?: string,
|
||||
userId?: UserId,
|
||||
): Promise<UserKey> {
|
||||
masterKey ||= await this.getMasterKey(userId);
|
||||
if (masterKey == null) {
|
||||
@@ -275,7 +292,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return await this.stateService.getKeyHash();
|
||||
}
|
||||
|
||||
async clearMasterKeyHash(userId?: string): Promise<void> {
|
||||
async clearMasterKeyHash(userId?: UserId): Promise<void> {
|
||||
return await this.stateService.setKeyHash(null, { userId: userId });
|
||||
}
|
||||
|
||||
@@ -389,7 +406,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return this.buildProtectedSymmetricKey(key, newSymKey);
|
||||
}
|
||||
|
||||
async clearOrgKeys(memoryOnly?: boolean, userId?: string): Promise<void> {
|
||||
async clearOrgKeys(memoryOnly?: boolean, userId?: UserId): Promise<void> {
|
||||
await this.stateService.setDecryptedOrganizationKeys(null, { userId: userId });
|
||||
if (!memoryOnly) {
|
||||
await this.stateService.setEncryptedOrganizationKeys(null, { userId: userId });
|
||||
@@ -452,7 +469,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return providerKeys;
|
||||
}
|
||||
|
||||
async clearProviderKeys(memoryOnly?: boolean, userId?: string): Promise<void> {
|
||||
async clearProviderKeys(memoryOnly?: boolean, userId?: UserId): Promise<void> {
|
||||
await this.stateService.setDecryptedProviderKeys(null, { userId: userId });
|
||||
if (!memoryOnly) {
|
||||
await this.stateService.setEncryptedProviderKeys(null, { userId: userId });
|
||||
@@ -537,7 +554,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return [publicB64, privateEnc];
|
||||
}
|
||||
|
||||
async clearKeyPair(memoryOnly?: boolean, userId?: string): Promise<void[]> {
|
||||
async clearKeyPair(memoryOnly?: boolean, userId?: UserId): Promise<void[]> {
|
||||
const keysToClear: Promise<void>[] = [
|
||||
this.stateService.setDecryptedPrivateKey(null, { userId: userId }),
|
||||
this.stateService.setPublicKey(null, { userId: userId }),
|
||||
@@ -553,7 +570,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return (await this.stretchKey(pinKey)) as PinKey;
|
||||
}
|
||||
|
||||
async clearPinKeys(userId?: string): Promise<void> {
|
||||
async clearPinKeys(userId?: UserId): Promise<void> {
|
||||
await this.stateService.setPinKeyEncryptedUserKey(null, { userId: userId });
|
||||
await this.stateService.setPinKeyEncryptedUserKeyEphemeral(null, { userId: userId });
|
||||
await this.stateService.setProtectedPin(null, { userId: userId });
|
||||
@@ -613,7 +630,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return new SymmetricCryptoKey(randomBytes) as CipherKey;
|
||||
}
|
||||
|
||||
async clearKeys(userId?: string): Promise<any> {
|
||||
async clearKeys(userId?: UserId): Promise<any> {
|
||||
await this.clearUserKey(true, userId);
|
||||
await this.clearMasterKeyHash(userId);
|
||||
await this.clearOrgKeys(false, userId);
|
||||
@@ -776,7 +793,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
* @param key The user key
|
||||
* @param userId The desired user
|
||||
*/
|
||||
protected async storeAdditionalKeys(key: UserKey, userId?: string) {
|
||||
protected async storeAdditionalKeys(key: UserKey, userId?: UserId) {
|
||||
const storeAuto = await this.shouldStoreKey(KeySuffixOptions.Auto, userId);
|
||||
if (storeAuto) {
|
||||
await this.stateService.setUserKeyAutoUnlock(key.keyB64, { userId: userId });
|
||||
@@ -802,7 +819,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
* ephemeral version.
|
||||
* @param key The user key
|
||||
*/
|
||||
protected async storePinKey(key: UserKey, userId?: string) {
|
||||
protected async storePinKey(key: UserKey, userId?: UserId) {
|
||||
const pin = await this.encryptService.decryptToUtf8(
|
||||
new EncString(await this.stateService.getProtectedPin({ userId: userId })),
|
||||
key,
|
||||
@@ -822,7 +839,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
}
|
||||
|
||||
protected async shouldStoreKey(keySuffix: KeySuffixOptions, userId?: string) {
|
||||
protected async shouldStoreKey(keySuffix: KeySuffixOptions, userId?: UserId) {
|
||||
let shouldStoreKey = false;
|
||||
switch (keySuffix) {
|
||||
case KeySuffixOptions.Auto: {
|
||||
@@ -841,7 +858,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
|
||||
protected async getKeyFromStorage(
|
||||
keySuffix: KeySuffixOptions,
|
||||
userId?: string,
|
||||
userId?: UserId,
|
||||
): Promise<UserKey> {
|
||||
if (keySuffix === KeySuffixOptions.Auto) {
|
||||
const userKey = await this.stateService.getUserKeyAutoUnlock({ userId: userId });
|
||||
@@ -889,7 +906,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
}
|
||||
|
||||
protected async clearAllStoredUserKeys(userId?: string): Promise<void> {
|
||||
protected async clearAllStoredUserKeys(userId?: UserId): Promise<void> {
|
||||
await this.stateService.setUserKeyAutoUnlock(null, { userId: userId });
|
||||
await this.stateService.setPinKeyEncryptedUserKeyEphemeral(null, { userId: userId });
|
||||
}
|
||||
@@ -984,7 +1001,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
// These methods support migrating the old keys to the new ones.
|
||||
// TODO: Remove after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3475)
|
||||
|
||||
async clearDeprecatedKeys(keySuffix: KeySuffixOptions, userId?: string) {
|
||||
async clearDeprecatedKeys(keySuffix: KeySuffixOptions, userId?: UserId) {
|
||||
if (keySuffix === KeySuffixOptions.Auto) {
|
||||
await this.stateService.setCryptoMasterKeyAuto(null, { userId: userId });
|
||||
} else if (keySuffix === KeySuffixOptions.Pin) {
|
||||
@@ -993,7 +1010,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
}
|
||||
|
||||
async migrateAutoKeyIfNeeded(userId?: string) {
|
||||
async migrateAutoKeyIfNeeded(userId?: UserId) {
|
||||
const oldAutoKey = await this.stateService.getCryptoMasterKeyAuto({ userId: userId });
|
||||
if (!oldAutoKey) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user