From b1dffb5c2cb7c02feec079704af52f7d9b07359b Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Mon, 12 Sep 2022 20:06:47 -0400 Subject: [PATCH] Fix account key (de)serialization --- libs/common/src/abstractions/state.service.ts | 7 ++- libs/common/src/models/domain/account.ts | 54 +++++++++++++------ .../src/models/domain/acount-keys.spec.ts | 41 ++++++++++++-- libs/common/src/services/crypto.service.ts | 2 +- libs/common/src/services/state.service.ts | 20 ++++--- 5 files changed, 93 insertions(+), 31 deletions(-) diff --git a/libs/common/src/abstractions/state.service.ts b/libs/common/src/abstractions/state.service.ts index 737a51c10b..098d8108bb 100644 --- a/libs/common/src/abstractions/state.service.ts +++ b/libs/common/src/abstractions/state.service.ts @@ -214,8 +214,11 @@ export abstract class StateService { ) => Promise; getEncryptedPrivateKey: (options?: StorageOptions) => Promise; setEncryptedPrivateKey: (value: string, options?: StorageOptions) => Promise; - getEncryptedProviderKeys: (options?: StorageOptions) => Promise; - setEncryptedProviderKeys: (value: any, options?: StorageOptions) => Promise; + getEncryptedProviderKeys: (options?: StorageOptions) => Promise>; + setEncryptedProviderKeys: ( + value: Record, + options?: StorageOptions + ) => Promise; getEncryptedSends: (options?: StorageOptions) => Promise<{ [id: string]: SendData }>; setEncryptedSends: (value: { [id: string]: SendData }, options?: StorageOptions) => Promise; getEntityId: (options?: StorageOptions) => Promise; diff --git a/libs/common/src/models/domain/account.ts b/libs/common/src/models/domain/account.ts index e1e8a33f75..b86402e538 100644 --- a/libs/common/src/models/domain/account.ts +++ b/libs/common/src/models/domain/account.ts @@ -108,15 +108,13 @@ export class AccountKeys { >(); organizationKeys?: EncryptionPair< { [orgId: string]: EncryptedOrganizationKeyData }, - Map + Record > = new EncryptionPair< { [orgId: string]: EncryptedOrganizationKeyData }, - Map - >(); - providerKeys?: EncryptionPair> = new EncryptionPair< - any, - Map + Record >(); + providerKeys?: EncryptionPair, Record> = + new EncryptionPair, Record>(); privateKey?: EncryptionPair = new EncryptionPair(); publicKey?: ArrayBuffer; private publicKeySerialized?: string; @@ -124,10 +122,36 @@ export class AccountKeys { toJSON() { this.publicKeySerialized = Utils.fromBufferToByteString(this.publicKey); - return this; + return { + cryptoMasterKey: this.cryptoMasterKey?.toJSON(), + cryptoMasterKeyAuto: this.cryptoMasterKeyAuto, + cryptoMasterKeyB64: this.cryptoMasterKeyB64, + cryptoMasterKeyBiometric: this.cryptoMasterKeyBiometric, + cryptoSymmetricKey: this.cryptoSymmetricKey?.toJSON() as { + encrypted: string; + decrypted: Jsonify; + decryptedSerialized: string; + }, + organizationKeys: this.organizationKeys?.toJSON() as { + encrypted: { [orgId: string]: EncryptedOrganizationKeyData }; + decrypted: Record>; + decryptedSerialized: string; + }, + providerKeys: this.providerKeys?.toJSON() as { + encrypted: any; + decrypted: Record>; + decryptedSerialized: string; + }, + privateKey: this.privateKey?.toJSON() as { + encrypted: string; + decrypted: Jsonify; + decryptedSerialized: string; + }, + publicKeySerialized: this.publicKeySerialized, + }; } - static fromJSON(obj: any): AccountKeys { + static fromJSON(obj: Partial>): AccountKeys { return Object.assign( new AccountKeys(), { cryptoMasterKey: SymmetricCryptoKey.fromJSON(obj?.cryptoMasterKey) }, @@ -137,8 +161,8 @@ export class AccountKeys { SymmetricCryptoKey.fromJSON ), }, - { organizationKeys: AccountKeys.initMapEncryptionPairsFromJSON(obj?.organizationKeys) }, - { providerKeys: AccountKeys.initMapEncryptionPairsFromJSON(obj?.providerKeys) }, + { organizationKeys: AccountKeys.initRecordEncryptionPairsFromJSON(obj?.organizationKeys) }, + { providerKeys: AccountKeys.initRecordEncryptionPairsFromJSON(obj?.providerKeys) }, { privateKey: EncryptionPair.fromJSON(obj?.privateKey) }, { publicKey: Utils.fromByteStringToArray(obj?.publicKeySerialized)?.buffer, @@ -148,13 +172,13 @@ export class AccountKeys { // These `any` types are a result of Jsonify> === {} // Issue raised https://github.com/sindresorhus/type-fest/issues/457 - static initMapEncryptionPairsFromJSON(obj: any) { + static initRecordEncryptionPairsFromJSON(obj: any) { return EncryptionPair.fromJSON(obj, (decObj: any) => { - const map = new Map(); - for (const id in decObj) { - map.set(id, SymmetricCryptoKey.fromJSON(decObj[id])); + const record: Record = {}; + for (const key in decObj) { + record[key] = SymmetricCryptoKey.fromJSON(decObj[key]); } - return map; + return record; }); } } diff --git a/libs/common/src/models/domain/acount-keys.spec.ts b/libs/common/src/models/domain/acount-keys.spec.ts index e05065e344..85e5518383 100644 --- a/libs/common/src/models/domain/acount-keys.spec.ts +++ b/libs/common/src/models/domain/acount-keys.spec.ts @@ -6,15 +6,22 @@ import { AccountKeys, EncryptionPair } from "./account"; import { SymmetricCryptoKey } from "./symmetricCryptoKey"; describe("AccountKeys", () => { + const buffer = makeStaticByteArray(64).buffer; + const symmetricKey = new SymmetricCryptoKey(buffer); + describe("toJSON", () => { it("should serialize itself", () => { const keys = new AccountKeys(); - const buffer = makeStaticByteArray(64).buffer; - const symmetricKey = new SymmetricCryptoKey(buffer); keys.cryptoMasterKey = symmetricKey; keys.publicKey = buffer; keys.cryptoSymmetricKey = new EncryptionPair(); keys.cryptoSymmetricKey.decrypted = symmetricKey; + keys.providerKeys = new EncryptionPair< + Record, + Record + >(); + keys.providerKeys.encrypted = { test: "test" }; + keys.providerKeys.decrypted = { providerId: symmetricKey }; const symmetricKeySpy = jest.spyOn(symmetricKey, "toJSON"); const actual = JSON.stringify(keys.toJSON()); @@ -23,6 +30,8 @@ describe("AccountKeys", () => { expect(actual).toContain( `"publicKeySerialized":${JSON.stringify(Utils.fromBufferToByteString(buffer))}` ); + expect(actual).toContain(`"providerKeys":${JSON.stringify(keys.providerKeys.toJSON())}`); + expect(actual).toContain(`"providerId":${JSON.stringify(symmetricKey.toJSON())}`); }); it("should serialize public key as a string", () => { @@ -49,19 +58,41 @@ describe("AccountKeys", () => { it("should deserialize organizationKeys", () => { const spy = jest.spyOn(SymmetricCryptoKey, "fromJSON"); - AccountKeys.fromJSON({ organizationKeys: [{ orgId: "keyJSON" }] }); + AccountKeys.fromJSON({ + organizationKeys: { + encrypted: {}, + decrypted: { + "00000000-0000-0000-0000-000000000000": { + keyB64: symmetricKey.keyB64, + }, + }, + decryptedSerialized: null, + }, + }); expect(spy).toHaveBeenCalled(); }); it("should deserialize providerKeys", () => { const spy = jest.spyOn(SymmetricCryptoKey, "fromJSON"); - AccountKeys.fromJSON({ providerKeys: [{ providerId: "keyJSON" }] }); + AccountKeys.fromJSON({ + providerKeys: { + encrypted: {}, + decrypted: { + "00000000-0000-0000-0000-000000000000": { + keyB64: symmetricKey.keyB64, + }, + }, + decryptedSerialized: null, + }, + }); expect(spy).toHaveBeenCalled(); }); it("should deserialize privateKey", () => { const spy = jest.spyOn(EncryptionPair, "fromJSON"); - AccountKeys.fromJSON({ privateKey: { encrypted: "encrypted", decrypted: "decrypted" } }); + AccountKeys.fromJSON({ + privateKey: { encrypted: "encrypted", decrypted: null, decryptedSerialized: "test" }, + }); expect(spy).toHaveBeenCalled(); }); }); diff --git a/libs/common/src/services/crypto.service.ts b/libs/common/src/services/crypto.service.ts index 9b024cf7f2..7604e0857d 100644 --- a/libs/common/src/services/crypto.service.ts +++ b/libs/common/src/services/crypto.service.ts @@ -84,7 +84,7 @@ export class CryptoService implements CryptoServiceAbstraction { } async setProviderKeys(providers: ProfileProviderResponse[]): Promise { - const providerKeys: any = {}; + const providerKeys: Record = {}; providers.forEach((provider) => { providerKeys[provider.id] = provider.key; }); diff --git a/libs/common/src/services/state.service.ts b/libs/common/src/services/state.service.ts index da572b6c25..52074262b8 100644 --- a/libs/common/src/services/state.service.ts +++ b/libs/common/src/services/state.service.ts @@ -687,7 +687,7 @@ export class StateService< const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); - return account?.keys?.organizationKeys?.decrypted; + return new Map(Object.entries(account?.keys?.organizationKeys?.decrypted)); } async setDecryptedOrganizationKeys( @@ -697,7 +697,7 @@ export class StateService< const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); - account.keys.organizationKeys.decrypted = value; + account.keys.organizationKeys.decrypted = Object.fromEntries(value); await this.saveAccount( account, this.reconcileOptions(options, await this.defaultInMemoryOptions()) @@ -784,9 +784,10 @@ export class StateService< async getDecryptedProviderKeys( options?: StorageOptions ): Promise> { - return ( - await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) - )?.keys?.providerKeys?.decrypted; + const account = await this.getAccount( + this.reconcileOptions(options, await this.defaultInMemoryOptions()) + ); + return new Map(Object.entries(account?.keys?.providerKeys?.decrypted)); } async setDecryptedProviderKeys( @@ -796,7 +797,7 @@ export class StateService< const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); - account.keys.providerKeys.decrypted = value; + account.keys.providerKeys.decrypted = Object.fromEntries(value); await this.saveAccount( account, this.reconcileOptions(options, await this.defaultInMemoryOptions()) @@ -1461,13 +1462,16 @@ export class StateService< ); } - async getEncryptedProviderKeys(options?: StorageOptions): Promise { + async getEncryptedProviderKeys(options?: StorageOptions): Promise> { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.keys?.providerKeys?.encrypted; } - async setEncryptedProviderKeys(value: any, options?: StorageOptions): Promise { + async setEncryptedProviderKeys( + value: Record, + options?: StorageOptions + ): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) );