1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-22 11:45:59 +01:00

Fix account key (de)serialization

This commit is contained in:
Matt Gibson 2022-09-12 20:06:47 -04:00
parent 5370cc463d
commit b1dffb5c2c
No known key found for this signature in database
GPG Key ID: A2275080D765C2D7
5 changed files with 93 additions and 31 deletions

View File

@ -214,8 +214,11 @@ export abstract class StateService<T extends Account = Account> {
) => Promise<void>; ) => Promise<void>;
getEncryptedPrivateKey: (options?: StorageOptions) => Promise<string>; getEncryptedPrivateKey: (options?: StorageOptions) => Promise<string>;
setEncryptedPrivateKey: (value: string, options?: StorageOptions) => Promise<void>; setEncryptedPrivateKey: (value: string, options?: StorageOptions) => Promise<void>;
getEncryptedProviderKeys: (options?: StorageOptions) => Promise<any>; getEncryptedProviderKeys: (options?: StorageOptions) => Promise<Record<string, string>>;
setEncryptedProviderKeys: (value: any, options?: StorageOptions) => Promise<void>; setEncryptedProviderKeys: (
value: Record<string, string>,
options?: StorageOptions
) => Promise<void>;
getEncryptedSends: (options?: StorageOptions) => Promise<{ [id: string]: SendData }>; getEncryptedSends: (options?: StorageOptions) => Promise<{ [id: string]: SendData }>;
setEncryptedSends: (value: { [id: string]: SendData }, options?: StorageOptions) => Promise<void>; setEncryptedSends: (value: { [id: string]: SendData }, options?: StorageOptions) => Promise<void>;
getEntityId: (options?: StorageOptions) => Promise<string>; getEntityId: (options?: StorageOptions) => Promise<string>;

View File

@ -108,15 +108,13 @@ export class AccountKeys {
>(); >();
organizationKeys?: EncryptionPair< organizationKeys?: EncryptionPair<
{ [orgId: string]: EncryptedOrganizationKeyData }, { [orgId: string]: EncryptedOrganizationKeyData },
Map<string, SymmetricCryptoKey> Record<string, SymmetricCryptoKey>
> = new EncryptionPair< > = new EncryptionPair<
{ [orgId: string]: EncryptedOrganizationKeyData }, { [orgId: string]: EncryptedOrganizationKeyData },
Map<string, SymmetricCryptoKey> Record<string, SymmetricCryptoKey>
>();
providerKeys?: EncryptionPair<any, Map<string, SymmetricCryptoKey>> = new EncryptionPair<
any,
Map<string, SymmetricCryptoKey>
>(); >();
providerKeys?: EncryptionPair<Record<string, string>, Record<string, SymmetricCryptoKey>> =
new EncryptionPair<Record<string, string>, Record<string, SymmetricCryptoKey>>();
privateKey?: EncryptionPair<string, ArrayBuffer> = new EncryptionPair<string, ArrayBuffer>(); privateKey?: EncryptionPair<string, ArrayBuffer> = new EncryptionPair<string, ArrayBuffer>();
publicKey?: ArrayBuffer; publicKey?: ArrayBuffer;
private publicKeySerialized?: string; private publicKeySerialized?: string;
@ -124,10 +122,36 @@ export class AccountKeys {
toJSON() { toJSON() {
this.publicKeySerialized = Utils.fromBufferToByteString(this.publicKey); 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<SymmetricCryptoKey>;
decryptedSerialized: string;
},
organizationKeys: this.organizationKeys?.toJSON() as {
encrypted: { [orgId: string]: EncryptedOrganizationKeyData };
decrypted: Record<string, Jsonify<SymmetricCryptoKey>>;
decryptedSerialized: string;
},
providerKeys: this.providerKeys?.toJSON() as {
encrypted: any;
decrypted: Record<string, Jsonify<SymmetricCryptoKey>>;
decryptedSerialized: string;
},
privateKey: this.privateKey?.toJSON() as {
encrypted: string;
decrypted: Jsonify<ArrayBuffer>;
decryptedSerialized: string;
},
publicKeySerialized: this.publicKeySerialized,
};
} }
static fromJSON(obj: any): AccountKeys { static fromJSON(obj: Partial<Jsonify<AccountKeys>>): AccountKeys {
return Object.assign( return Object.assign(
new AccountKeys(), new AccountKeys(),
{ cryptoMasterKey: SymmetricCryptoKey.fromJSON(obj?.cryptoMasterKey) }, { cryptoMasterKey: SymmetricCryptoKey.fromJSON(obj?.cryptoMasterKey) },
@ -137,8 +161,8 @@ export class AccountKeys {
SymmetricCryptoKey.fromJSON SymmetricCryptoKey.fromJSON
), ),
}, },
{ organizationKeys: AccountKeys.initMapEncryptionPairsFromJSON(obj?.organizationKeys) }, { organizationKeys: AccountKeys.initRecordEncryptionPairsFromJSON(obj?.organizationKeys) },
{ providerKeys: AccountKeys.initMapEncryptionPairsFromJSON(obj?.providerKeys) }, { providerKeys: AccountKeys.initRecordEncryptionPairsFromJSON(obj?.providerKeys) },
{ privateKey: EncryptionPair.fromJSON(obj?.privateKey) }, { privateKey: EncryptionPair.fromJSON(obj?.privateKey) },
{ {
publicKey: Utils.fromByteStringToArray(obj?.publicKeySerialized)?.buffer, publicKey: Utils.fromByteStringToArray(obj?.publicKeySerialized)?.buffer,
@ -148,13 +172,13 @@ export class AccountKeys {
// These `any` types are a result of Jsonify<Map<>> === {} // These `any` types are a result of Jsonify<Map<>> === {}
// Issue raised https://github.com/sindresorhus/type-fest/issues/457 // Issue raised https://github.com/sindresorhus/type-fest/issues/457
static initMapEncryptionPairsFromJSON(obj: any) { static initRecordEncryptionPairsFromJSON(obj: any) {
return EncryptionPair.fromJSON(obj, (decObj: any) => { return EncryptionPair.fromJSON(obj, (decObj: any) => {
const map = new Map<string, SymmetricCryptoKey>(); const record: Record<string, SymmetricCryptoKey> = {};
for (const id in decObj) { for (const key in decObj) {
map.set(id, SymmetricCryptoKey.fromJSON(decObj[id])); record[key] = SymmetricCryptoKey.fromJSON(decObj[key]);
} }
return map; return record;
}); });
} }
} }

View File

@ -6,15 +6,22 @@ import { AccountKeys, EncryptionPair } from "./account";
import { SymmetricCryptoKey } from "./symmetricCryptoKey"; import { SymmetricCryptoKey } from "./symmetricCryptoKey";
describe("AccountKeys", () => { describe("AccountKeys", () => {
const buffer = makeStaticByteArray(64).buffer;
const symmetricKey = new SymmetricCryptoKey(buffer);
describe("toJSON", () => { describe("toJSON", () => {
it("should serialize itself", () => { it("should serialize itself", () => {
const keys = new AccountKeys(); const keys = new AccountKeys();
const buffer = makeStaticByteArray(64).buffer;
const symmetricKey = new SymmetricCryptoKey(buffer);
keys.cryptoMasterKey = symmetricKey; keys.cryptoMasterKey = symmetricKey;
keys.publicKey = buffer; keys.publicKey = buffer;
keys.cryptoSymmetricKey = new EncryptionPair<string, SymmetricCryptoKey>(); keys.cryptoSymmetricKey = new EncryptionPair<string, SymmetricCryptoKey>();
keys.cryptoSymmetricKey.decrypted = symmetricKey; keys.cryptoSymmetricKey.decrypted = symmetricKey;
keys.providerKeys = new EncryptionPair<
Record<string, string>,
Record<string, SymmetricCryptoKey>
>();
keys.providerKeys.encrypted = { test: "test" };
keys.providerKeys.decrypted = { providerId: symmetricKey };
const symmetricKeySpy = jest.spyOn(symmetricKey, "toJSON"); const symmetricKeySpy = jest.spyOn(symmetricKey, "toJSON");
const actual = JSON.stringify(keys.toJSON()); const actual = JSON.stringify(keys.toJSON());
@ -23,6 +30,8 @@ describe("AccountKeys", () => {
expect(actual).toContain( expect(actual).toContain(
`"publicKeySerialized":${JSON.stringify(Utils.fromBufferToByteString(buffer))}` `"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", () => { it("should serialize public key as a string", () => {
@ -49,19 +58,41 @@ describe("AccountKeys", () => {
it("should deserialize organizationKeys", () => { it("should deserialize organizationKeys", () => {
const spy = jest.spyOn(SymmetricCryptoKey, "fromJSON"); 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(); expect(spy).toHaveBeenCalled();
}); });
it("should deserialize providerKeys", () => { it("should deserialize providerKeys", () => {
const spy = jest.spyOn(SymmetricCryptoKey, "fromJSON"); 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(); expect(spy).toHaveBeenCalled();
}); });
it("should deserialize privateKey", () => { it("should deserialize privateKey", () => {
const spy = jest.spyOn(EncryptionPair, "fromJSON"); 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(); expect(spy).toHaveBeenCalled();
}); });
}); });

View File

@ -84,7 +84,7 @@ export class CryptoService implements CryptoServiceAbstraction {
} }
async setProviderKeys(providers: ProfileProviderResponse[]): Promise<void> { async setProviderKeys(providers: ProfileProviderResponse[]): Promise<void> {
const providerKeys: any = {}; const providerKeys: Record<string, string> = {};
providers.forEach((provider) => { providers.forEach((provider) => {
providerKeys[provider.id] = provider.key; providerKeys[provider.id] = provider.key;
}); });

View File

@ -687,7 +687,7 @@ export class StateService<
const account = await this.getAccount( const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions()) this.reconcileOptions(options, await this.defaultInMemoryOptions())
); );
return account?.keys?.organizationKeys?.decrypted; return new Map(Object.entries(account?.keys?.organizationKeys?.decrypted));
} }
async setDecryptedOrganizationKeys( async setDecryptedOrganizationKeys(
@ -697,7 +697,7 @@ export class StateService<
const account = await this.getAccount( const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions()) this.reconcileOptions(options, await this.defaultInMemoryOptions())
); );
account.keys.organizationKeys.decrypted = value; account.keys.organizationKeys.decrypted = Object.fromEntries(value);
await this.saveAccount( await this.saveAccount(
account, account,
this.reconcileOptions(options, await this.defaultInMemoryOptions()) this.reconcileOptions(options, await this.defaultInMemoryOptions())
@ -784,9 +784,10 @@ export class StateService<
async getDecryptedProviderKeys( async getDecryptedProviderKeys(
options?: StorageOptions options?: StorageOptions
): Promise<Map<string, SymmetricCryptoKey>> { ): Promise<Map<string, SymmetricCryptoKey>> {
return ( const account = await this.getAccount(
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) this.reconcileOptions(options, await this.defaultInMemoryOptions())
)?.keys?.providerKeys?.decrypted; );
return new Map(Object.entries(account?.keys?.providerKeys?.decrypted));
} }
async setDecryptedProviderKeys( async setDecryptedProviderKeys(
@ -796,7 +797,7 @@ export class StateService<
const account = await this.getAccount( const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions()) this.reconcileOptions(options, await this.defaultInMemoryOptions())
); );
account.keys.providerKeys.decrypted = value; account.keys.providerKeys.decrypted = Object.fromEntries(value);
await this.saveAccount( await this.saveAccount(
account, account,
this.reconcileOptions(options, await this.defaultInMemoryOptions()) this.reconcileOptions(options, await this.defaultInMemoryOptions())
@ -1461,13 +1462,16 @@ export class StateService<
); );
} }
async getEncryptedProviderKeys(options?: StorageOptions): Promise<any> { async getEncryptedProviderKeys(options?: StorageOptions): Promise<Record<string, string>> {
return ( return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.keys?.providerKeys?.encrypted; )?.keys?.providerKeys?.encrypted;
} }
async setEncryptedProviderKeys(value: any, options?: StorageOptions): Promise<void> { async setEncryptedProviderKeys(
value: Record<string, string>,
options?: StorageOptions
): Promise<void> {
const account = await this.getAccount( const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskOptions()) this.reconcileOptions(options, await this.defaultOnDiskOptions())
); );