From 783720218045d5ca573837bb27d1ce18a183af73 Mon Sep 17 00:00:00 2001 From: Jacob Fink Date: Tue, 6 Jun 2023 12:48:56 -0400 Subject: [PATCH] migrate pin to use user's symmetric key instead of master key - set up new state - migrate on lock component - use new crypto service methods --- .../src/auth/components/lock.component.ts | 102 ++++++++++++++---- .../src/components/set-pin.component.ts | 10 +- .../platform/abstractions/crypto.service.ts | 28 ++++- .../platform/abstractions/state.service.ts | 41 ++++++- .../models/domain/account-keys.spec.ts | 1 + .../models/domain/account-settings.spec.ts | 14 +++ .../src/platform/models/domain/account.ts | 7 +- .../src/platform/services/crypto.service.ts | 66 +++++++----- .../src/platform/services/state.service.ts | 38 ++++++- .../vaultTimeoutSettings.service.ts | 5 +- 10 files changed, 255 insertions(+), 57 deletions(-) diff --git a/libs/angular/src/auth/components/lock.component.ts b/libs/angular/src/auth/components/lock.component.ts index 01c083ee5a9..e26e49076e7 100644 --- a/libs/angular/src/auth/components/lock.component.ts +++ b/libs/angular/src/auth/components/lock.component.ts @@ -11,9 +11,10 @@ import { InternalPolicyService } from "@bitwarden/common/admin-console/abstracti import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { ForceResetPasswordReason } from "@bitwarden/common/auth/models/domain/force-reset-password-reason"; +import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response"; -import { HashPurpose, KeySuffixOptions } from "@bitwarden/common/enums"; +import { HashPurpose, KdfType, KeySuffixOptions } from "@bitwarden/common/enums"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -23,8 +24,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; +import { UserSymKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { DialogServiceAbstraction, SimpleDialogType } from "../../services/dialog"; @@ -152,25 +153,42 @@ export class LockComponent implements OnInit, OnDestroy { try { const kdf = await this.stateService.getKdfType(); const kdfConfig = await this.stateService.getKdfConfig(); + let oldPinProtected: EncString; + let userSymKeyPin: EncString; if (this.pinSet[0]) { - const key = await this.cryptoService.makeKeyFromPin( + // MP on restart enabled + userSymKeyPin = await this.stateService.getDecryptedUserSymKeyPin(); + oldPinProtected = await this.stateService.getDecryptedPinProtected(); + } else { + // MP on restart disabled + userSymKeyPin = new EncString(await this.stateService.getEncryptedUserSymKeyPin()); + oldPinProtected = new EncString(await this.stateService.getEncryptedPinProtected()); + } + + let userSymKey: UserSymKey; + if (oldPinProtected) { + userSymKey = await this.decryptAndMigrateOldPinKey(true, kdf, kdfConfig, oldPinProtected); + } else { + userSymKey = await this.cryptoService.decryptUserSymKeyWithPin( this.pin, this.email, kdf, kdfConfig, - await this.stateService.getDecryptedPinProtected() + userSymKeyPin ); - const encKey = await this.cryptoService.getEncKey(key); + } + + if (this.pinSet[0]) { const protectedPin = await this.stateService.getProtectedPin(); - const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey); - failed = decPin !== this.pin; - if (!failed) { - await this.setKeyAndContinue(key); - } - } else { - const key = await this.cryptoService.makeKeyFromPin(this.pin, this.email, kdf, kdfConfig); - failed = false; - await this.setKeyAndContinue(key); + const decryptedPin = await this.cryptoService.decryptToUtf8( + new EncString(protectedPin), + userSymKey + ); + failed = decryptedPin !== this.pin; + } + + if (!failed) { + await this.setKeyAndContinue(userSymKey); } } catch { failed = true; @@ -257,8 +275,9 @@ export class LockComponent implements OnInit, OnDestroy { } await this.setKeyAndContinue(key, true); } - private async setKeyAndContinue(key: SymmetricCryptoKey, evaluatePasswordAfterUnlock = false) { - await this.cryptoService.setKey(key); + + private async setKeyAndContinue(key: UserSymKey, evaluatePasswordAfterUnlock = false) { + await this.cryptoService.setUserKey(key); await this.doContinue(evaluatePasswordAfterUnlock); } @@ -297,9 +316,12 @@ export class LockComponent implements OnInit, OnDestroy { private async load() { this.pinSet = await this.vaultTimeoutSettingsService.isPinLockSet(); - this.pinLock = - (this.pinSet[0] && (await this.stateService.getDecryptedPinProtected()) != null) || - this.pinSet[1]; + + let decryptedPinSet = await this.stateService.getDecryptedUserSymKeyPin(); + decryptedPinSet ||= await this.stateService.getDecryptedPinProtected(); + // (is MP on Restart enabled && MP already entered) || (Pin is set and MP on Restart disabled) + this.pinLock = (this.pinSet[0] && decryptedPinSet != null) || this.pinSet[1]; + this.supportsBiometric = await this.platformUtilsService.supportsBiometric(); this.biometricLock = (await this.vaultTimeoutSettingsService.isBiometricLockSet()) && @@ -344,4 +366,46 @@ export class LockComponent implements OnInit, OnDestroy { this.enforcedMasterPasswordOptions ); } + + /** + * Migrates the Pin key from encrypting the user's master key to encrypting + * the user's symmetric key + * @param masterPasswordOnRestart True if Master Password on Restart is enabled + * @param kdf User's KdfType + * @param kdfConfig User's KdfConfig + * @param oldPinProtected The old Pin key from state (retrieved from different + * places depending on if Master Password on Restart was enabled) + * @returns The user's symmetric key + */ + private async decryptAndMigrateOldPinKey( + masterPasswordOnRestart: boolean, + kdf: KdfType, + kdfConfig: KdfConfig, + oldPinProtected?: EncString + ): Promise { + // decrypt + const masterKey = await this.cryptoService.decryptMasterKeyWithPin( + this.pin, + this.email, + kdf, + kdfConfig, + oldPinProtected + ); + const encUserSymKey = await this.stateService.getEncryptedCryptoSymmetricKey(); + const userSymKey = await this.cryptoService.decryptUserSymKeyWithMasterKey( + masterKey, + new EncString(encUserSymKey) + ); + // migrate + const pinKey = await this.cryptoService.makePinKey(this.pin, this.email, kdf, kdfConfig); + const pinProtectedKey = await this.cryptoService.encrypt(userSymKey.key, pinKey); + if (masterPasswordOnRestart) { + await this.stateService.setDecryptedPinProtected(null); + await this.stateService.setDecryptedUserSymKeyPin(pinProtectedKey); + } else { + await this.stateService.setEncryptedPinProtected(null); + await this.stateService.setEncryptedUserSymKeyPin(pinProtectedKey.encryptedString); + } + return userSymKey; + } } diff --git a/libs/angular/src/components/set-pin.component.ts b/libs/angular/src/components/set-pin.component.ts index af00980f156..b83ef2f816c 100644 --- a/libs/angular/src/components/set-pin.component.ts +++ b/libs/angular/src/components/set-pin.component.ts @@ -39,14 +39,14 @@ export class SetPinComponent implements OnInit { const kdfConfig = await this.stateService.getKdfConfig(); const email = await this.stateService.getEmail(); const pinKey = await this.cryptoService.makePinKey(this.pin, email, kdf, kdfConfig); - const key = await this.cryptoService.getKey(); - const pinProtectedKey = await this.cryptoService.encrypt(key.key, pinKey); + const userKey = await this.cryptoService.getUserKeyFromMemory(); + const pinProtectedKey = await this.cryptoService.encrypt(userKey.key, pinKey); if (this.masterPassOnRestart) { - const encPin = await this.cryptoService.encrypt(this.pin); + const encPin = await this.cryptoService.encrypt(this.pin, userKey); await this.stateService.setProtectedPin(encPin.encryptedString); - await this.stateService.setDecryptedPinProtected(pinProtectedKey); + await this.stateService.setDecryptedUserSymKeyPin(pinProtectedKey); } else { - await this.stateService.setEncryptedPinProtected(pinProtectedKey.encryptedString); + await this.stateService.setEncryptedUserSymKeyPin(pinProtectedKey.encryptedString); } this.modalRef.close(true); diff --git a/libs/common/src/platform/abstractions/crypto.service.ts b/libs/common/src/platform/abstractions/crypto.service.ts index d0114c8101c..e9bc69bd817 100644 --- a/libs/common/src/platform/abstractions/crypto.service.ts +++ b/libs/common/src/platform/abstractions/crypto.service.ts @@ -22,7 +22,7 @@ export abstract class CryptoService { hasUserKey: () => Promise; hasUserKeyInMemory: (userId?: string) => Promise; hasUserKeyStored: (keySuffix?: KeySuffixOptions, userId?: string) => Promise; - makeUserSymKey: (key: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, EncString]>; + makeUserSymKey: (key: SymmetricCryptoKey) => Promise<[UserSymKey, EncString]>; clearUserKey: (clearSecretStorage?: boolean, userId?: string) => Promise; clearUserKeyFromStorage: (keySuffix: KeySuffixOptions) => Promise; setUserSymKeyMasterKey: (UserSymKeyMasterKey: string, userId?: string) => Promise; @@ -39,7 +39,11 @@ export abstract class CryptoService { masterKey: MasterKey, userSymKey?: UserSymKey ) => Promise<[UserSymKey, EncString]>; - decryptUserSymKeyWithMasterKey: (masterKey: MasterKey, userId?: string) => Promise; + decryptUserSymKeyWithMasterKey: ( + masterKey: MasterKey, + userSymKey?: EncString, + userId?: string + ) => Promise; hashPassword: (password: string, key: MasterKey, hashPurpose?: HashPurpose) => Promise; setKeyHash: (keyHash: string) => Promise; getKeyHash: () => Promise; @@ -65,6 +69,16 @@ export abstract class CryptoService { clearKeyPair: (memoryOnly?: boolean, userId?: string) => Promise; makePinKey: (pin: string, salt: string, kdf: KdfType, kdfConfig: KdfConfig) => Promise; clearPinProtectedKey: () => Promise; + /** + * Decrypts the user's symmetric key with their pin + * @param pin The user's PIN + * @param salt The user's salt + * @param kdf The user's KDF + * @param kdfConfig The user's KDF config + * @param pinProtectedUserSymKey The user's PIN protected symmetric key, if not provided + * it will be retrieved from storage + * @returns The decrypted user's symmetric key + */ decryptUserSymKeyWithPin: ( pin: string, salt: string, @@ -72,6 +86,16 @@ export abstract class CryptoService { kdfConfig: KdfConfig, protectedKeyCs?: EncString ) => Promise; + /** + * @deprecated Left for migration purposes. Use decryptUserSymKeyWithPin instead. + */ + decryptMasterKeyWithPin: ( + pin: string, + salt: string, + kdf: KdfType, + kdfConfig: KdfConfig, + protectedKeyCs?: EncString + ) => Promise; makeSendKey: (keyMaterial: ArrayBuffer) => Promise; clearKeys: (userId?: string) => Promise; rsaEncrypt: (data: ArrayBuffer, publicKey?: ArrayBuffer) => Promise; diff --git a/libs/common/src/platform/abstractions/state.service.ts b/libs/common/src/platform/abstractions/state.service.ts index 5e2ad1b0acb..815d25b4ddb 100644 --- a/libs/common/src/platform/abstractions/state.service.ts +++ b/libs/common/src/platform/abstractions/state.service.ts @@ -76,8 +76,6 @@ export abstract class StateService { setCollapsedGroupings: (value: string[], options?: StorageOptions) => Promise; getConvertAccountToKeyConnector: (options?: StorageOptions) => Promise; setConvertAccountToKeyConnector: (value: boolean, options?: StorageOptions) => Promise; - - // new keys getUserSymKey: (options?: StorageOptions) => Promise; setUserSymKey: (value: UserSymKey, options?: StorageOptions) => Promise; getMasterKey: (options?: StorageOptions) => Promise; @@ -89,10 +87,35 @@ export abstract class StateService { getUserSymKeyBiometric: (options?: StorageOptions) => Promise; hasUserSymKeyBiometric: (options?: StorageOptions) => Promise; setUserSymKeyBiometric: (value: BiometricKey, options?: StorageOptions) => Promise; - // end new keys + /** + * Gets the encrypted version of the user's symmetric key encrypted by the Pin key. + * Used when Master Password on Reset is disabled + */ + getEncryptedUserSymKeyPin: (options?: StorageOptions) => Promise; + /** + * Sets the encrypted version of the user's symmetric key encrypted by the Pin key. + * Used when Master Password on Reset is disabled + */ + setEncryptedUserSymKeyPin: (value: string, options?: StorageOptions) => Promise; + /** + * Gets the decrypted version of the user's symmetric key encrypted by the Pin key. + * Used when Master Password on Reset is enabled + */ + getDecryptedUserSymKeyPin: (options?: StorageOptions) => Promise; + /** + * Sets the decrypted version of the user's symmetric key encrypted by the Pin key. + * Used when Master Password on Reset is enabled + */ + setDecryptedUserSymKeyPin: (value: EncString, options?: StorageOptions) => Promise; // deprecated keys + /** + * @deprecated For migration purposes only, use getUserSymKeyMasterKey instead + */ getEncryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise; + /** + * @deprecated For migration purposes only, use setUserSymKeyMasterKey instead + */ setEncryptedCryptoSymmetricKey: (value: string, options?: StorageOptions) => Promise; getDecryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise; setDecryptedCryptoSymmetricKey: ( @@ -128,7 +151,13 @@ export abstract class StateService { value: GeneratedPasswordHistory[], options?: StorageOptions ) => Promise; + /** + * @deprecated For migration purposes only, use getDecryptedUserSymKeyPin instead + */ getDecryptedPinProtected: (options?: StorageOptions) => Promise; + /** + * @deprecated For migration purposes only, use setDecryptedUserSymKeyPin instead + */ setDecryptedPinProtected: (value: EncString, options?: StorageOptions) => Promise; /** * @deprecated Do not call this, use PolicyService @@ -255,7 +284,13 @@ export abstract class StateService { value: GeneratedPasswordHistory[], options?: StorageOptions ) => Promise; + /** + * @deprecated For migration purposes only, use getEncryptedUserSymKeyPin instead + */ getEncryptedPinProtected: (options?: StorageOptions) => Promise; + /** + * @deprecated For migration purposes only, use setEncryptedUserSymKeyPin instead + */ setEncryptedPinProtected: (value: string, options?: StorageOptions) => Promise; /** * @deprecated Do not call this directly, use PolicyService diff --git a/libs/common/src/platform/models/domain/account-keys.spec.ts b/libs/common/src/platform/models/domain/account-keys.spec.ts index 88183229f55..f20c40c9a71 100644 --- a/libs/common/src/platform/models/domain/account-keys.spec.ts +++ b/libs/common/src/platform/models/domain/account-keys.spec.ts @@ -4,6 +4,7 @@ import { Utils } from "../../misc/utils"; import { AccountKeys, EncryptionPair } from "./account"; import { SymmetricCryptoKey } from "./symmetric-crypto-key"; +//TODO(Jake): Fix tests describe("AccountKeys", () => { describe("toJSON", () => { it("should serialize itself", () => { diff --git a/libs/common/src/platform/models/domain/account-settings.spec.ts b/libs/common/src/platform/models/domain/account-settings.spec.ts index b108bf6d6dc..0156fc27798 100644 --- a/libs/common/src/platform/models/domain/account-settings.spec.ts +++ b/libs/common/src/platform/models/domain/account-settings.spec.ts @@ -7,6 +7,20 @@ describe("AccountSettings", () => { expect(AccountSettings.fromJSON(JSON.parse("{}"))).toBeInstanceOf(AccountSettings); }); + it("should deserialize userSymKeyPin", () => { + const accountSettings = new AccountSettings(); + accountSettings.userSymKeyPin = EncryptionPair.fromJSON({ + encrypted: "encrypted", + decrypted: "3.data", + }); + const jsonObj = JSON.parse(JSON.stringify(accountSettings)); + const actual = AccountSettings.fromJSON(jsonObj); + + expect(actual.userSymKeyPin).toBeInstanceOf(EncryptionPair); + expect(actual.userSymKeyPin.encrypted).toEqual("encrypted"); + expect(actual.userSymKeyPin.decrypted.encryptedString).toEqual("3.data"); + }); + it("should deserialize pinProtected", () => { const accountSettings = new AccountSettings(); accountSettings.pinProtected = EncryptionPair.fromJSON({ diff --git a/libs/common/src/platform/models/domain/account.ts b/libs/common/src/platform/models/domain/account.ts index 4da1a890c45..891748d326e 100644 --- a/libs/common/src/platform/models/domain/account.ts +++ b/libs/common/src/platform/models/domain/account.ts @@ -235,7 +235,8 @@ export class AccountSettings { passwordGenerationOptions?: any; usernameGenerationOptions?: any; generatorOptions?: any; - pinProtected?: EncryptionPair = new EncryptionPair(); + userSymKeyPin?: EncryptionPair = new EncryptionPair(); + pinProtected?: EncryptionPair = new EncryptionPair(); // Deprecated protectedPin?: string; settings?: AccountSettingsSettings; // TODO: Merge whatever is going on here into the AccountSettings model properly vaultTimeout?: number; @@ -254,6 +255,10 @@ export class AccountSettings { return Object.assign(new AccountSettings(), obj, { environmentUrls: EnvironmentUrls.fromJSON(obj?.environmentUrls), + userSymKeyPin: EncryptionPair.fromJSON( + obj?.userSymKeyPin, + EncString.fromJSON + ), pinProtected: EncryptionPair.fromJSON( obj?.pinProtected, EncString.fromJSON diff --git a/libs/common/src/platform/services/crypto.service.ts b/libs/common/src/platform/services/crypto.service.ts index 39206f263b4..3e16deec5bf 100644 --- a/libs/common/src/platform/services/crypto.service.ts +++ b/libs/common/src/platform/services/crypto.service.ts @@ -245,28 +245,36 @@ export class CryptoService implements CryptoServiceAbstraction { /** * Decrypts the user symmetric key with the provided master key * @param masterKey The user's master key + * @param userSymKey The user's encrypted symmetric key * @param userId The desired user * @returns The user's symmetric key */ - async decryptUserSymKeyWithMasterKey(masterKey: MasterKey, userId?: string): Promise { + async decryptUserSymKeyWithMasterKey( + masterKey: MasterKey, + userSymKey?: EncString, + userId?: string + ): Promise { masterKey ||= await this.getMasterKey(); if (masterKey == null) { throw new Error("No Master Key found."); } - // TODO(Jake): Do we need to let this be passed in as well? - const userSymKeyMasterKey = await this.stateService.getUserSymKeyMasterKey({ userId: userId }); - if (userSymKeyMasterKey == null) { - throw new Error("No User Key found."); + if (!userSymKey) { + const userSymKeyMasterKey = await this.stateService.getUserSymKeyMasterKey({ + userId: userId, + }); + if (userSymKeyMasterKey == null) { + throw new Error("No User Key found."); + } + userSymKey = new EncString(userSymKeyMasterKey); } let decUserKey: ArrayBuffer; - const encUserKey = new EncString(userSymKeyMasterKey); - if (encUserKey.encryptionType === EncryptionType.AesCbc256_B64) { - decUserKey = await this.decryptToBytes(encUserKey, masterKey); - } else if (encUserKey.encryptionType === EncryptionType.AesCbc256_HmacSha256_B64) { + if (userSymKey.encryptionType === EncryptionType.AesCbc256_B64) { + decUserKey = await this.decryptToBytes(userSymKey, masterKey); + } else if (userSymKey.encryptionType === EncryptionType.AesCbc256_HmacSha256_B64) { const newKey = await this.stretchKey(masterKey); - decUserKey = await this.decryptToBytes(encUserKey, newKey); + decUserKey = await this.decryptToBytes(userSymKey, newKey); } else { throw new Error("Unsupported encryption type."); } @@ -673,28 +681,19 @@ export class CryptoService implements CryptoServiceAbstraction { * @param userId The desired user */ async clearPinProtectedKey(userId?: string): Promise { - return await this.stateService.setEncryptedPinProtected(null, { userId: userId }); + await this.stateService.setEncryptedPinProtected(null, { userId: userId }); + await this.stateService.setEncryptedUserSymKeyPin(null, { userId: userId }); } - /** - * Decrypts the user's symmetric key with their pin - * @param pin The user's PIN - * @param salt The user's salt - * @param kdf The user's KDF - * @param kdfConfig The user's KDF config - * @param pinProtectedUserSymKey The user's PIN protected symmetric key, if not provided - * it will be retrieved from storage - * @returns The decrypted user's symmetric key - */ async decryptUserSymKeyWithPin( pin: string, salt: string, kdf: KdfType, kdfConfig: KdfConfig, - pinProtectedUserSymKey: EncString = null + pinProtectedUserSymKey?: EncString ): Promise { - if (pinProtectedUserSymKey == null) { - const pinProtectedUserSymKeyString = await this.stateService.getEncryptedPinProtected(); + if (!pinProtectedUserSymKey) { + const pinProtectedUserSymKeyString = await this.stateService.getEncryptedUserSymKeyPin(); if (pinProtectedUserSymKeyString == null) { throw new Error("No PIN protected key found."); } @@ -705,6 +704,25 @@ export class CryptoService implements CryptoServiceAbstraction { return new SymmetricCryptoKey(userSymKey) as UserSymKey; } + async decryptMasterKeyWithPin( + pin: string, + salt: string, + kdf: KdfType, + kdfConfig: KdfConfig, + pinProtectedMasterKey?: EncString + ): Promise { + if (!pinProtectedMasterKey) { + const pinProtectedMasterKeyString = await this.stateService.getEncryptedPinProtected(); + if (pinProtectedMasterKeyString == null) { + throw new Error("No PIN protected key found."); + } + pinProtectedMasterKey = new EncString(pinProtectedMasterKeyString); + } + const pinKey = await this.makePinKey(pin, salt, kdf, kdfConfig); + const masterKey = await this.decryptToBytes(pinProtectedMasterKey, pinKey); + return new SymmetricCryptoKey(masterKey) as MasterKey; + } + /** * @param keyMaterial The key material to derive the send key from * @returns A new send key diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index d5a5b2a84fb..bdfb9af4cb0 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -619,7 +619,7 @@ export class StateService< * so we can unlock with MP offline */ async getUserSymKeyMasterKey(options?: StorageOptions): Promise { - // TODO: defaultOnDiskOptions? Other's are saved in secure storage + // TODO(Jake): defaultOnDiskOptions? Other's are saved in secure storage return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.keys.userSymKeyMasterKey; @@ -630,7 +630,7 @@ export class StateService< * so we can unlock with MP offline */ async setUserSymKeyMasterKey(value: string, options?: StorageOptions): Promise { - // TODO: defaultOnDiskOptions? Other's are saved in secure storage + // TODO(Jake): defaultOnDiskOptions? Other's are saved in secure storage const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); @@ -720,6 +720,40 @@ export class StateService< await this.saveSecureStorageKey(partialKeys.userBiometricKey, value, options); } + async getEncryptedUserSymKeyPin(options?: StorageOptions): Promise { + return ( + await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) + )?.settings?.userSymKeyPin?.encrypted; + } + + async setEncryptedUserSymKeyPin(value: string, options?: StorageOptions): Promise { + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + account.settings.userSymKeyPin.encrypted = value; + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + } + + async getDecryptedUserSymKeyPin(options?: StorageOptions): Promise { + return ( + await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) + )?.settings?.userSymKeyPin?.decrypted; + } + + async setDecryptedUserSymKeyPin(value: EncString, options?: StorageOptions): Promise { + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultInMemoryOptions()) + ); + account.settings.userSymKeyPin.decrypted = value; + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultInMemoryOptions()) + ); + } + /** * @deprecated Use UserSymKeyAuto instead */ diff --git a/libs/common/src/services/vaultTimeout/vaultTimeoutSettings.service.ts b/libs/common/src/services/vaultTimeout/vaultTimeoutSettings.service.ts index ae3c16e5865..78e54a22afc 100644 --- a/libs/common/src/services/vaultTimeout/vaultTimeoutSettings.service.ts +++ b/libs/common/src/services/vaultTimeout/vaultTimeoutSettings.service.ts @@ -46,7 +46,9 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA async isPinLockSet(): Promise<[boolean, boolean]> { const protectedPin = await this.stateService.getProtectedPin(); - const pinProtectedKey = await this.stateService.getEncryptedPinProtected(); + let pinProtectedKey = await this.stateService.getEncryptedUserSymKeyPin(); + pinProtectedKey ||= await this.stateService.getEncryptedPinProtected(); + return [protectedPin != null, pinProtectedKey != null]; } @@ -105,6 +107,7 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA async clear(userId?: string): Promise { await this.stateService.setEverBeenUnlocked(false, { userId: userId }); await this.stateService.setDecryptedPinProtected(null, { userId: userId }); + await this.stateService.setDecryptedUserSymKeyPin(null, { userId: userId }); await this.stateService.setProtectedPin(null, { userId: userId }); } }