diff --git a/common/src/abstractions/state.service.ts b/common/src/abstractions/state.service.ts index a90088a2..4fa52847 100644 --- a/common/src/abstractions/state.service.ts +++ b/common/src/abstractions/state.service.ts @@ -1,5 +1,10 @@ import { BehaviorSubject } from "rxjs"; +import { + BaseEncryptedOrganizationKey, + EncryptedOrganizationKey, +} from "jslib-common/models/domain/account/encryptedKey"; + import { KdfType } from "../enums/kdfType"; import { ThemeType } from "../enums/themeType"; import { UriMatchType } from "../enums/uriMatchType"; @@ -186,9 +191,9 @@ export abstract class StateService { ) => Promise; getEncryptedOrganizationKeys: ( options?: StorageOptions - ) => Promise<{ [orgId: string]: EncryptedOrganizationKeyStore }>; + ) => Promise<{ [orgId: string]: BaseEncryptedOrganizationKey }>; setEncryptedOrganizationKeys: ( - value: { [orgId: string]: EncryptedOrganizationKeyStore }, + value: { [orgId: string]: BaseEncryptedOrganizationKey }, options?: StorageOptions ) => Promise; getEncryptedPasswordGenerationHistory: ( diff --git a/common/src/interfaces/encryptedOrganizationKeyStore.ts b/common/src/interfaces/encryptedOrganizationKeyStore.ts deleted file mode 100644 index c1cab66a..00000000 --- a/common/src/interfaces/encryptedOrganizationKeyStore.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface EncryptedOrganizationKeyStore { - key: string; - providerId?: string; -} diff --git a/common/src/models/domain/account.ts b/common/src/models/domain/account.ts index 23381148..ea7a3cf3 100644 --- a/common/src/models/domain/account.ts +++ b/common/src/models/domain/account.ts @@ -21,7 +21,6 @@ import { GeneratedPasswordHistory } from "./generatedPasswordHistory"; import { Policy } from "./policy"; import { SymmetricCryptoKey } from "./symmetricCryptoKey"; - export class EncryptionPair { encrypted?: TEncrypted; decrypted?: TDecrypted; diff --git a/common/src/models/domain/account/encryptedKey.ts b/common/src/models/domain/account/encryptedKey.ts new file mode 100644 index 00000000..44f5a6d5 --- /dev/null +++ b/common/src/models/domain/account/encryptedKey.ts @@ -0,0 +1,47 @@ +import { CryptoService } from "jslib-common/abstractions/crypto.service"; + +import { EncString } from "../encString"; +import { SymmetricCryptoKey } from "../symmetricCryptoKey"; + +export abstract class BaseEncryptedOrganizationKey { + decrypt: (cryptoService: CryptoService) => Promise; + toJSON: () => string; + + static fromObj(obj: { key: string; providerId?: string }) { + if (obj.providerId != null) { + return new ProviderEncryptedOrganizationKey(obj.key, obj.providerId); + } + + return new EncryptedOrganizationKey(obj.key); + } +} + +export class EncryptedOrganizationKey implements BaseEncryptedOrganizationKey { + constructor(private key: string) {} + + async decrypt(cryptoService: CryptoService) { + const decValue = await cryptoService.rsaDecrypt(this.key); + return new SymmetricCryptoKey(decValue); + } + + toJSON() { + return JSON.stringify({ key: this.key }); + } +} + +export class ProviderEncryptedOrganizationKey implements BaseEncryptedOrganizationKey { + constructor(private key: string, private providerId: string) {} + + async decrypt(cryptoService: CryptoService) { + const providerKey = await cryptoService.getProviderKey(this.providerId); + const decValue = await cryptoService.decryptToBytes(new EncString(this.key), providerKey); + return new SymmetricCryptoKey(decValue); + } + + toJSON() { + return JSON.stringify({ + key: this.key, + providerId: this.providerId, + }); + } +} diff --git a/common/src/services/crypto.service.ts b/common/src/services/crypto.service.ts index f0054375..8e91c0ae 100644 --- a/common/src/services/crypto.service.ts +++ b/common/src/services/crypto.service.ts @@ -1,6 +1,10 @@ import * as bigInt from "big-integer"; -import { EncryptedOrganizationKeyStore } from 'jslib-common/interfaces/encryptedOrganizationKeyStore'; +import { EncryptedOrganizationKeyStore } from "jslib-common/interfaces/encryptedOrganizationKeyStore"; +import { + BaseEncryptedOrganizationKey, + EncryptedOrganizationKey, +} from "jslib-common/models/domain/account/encryptedKey"; import { CryptoService as CryptoServiceAbstraction } from "../abstractions/crypto.service"; import { CryptoFunctionService } from "../abstractions/cryptoFunction.service"; @@ -61,22 +65,15 @@ export class CryptoService implements CryptoServiceAbstraction { orgs: ProfileOrganizationResponse[], providerOrgs: ProfileProviderOrganizationResponse[] ): Promise { - const orgKeyStore: { [orgId: string]: EncryptedOrganizationKeyStore } = {}; - orgs.forEach((org) => { - orgKeyStore[org.id] = { key: org.key }; + const encOrgKeys: { [orgId: string]: BaseEncryptedOrganizationKey } = {}; + + const allOrgs = orgs.concat(providerOrgs); + allOrgs.forEach((org) => { + encOrgKeys[org.id] = BaseEncryptedOrganizationKey.fromObj(org); }); - for (const providerOrg of providerOrgs) { - // Store providerId with the key later so it can be decrypted using the provider's key - // We can't decrypt it yet because we may not have all the required encryption keys available - orgKeyStore[providerOrg.id] = { - key: providerOrg.key, - providerId: providerOrg.providerId, - } - } - await this.stateService.setDecryptedOrganizationKeys(null); - return await this.stateService.setEncryptedOrganizationKeys(orgKeyStore); + return await this.stateService.setEncryptedOrganizationKeys(encOrgKeys); } async setProviderKeys(providers: ProfileProviderResponse[]): Promise { @@ -219,36 +216,21 @@ export class CryptoService implements CryptoServiceAbstraction { return decryptedOrganizationKeys; } - const encOrgKeyStore = await this.stateService.getEncryptedOrganizationKeys(); - if (encOrgKeyStore == null) { + const encOrgKeys = await this.stateService.getEncryptedOrganizationKeys(); + if (encOrgKeys == null) { return null; } let setKey = false; - for (const orgId in encOrgKeyStore) { + for (const orgId in encOrgKeys) { // eslint-disable-next-line - if (!encOrgKeyStore.hasOwnProperty(orgId)) { + if (!encOrgKeys.hasOwnProperty(orgId) || orgKeys.has(orgId)) { continue; } - if (orgKeys.has(orgId)) { - continue; - } - - const encOrgKey = encOrgKeyStore[orgId].key; - const providerId = encOrgKeyStore[orgId].providerId; - - let decValue = null; - if (providerId == null) { - // User encrypted - decValue = await this.rsaDecrypt(encOrgKey); - } else { - // Provider encrypted - const providerKey = await this.getProviderKey(providerId); - decValue = await this.decryptToBytes(new EncString(encOrgKey), providerKey); - } - orgKeys.set(orgId, new SymmetricCryptoKey(decValue)); + const decryptedKey = await encOrgKeys[orgId].decrypt(this); + orgKeys.set(orgId, decryptedKey); setKey = true; } diff --git a/common/src/services/state.service.ts b/common/src/services/state.service.ts index 0a7e614e..ea1891b1 100644 --- a/common/src/services/state.service.ts +++ b/common/src/services/state.service.ts @@ -1,6 +1,10 @@ import { BehaviorSubject } from "rxjs"; import { EncryptedOrganizationKeyStore } from "jslib-common/interfaces/encryptedOrganizationKeyStore"; +import { + BaseEncryptedOrganizationKey, + EncryptedOrganizationKey, +} from "jslib-common/models/domain/account/encryptedKey"; import { LogService } from "../abstractions/log.service"; import { StateService as StateServiceAbstraction } from "../abstractions/state.service"; @@ -1217,20 +1221,32 @@ export class StateService< async getEncryptedOrganizationKeys( options?: StorageOptions - ): Promise<{ [orgId: string]: EncryptedOrganizationKeyStore }> { - return ( + ): Promise<{ [orgId: string]: BaseEncryptedOrganizationKey }> { + const data = ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.keys?.organizationKeys.encrypted; + + const result: { [orgId: string]: BaseEncryptedOrganizationKey } = {}; + for (const orgId in data) { + result[orgId] = BaseEncryptedOrganizationKey.fromObj(data[orgId]); + } + + return result; } async setEncryptedOrganizationKeys( value: { [orgId: string]: EncryptedOrganizationKeyStore }, options?: StorageOptions ): Promise { + const data: { [orgId: string]: EncryptedOrganizationKey } = {}; + for (const orgId in value) { + data[orgId] = value[orgId].toJSON(); + } + const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); - account.keys.organizationKeys.encrypted = value; + account.keys.organizationKeys.encrypted = data; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions())