From 702dfb7eaf8e76fe512a6ef94e06b190d927e48a Mon Sep 17 00:00:00 2001 From: Jacob Fink Date: Tue, 23 May 2023 11:52:47 -0400 Subject: [PATCH] add new storage to replace MasterKey with UserSymKey --- .../platform/abstractions/state.service.ts | 37 +++- .../src/platform/models/domain/account.ts | 12 +- .../models/domain/symmetric-crypto-key.ts | 2 + .../src/platform/services/state.service.ts | 179 +++++++++++++++++- 4 files changed, 220 insertions(+), 10 deletions(-) diff --git a/libs/common/src/platform/abstractions/state.service.ts b/libs/common/src/platform/abstractions/state.service.ts index e5b4e17fb8..cc0540ab23 100644 --- a/libs/common/src/platform/abstractions/state.service.ts +++ b/libs/common/src/platform/abstractions/state.service.ts @@ -26,7 +26,12 @@ import { ServerConfigData } from "../models/data/server-config.data"; import { Account, AccountSettingsSettings } from "../models/domain/account"; import { EncString } from "../models/domain/enc-string"; import { StorageOptions } from "../models/domain/storage-options"; -import { DeviceKey, SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; +import { + DeviceKey, + MasterKey, + SymmetricCryptoKey, + UserSymKey, +} from "../models/domain/symmetric-crypto-key"; export abstract class StateService { accounts$: Observable<{ [userId: string]: T }>; @@ -71,6 +76,27 @@ export abstract class StateService { setCollapsedGroupings: (value: string[], options?: StorageOptions) => Promise; getConvertAccountToKeyConnector: (options?: StorageOptions) => Promise; setConvertAccountToKeyConnector: (value: boolean, options?: StorageOptions) => Promise; + + // new keys + getMasterKey: (options?: StorageOptions) => Promise; + setMasterKey: (value: MasterKey, options?: StorageOptions) => Promise; + getUserSymKey: (options?: StorageOptions) => Promise; + setUserSymKey: (value: UserSymKey, options?: StorageOptions) => Promise; + getUserSymKeyAuto: (options?: StorageOptions) => Promise; + setUserSymKeyAuto: (value: string, options?: StorageOptions) => Promise; + getUserSymKeyBiometric: (options?: StorageOptions) => Promise; + hasUserSymKeyBiometric: (options?: StorageOptions) => Promise; + setUserSymKeyBiometric: (value: BiometricKey, options?: StorageOptions) => Promise; + // end new keys + + // deprecated keys + getEncryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise; + setEncryptedCryptoSymmetricKey: (value: string, options?: StorageOptions) => Promise; + getDecryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise; + setDecryptedCryptoSymmetricKey: ( + value: SymmetricCryptoKey, + options?: StorageOptions + ) => Promise; getCryptoMasterKey: (options?: StorageOptions) => Promise; setCryptoMasterKey: (value: SymmetricCryptoKey, options?: StorageOptions) => Promise; getCryptoMasterKeyAuto: (options?: StorageOptions) => Promise; @@ -80,15 +106,12 @@ export abstract class StateService { getCryptoMasterKeyBiometric: (options?: StorageOptions) => Promise; hasCryptoMasterKeyBiometric: (options?: StorageOptions) => Promise; setCryptoMasterKeyBiometric: (value: BiometricKey, options?: StorageOptions) => Promise; + // end deprecated keys + getDecryptedCiphers: (options?: StorageOptions) => Promise; setDecryptedCiphers: (value: CipherView[], options?: StorageOptions) => Promise; getDecryptedCollections: (options?: StorageOptions) => Promise; setDecryptedCollections: (value: CollectionView[], options?: StorageOptions) => Promise; - getDecryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise; - setDecryptedCryptoSymmetricKey: ( - value: SymmetricCryptoKey, - options?: StorageOptions - ) => Promise; getDecryptedOrganizationKeys: ( options?: StorageOptions ) => Promise>; @@ -205,8 +228,6 @@ export abstract class StateService { value: { [id: string]: CollectionData }, options?: StorageOptions ) => Promise; - getEncryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise; - setEncryptedCryptoSymmetricKey: (value: string, options?: StorageOptions) => Promise; /** * @deprecated Do not call this directly, use FolderService */ diff --git a/libs/common/src/platform/models/domain/account.ts b/libs/common/src/platform/models/domain/account.ts index a8cea255c1..7a132c8813 100644 --- a/libs/common/src/platform/models/domain/account.ts +++ b/libs/common/src/platform/models/domain/account.ts @@ -23,7 +23,7 @@ import { Utils } from "../../misc/utils"; import { ServerConfigData } from "../../models/data/server-config.data"; import { EncString } from "./enc-string"; -import { DeviceKey, SymmetricCryptoKey } from "./symmetric-crypto-key"; +import { DeviceKey, MasterKey, SymmetricCryptoKey, UserSymKey } from "./symmetric-crypto-key"; export class EncryptionPair { encrypted?: TEncrypted; @@ -99,6 +99,14 @@ export class AccountData { } export class AccountKeys { + // new keys + masterKey?: MasterKey; + userSymKey?: UserSymKey; + userSymKeyAuto?: string; + userSymKeyBiometric?: string; + // end new keys + + //deprecated keys cryptoMasterKey?: SymmetricCryptoKey; cryptoMasterKeyAuto?: string; cryptoMasterKeyB64?: string; @@ -107,6 +115,8 @@ export class AccountKeys { string, SymmetricCryptoKey >(); + // end deprecated keys + deviceKey?: DeviceKey; organizationKeys?: EncryptionPair< { [orgId: string]: EncryptedOrganizationKeyData }, diff --git a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts index a553b1ae4f..3b5c5d42c1 100644 --- a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts +++ b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts @@ -78,3 +78,5 @@ export class SymmetricCryptoKey { // Setup all separate key types as opaque types export type DeviceKey = Opaque; +export type UserSymKey = Opaque; +export type MasterKey = Opaque; diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index dce5d76440..2d776ed7c8 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -50,7 +50,12 @@ 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 { DeviceKey, SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; +import { + DeviceKey, + MasterKey, + SymmetricCryptoKey, + UserSymKey, +} from "../models/domain/symmetric-crypto-key"; const keys = { state: "state", @@ -62,6 +67,10 @@ const keys = { }; const partialKeys = { + userAutoKey: "_user_auto", + userBiometricKey: "_user_biometric", + userSymKey: "_user_sym", + autoKey: "_masterkey_auto", biometricKey: "_masterkey_biometric", masterKey: "_masterkey", @@ -515,6 +524,9 @@ export class StateService< ); } + /** + * @deprecated Do not save the Master Key. Use the User Symmetric Key instead + */ async getCryptoMasterKey(options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) @@ -522,6 +534,9 @@ export class StateService< return account?.keys?.cryptoMasterKey; } + /** + * @deprecated Do not save the Master Key. Use the User Symmetric Key instead + */ async setCryptoMasterKey(value: SymmetricCryptoKey, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) @@ -542,6 +557,138 @@ export class StateService< } } + async getMasterKey(options?: StorageOptions): Promise { + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultInMemoryOptions()) + ); + return account?.keys?.masterKey; + } + async setMasterKey(value: MasterKey, options?: StorageOptions): Promise { + 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()) + ); + } + + /** + * User's symmetric key used to encrypt/decrypt data + */ + async getUserSymKey(options?: StorageOptions): Promise { + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultInMemoryOptions()) + ); + return account?.keys?.userSymKey as UserSymKey; + } + + /** + * User's symmetric key used to encrypt/decrypt data + */ + async setUserSymKey(value: UserSymKey, options?: StorageOptions): Promise { + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultInMemoryOptions()) + ); + account.keys.userSymKey = value; + await this.saveAccount( + account, + this.reconcileOptions(options, await this.defaultInMemoryOptions()) + ); + + if (options.userId == this.activeAccountSubject.getValue()) { + const nextValue = value != null; + + // Avoid emitting if we are already unlocked + if (this.activeAccountUnlockedSubject.getValue() != nextValue) { + this.activeAccountUnlockedSubject.next(nextValue); + } + } + } + + /** + * User's symmetric key when using the "never" option of vault timeout + */ + async getUserSymKeyAuto(options?: StorageOptions): Promise { + options = this.reconcileOptions( + this.reconcileOptions(options, { keySuffix: "auto" }), + await this.defaultSecureStorageOptions() + ); + if (options?.userId == null) { + return null; + } + return await this.secureStorageService.get( + `${options.userId}${partialKeys.userAutoKey}`, + options + ); + } + + /** + * User's symmetric key when using the "never" option of vault timeout + */ + async setUserSymKeyAuto(value: string, options?: StorageOptions): Promise { + options = this.reconcileOptions( + this.reconcileOptions(options, { keySuffix: "auto" }), + await this.defaultSecureStorageOptions() + ); + if (options?.userId == null) { + return; + } + await this.saveSecureStorageKey(partialKeys.userAutoKey, value, options); + } + + /** + * User's encrypted symmetric key when using biometrics + */ + async getUserSymKeyBiometric(options?: StorageOptions): Promise { + options = this.reconcileOptions( + this.reconcileOptions(options, { keySuffix: "biometric" }), + await this.defaultSecureStorageOptions() + ); + if (options?.userId == null) { + return null; + } + return await this.secureStorageService.get( + `${options.userId}${partialKeys.userBiometricKey}`, + options + ); + } + + /** + * User's encrypted symmetric key when using biometrics + */ + async hasUserSymKeyBiometric(options?: StorageOptions): Promise { + options = this.reconcileOptions( + this.reconcileOptions(options, { keySuffix: "biometric" }), + await this.defaultSecureStorageOptions() + ); + if (options?.userId == null) { + return false; + } + return await this.secureStorageService.has( + `${options.userId}${partialKeys.userBiometricKey}`, + options + ); + } + + /** + * User's encrypted symmetric key when using biometrics + */ + async setUserSymKeyBiometric(value: BiometricKey, options?: StorageOptions): Promise { + options = this.reconcileOptions( + this.reconcileOptions(options, { keySuffix: "biometric" }), + await this.defaultSecureStorageOptions() + ); + if (options?.userId == null) { + return; + } + await this.saveSecureStorageKey(partialKeys.userBiometricKey, value, options); + } + + /** + * @deprecated Use UserSymKeyAuto instead + */ async getCryptoMasterKeyAuto(options?: StorageOptions): Promise { options = this.reconcileOptions( this.reconcileOptions(options, { keySuffix: "auto" }), @@ -556,6 +703,9 @@ export class StateService< ); } + /** + * @deprecated Use UserSymKeyAuto instead + */ async setCryptoMasterKeyAuto(value: string, options?: StorageOptions): Promise { options = this.reconcileOptions( this.reconcileOptions(options, { keySuffix: "auto" }), @@ -567,6 +717,9 @@ export class StateService< await this.saveSecureStorageKey(partialKeys.autoKey, value, options); } + /** + * @deprecated I don't see where this is even used + */ async getCryptoMasterKeyB64(options?: StorageOptions): Promise { options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); if (options?.userId == null) { @@ -578,6 +731,9 @@ export class StateService< ); } + /** + * @deprecated I don't see where this is even used + */ async setCryptoMasterKeyB64(value: string, options?: StorageOptions): Promise { options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); if (options?.userId == null) { @@ -586,6 +742,9 @@ export class StateService< await this.saveSecureStorageKey(partialKeys.masterKey, value, options); } + /** + * @deprecated Use UserSymKeyBiometric instead + */ async getCryptoMasterKeyBiometric(options?: StorageOptions): Promise { options = this.reconcileOptions( this.reconcileOptions(options, { keySuffix: "biometric" }), @@ -600,6 +759,9 @@ export class StateService< ); } + /** + * @deprecated Use UserSymKeyBiometric instead + */ async hasCryptoMasterKeyBiometric(options?: StorageOptions): Promise { options = this.reconcileOptions( this.reconcileOptions(options, { keySuffix: "biometric" }), @@ -614,6 +776,9 @@ export class StateService< ); } + /** + * @deprecated Use UserSymKeyBiometric instead + */ async setCryptoMasterKeyBiometric(value: BiometricKey, options?: StorageOptions): Promise { options = this.reconcileOptions( this.reconcileOptions(options, { keySuffix: "biometric" }), @@ -661,6 +826,9 @@ export class StateService< ); } + /** + * @deprecated Use UserSymKey instead + */ async getDecryptedCryptoSymmetricKey(options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) @@ -668,6 +836,9 @@ export class StateService< return account?.keys?.cryptoSymmetricKey?.decrypted; } + /** + * @deprecated Use UserSymKey instead + */ async setDecryptedCryptoSymmetricKey( value: SymmetricCryptoKey, options?: StorageOptions @@ -1366,12 +1537,18 @@ export class StateService< ); } + /** + * @deprecated Use UserSymKey instead + */ async getEncryptedCryptoSymmetricKey(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.keys.cryptoSymmetricKey.encrypted; } + /** + * @deprecated Use UserSymKey instead + */ async setEncryptedCryptoSymmetricKey(value: string, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions())