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:
parent
5370cc463d
commit
b1dffb5c2c
@ -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>;
|
||||||
|
@ -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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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;
|
||||||
});
|
});
|
||||||
|
@ -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())
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user