1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +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:
Matt Gibson
2024-01-10 11:51:45 -05:00
committed by GitHub
parent 211d7a2626
commit 46a3834f46
21 changed files with 404 additions and 100 deletions

View File

@@ -311,7 +311,6 @@ export default class MainBackground {
this.memoryStorageService as BackgroundMemoryStorageService,
this.storageService as BrowserLocalStorageService,
);
this.encryptService = flagEnabled("multithreadDecryption")
? new MultithreadEncryptServiceImplementation(
this.cryptoFunctionService,
@@ -374,13 +373,14 @@ export default class MainBackground {
window,
);
this.i18nService = new BrowserI18nService(BrowserApi.getUILanguage(), this.stateService);
this.cryptoService = new BrowserCryptoService(
this.cryptoFunctionService,
this.encryptService,
this.platformUtilsService,
this.logService,
this.stateService,
this.accountService,
this.stateProvider,
);
this.tokenService = new TokenService(this.stateService);
this.appIdService = new AppIdService(this.storageService);

View File

@@ -1,5 +1,9 @@
import { CryptoService as AbstractCryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import {
AccountServiceInitOptions,
accountServiceFactory,
} from "../../../auth/background/service-factories/account-service.factory";
import {
StateServiceInitOptions,
stateServiceFactory,
@@ -20,6 +24,7 @@ import {
PlatformUtilsServiceInitOptions,
platformUtilsServiceFactory,
} from "./platform-utils-service.factory";
import { StateProviderInitOptions, stateProviderFactory } from "./state-provider.factory";
type CryptoServiceFactoryOptions = FactoryOptions;
@@ -28,7 +33,9 @@ export type CryptoServiceInitOptions = CryptoServiceFactoryOptions &
EncryptServiceInitOptions &
PlatformUtilsServiceInitOptions &
LogServiceInitOptions &
StateServiceInitOptions;
StateServiceInitOptions &
AccountServiceInitOptions &
StateProviderInitOptions;
export function cryptoServiceFactory(
cache: { cryptoService?: AbstractCryptoService } & CachedServices,
@@ -45,6 +52,8 @@ export function cryptoServiceFactory(
await platformUtilsServiceFactory(cache, opts),
await logServiceFactory(cache, opts),
await stateServiceFactory(cache, opts),
await accountServiceFactory(cache, opts),
await stateProviderFactory(cache, opts),
),
);
}

View File

@@ -5,9 +5,10 @@ import {
UserKey,
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { CryptoService } from "@bitwarden/common/platform/services/crypto.service";
import { UserId } from "@bitwarden/common/types/guid";
export class BrowserCryptoService extends CryptoService {
override async hasUserKeyStored(keySuffix: KeySuffixOptions, userId?: string): Promise<boolean> {
override async hasUserKeyStored(keySuffix: KeySuffixOptions, userId?: UserId): Promise<boolean> {
if (keySuffix === KeySuffixOptions.Biometric) {
return await this.stateService.getBiometricUnlock({ userId: userId });
}
@@ -20,7 +21,7 @@ export class BrowserCryptoService extends CryptoService {
*/
protected override async getKeyFromStorage(
keySuffix: KeySuffixOptions,
userId?: string,
userId?: UserId,
): Promise<UserKey> {
if (keySuffix === KeySuffixOptions.Biometric) {
await this.platformUtilService.authenticateBiometric();

View File

@@ -268,6 +268,8 @@ export class Main {
this.platformUtilsService,
this.logService,
this.stateService,
this.accountService,
this.stateProvider,
);
this.appIdService = new AppIdService(this.storageService);

View File

@@ -37,6 +37,7 @@ import { StateFactory } from "@bitwarden/common/platform/factories/state-factory
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
import { SystemService } from "@bitwarden/common/platform/services/system.service";
import { StateProvider } from "@bitwarden/common/platform/state";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service";
import { DialogService } from "@bitwarden/components";
@@ -180,6 +181,8 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
PlatformUtilsServiceAbstraction,
LogService,
StateServiceAbstraction,
AccountServiceAbstraction,
StateProvider,
],
},
],

View File

@@ -1,4 +1,5 @@
import { mock, mockReset } from "jest-mock-extended";
import { FakeStateProvider } from "@bitwarden/common/../spec/fake-state-provider";
import { mock } from "jest-mock-extended";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
@@ -9,6 +10,12 @@ import {
UserKey,
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { CsprngArray } from "@bitwarden/common/types/csprng";
import { UserId } from "@bitwarden/common/types/guid";
import {
FakeAccountService,
mockAccountServiceWith,
} from "../../../../../libs/common/spec/fake-account-service";
import { ElectronCryptoService } from "./electron-crypto.service";
import { ElectronStateService } from "./electron-state.service.abstraction";
@@ -21,15 +28,14 @@ describe("electronCryptoService", () => {
const platformUtilService = mock<PlatformUtilsService>();
const logService = mock<LogService>();
const stateService = mock<ElectronStateService>();
let accountService: FakeAccountService;
let stateProvider: FakeStateProvider;
const mockUserId = "mock user id";
const mockUserId = "mock user id" as UserId;
beforeEach(() => {
mockReset(cryptoFunctionService);
mockReset(encryptService);
mockReset(platformUtilService);
mockReset(logService);
mockReset(stateService);
accountService = mockAccountServiceWith("userId" as UserId);
stateProvider = new FakeStateProvider(accountService);
electronCryptoService = new ElectronCryptoService(
cryptoFunctionService,
@@ -37,9 +43,15 @@ describe("electronCryptoService", () => {
platformUtilService,
logService,
stateService,
accountService,
stateProvider,
);
});
afterEach(() => {
jest.resetAllMocks();
});
it("instantiates", () => {
expect(electronCryptoService).not.toBeFalsy();
});

View File

@@ -1,3 +1,4 @@
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -11,7 +12,9 @@ import {
UserKey,
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { CryptoService } from "@bitwarden/common/platform/services/crypto.service";
import { StateProvider } from "@bitwarden/common/platform/state";
import { CsprngString } from "@bitwarden/common/types/csprng";
import { UserId } from "@bitwarden/common/types/guid";
import { ElectronStateService } from "./electron-state.service.abstraction";
@@ -22,11 +25,21 @@ export class ElectronCryptoService extends CryptoService {
platformUtilsService: PlatformUtilsService,
logService: LogService,
protected override stateService: ElectronStateService,
accountService: AccountService,
stateProvider: StateProvider,
) {
super(cryptoFunctionService, encryptService, platformUtilsService, logService, stateService);
super(
cryptoFunctionService,
encryptService,
platformUtilsService,
logService,
stateService,
accountService,
stateProvider,
);
}
override async hasUserKeyStored(keySuffix: KeySuffixOptions, userId?: string): Promise<boolean> {
override async hasUserKeyStored(keySuffix: KeySuffixOptions, userId?: UserId): Promise<boolean> {
if (keySuffix === KeySuffixOptions.Biometric) {
// TODO: Remove after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3474)
const oldKey = await this.stateService.hasCryptoMasterKeyBiometric({ userId: userId });
@@ -35,7 +48,7 @@ export class ElectronCryptoService extends CryptoService {
return super.hasUserKeyStored(keySuffix, userId);
}
override async clearStoredUserKey(keySuffix: KeySuffixOptions, userId?: string): Promise<void> {
override async clearStoredUserKey(keySuffix: KeySuffixOptions, userId?: UserId): Promise<void> {
if (keySuffix === KeySuffixOptions.Biometric) {
this.stateService.setUserKeyBiometric(null, { userId: userId });
this.clearDeprecatedKeys(KeySuffixOptions.Biometric, userId);
@@ -44,7 +57,7 @@ export class ElectronCryptoService extends CryptoService {
super.clearStoredUserKey(keySuffix, userId);
}
protected override async storeAdditionalKeys(key: UserKey, userId?: string) {
protected override async storeAdditionalKeys(key: UserKey, userId?: UserId) {
await super.storeAdditionalKeys(key, userId);
const storeBiometricKey = await this.shouldStoreKey(KeySuffixOptions.Biometric, userId);
@@ -59,7 +72,7 @@ export class ElectronCryptoService extends CryptoService {
protected override async getKeyFromStorage(
keySuffix: KeySuffixOptions,
userId?: string,
userId?: UserId,
): Promise<UserKey> {
if (keySuffix === KeySuffixOptions.Biometric) {
await this.migrateBiometricKeyIfNeeded(userId);
@@ -69,7 +82,7 @@ export class ElectronCryptoService extends CryptoService {
return await super.getKeyFromStorage(keySuffix, userId);
}
protected async storeBiometricKey(key: UserKey, userId?: string): Promise<void> {
protected async storeBiometricKey(key: UserKey, userId?: UserId): Promise<void> {
let clientEncKeyHalf: CsprngString = null;
if (await this.stateService.getBiometricRequirePasswordOnStart({ userId })) {
clientEncKeyHalf = await this.getBiometricEncryptionClientKeyHalf(userId);
@@ -80,7 +93,7 @@ export class ElectronCryptoService extends CryptoService {
);
}
protected async shouldStoreKey(keySuffix: KeySuffixOptions, userId?: string): Promise<boolean> {
protected async shouldStoreKey(keySuffix: KeySuffixOptions, userId?: UserId): Promise<boolean> {
if (keySuffix === KeySuffixOptions.Biometric) {
const biometricUnlock = await this.stateService.getBiometricUnlock({ userId: userId });
return biometricUnlock && this.platformUtilService.supportsSecureStorage();
@@ -88,12 +101,12 @@ export class ElectronCryptoService extends CryptoService {
return await super.shouldStoreKey(keySuffix, userId);
}
protected override async clearAllStoredUserKeys(userId?: string): Promise<void> {
protected override async clearAllStoredUserKeys(userId?: UserId): Promise<void> {
await this.stateService.setUserKeyBiometric(null, { userId: userId });
super.clearAllStoredUserKeys(userId);
}
private async getBiometricEncryptionClientKeyHalf(userId?: string): Promise<CsprngString | null> {
private async getBiometricEncryptionClientKeyHalf(userId?: UserId): Promise<CsprngString | null> {
try {
let biometricKey = await this.stateService
.getBiometricEncryptionClientKeyHalf({ userId })
@@ -118,7 +131,7 @@ export class ElectronCryptoService extends CryptoService {
// These methods support migrating the old keys to the new ones.
// TODO: Remove after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3475)
override async clearDeprecatedKeys(keySuffix: KeySuffixOptions, userId?: string) {
override async clearDeprecatedKeys(keySuffix: KeySuffixOptions, userId?: UserId) {
if (keySuffix === KeySuffixOptions.Biometric) {
await this.stateService.setCryptoMasterKeyBiometric(null, { userId: userId });
}
@@ -126,7 +139,7 @@ export class ElectronCryptoService extends CryptoService {
super.clearDeprecatedKeys(keySuffix, userId);
}
private async migrateBiometricKeyIfNeeded(userId?: string) {
private async migrateBiometricKeyIfNeeded(userId?: UserId) {
if (await this.stateService.hasCryptoMasterKeyBiometric({ userId })) {
const oldBiometricKey = await this.stateService.getCryptoMasterKeyBiometric({ userId });
// decrypt