mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00:00
[PM-5735] Create kdf Service (#8715)
* key connector migration initial * migrator complete * fix dependencies * finalized tests * fix deps and sync main * clean up definition file * fixing tests * fixed tests * fixing CLI, Browser, Desktop builds * fixed factory options * reverting exports * implemented UserKeyDefinition clearOn * Initial Kdf Service Changes * rename and account setting kdfconfig * fixing tests and renaming migration * fixed DI ordering for browser * rename and fix DI * Clean up Migrations * fixing migrations * begin data structure changes for kdf config * Make KDF more type safe; co-author: jlf0dev * fixing tests * Fixed CLI login and comments * set now accepts userId and test updates --------- Co-authored-by: Jake Fink <jfink@bitwarden.com>
This commit is contained in:
@@ -55,6 +55,7 @@ import { MoveMasterKeyStateToProviderMigrator } from "./migrations/55-move-maste
|
||||
import { AuthRequestMigrator } from "./migrations/56-move-auth-requests";
|
||||
import { CipherServiceMigrator } from "./migrations/57-move-cipher-service-to-state-provider";
|
||||
import { RemoveRefreshTokenMigratedFlagMigrator } from "./migrations/58-remove-refresh-token-migrated-state-provider-flag";
|
||||
import { KdfConfigMigrator } from "./migrations/59-move-kdf-config-to-state-provider";
|
||||
import { RemoveLegacyEtmKeyMigrator } from "./migrations/6-remove-legacy-etm-key";
|
||||
import { MoveBiometricAutoPromptToAccount } from "./migrations/7-move-biometric-auto-prompt-to-account";
|
||||
import { MoveStateVersionMigrator } from "./migrations/8-move-state-version";
|
||||
@@ -62,7 +63,7 @@ import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-setting
|
||||
import { MinVersionMigrator } from "./migrations/min-version";
|
||||
|
||||
export const MIN_VERSION = 3;
|
||||
export const CURRENT_VERSION = 58;
|
||||
export const CURRENT_VERSION = 59;
|
||||
export type MinVersion = typeof MIN_VERSION;
|
||||
|
||||
export function createMigrationBuilder() {
|
||||
@@ -122,7 +123,8 @@ export function createMigrationBuilder() {
|
||||
.with(MoveMasterKeyStateToProviderMigrator, 54, 55)
|
||||
.with(AuthRequestMigrator, 55, 56)
|
||||
.with(CipherServiceMigrator, 56, 57)
|
||||
.with(RemoveRefreshTokenMigratedFlagMigrator, 57, CURRENT_VERSION);
|
||||
.with(RemoveRefreshTokenMigratedFlagMigrator, 57, 58)
|
||||
.with(KdfConfigMigrator, 58, CURRENT_VERSION);
|
||||
}
|
||||
|
||||
export async function currentVersion(
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
import { MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
|
||||
import { mockMigrationHelper } from "../migration-helper.spec";
|
||||
|
||||
import { KdfConfigMigrator } from "./59-move-kdf-config-to-state-provider";
|
||||
|
||||
function exampleJSON() {
|
||||
return {
|
||||
global: {
|
||||
otherStuff: "otherStuff1",
|
||||
},
|
||||
authenticatedAccounts: ["FirstAccount", "SecondAccount"],
|
||||
FirstAccount: {
|
||||
profile: {
|
||||
kdfIterations: 3,
|
||||
kdfMemory: 64,
|
||||
kdfParallelism: 5,
|
||||
kdfType: 1,
|
||||
otherStuff: "otherStuff1",
|
||||
},
|
||||
otherStuff: "otherStuff2",
|
||||
},
|
||||
SecondAccount: {
|
||||
profile: {
|
||||
kdfIterations: 600_001,
|
||||
kdfMemory: null as number,
|
||||
kdfParallelism: null as number,
|
||||
kdfType: 0,
|
||||
otherStuff: "otherStuff3",
|
||||
},
|
||||
otherStuff: "otherStuff4",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function rollbackJSON() {
|
||||
return {
|
||||
user_FirstAccount_kdfConfig_kdfConfig: {
|
||||
iterations: 3,
|
||||
memory: 64,
|
||||
parallelism: 5,
|
||||
kdfType: 1,
|
||||
},
|
||||
user_SecondAccount_kdfConfig_kdfConfig: {
|
||||
iterations: 600_001,
|
||||
memory: null as number,
|
||||
parallelism: null as number,
|
||||
kdfType: 0,
|
||||
},
|
||||
global: {
|
||||
otherStuff: "otherStuff1",
|
||||
},
|
||||
authenticatedAccounts: ["FirstAccount", "SecondAccount"],
|
||||
FirstAccount: {
|
||||
profile: {
|
||||
otherStuff: "otherStuff2",
|
||||
},
|
||||
otherStuff: "otherStuff3",
|
||||
},
|
||||
SecondAccount: {
|
||||
profile: {
|
||||
otherStuff: "otherStuff4",
|
||||
},
|
||||
otherStuff: "otherStuff5",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const kdfConfigKeyDefinition: KeyDefinitionLike = {
|
||||
key: "kdfConfig",
|
||||
stateDefinition: {
|
||||
name: "kdfConfig",
|
||||
},
|
||||
};
|
||||
|
||||
describe("KdfConfigMigrator", () => {
|
||||
let helper: MockProxy<MigrationHelper>;
|
||||
let sut: KdfConfigMigrator;
|
||||
|
||||
describe("migrate", () => {
|
||||
beforeEach(() => {
|
||||
helper = mockMigrationHelper(exampleJSON(), 59);
|
||||
sut = new KdfConfigMigrator(58, 59);
|
||||
});
|
||||
|
||||
it("should remove kdfType and kdfConfig from Account.Profile", async () => {
|
||||
await sut.migrate(helper);
|
||||
|
||||
expect(helper.set).toHaveBeenCalledTimes(2);
|
||||
expect(helper.set).toHaveBeenCalledWith("FirstAccount", {
|
||||
profile: {
|
||||
otherStuff: "otherStuff1",
|
||||
},
|
||||
otherStuff: "otherStuff2",
|
||||
});
|
||||
expect(helper.set).toHaveBeenCalledWith("SecondAccount", {
|
||||
profile: {
|
||||
otherStuff: "otherStuff3",
|
||||
},
|
||||
otherStuff: "otherStuff4",
|
||||
});
|
||||
expect(helper.setToUser).toHaveBeenCalledWith("FirstAccount", kdfConfigKeyDefinition, {
|
||||
iterations: 3,
|
||||
memory: 64,
|
||||
parallelism: 5,
|
||||
kdfType: 1,
|
||||
});
|
||||
expect(helper.setToUser).toHaveBeenCalledWith("SecondAccount", kdfConfigKeyDefinition, {
|
||||
iterations: 600_001,
|
||||
memory: null as number,
|
||||
parallelism: null as number,
|
||||
kdfType: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("rollback", () => {
|
||||
beforeEach(() => {
|
||||
helper = mockMigrationHelper(rollbackJSON(), 59);
|
||||
sut = new KdfConfigMigrator(58, 59);
|
||||
});
|
||||
|
||||
it("should null out new KdfConfig account value and set account.profile", async () => {
|
||||
await sut.rollback(helper);
|
||||
|
||||
expect(helper.setToUser).toHaveBeenCalledTimes(2);
|
||||
expect(helper.setToUser).toHaveBeenCalledWith("FirstAccount", kdfConfigKeyDefinition, null);
|
||||
expect(helper.setToUser).toHaveBeenCalledWith("SecondAccount", kdfConfigKeyDefinition, null);
|
||||
expect(helper.set).toHaveBeenCalledTimes(2);
|
||||
expect(helper.set).toHaveBeenCalledWith("FirstAccount", {
|
||||
profile: {
|
||||
kdfIterations: 3,
|
||||
kdfMemory: 64,
|
||||
kdfParallelism: 5,
|
||||
kdfType: 1,
|
||||
otherStuff: "otherStuff2",
|
||||
},
|
||||
otherStuff: "otherStuff3",
|
||||
});
|
||||
expect(helper.set).toHaveBeenCalledWith("SecondAccount", {
|
||||
profile: {
|
||||
kdfIterations: 600_001,
|
||||
kdfMemory: null as number,
|
||||
kdfParallelism: null as number,
|
||||
kdfType: 0,
|
||||
otherStuff: "otherStuff4",
|
||||
},
|
||||
otherStuff: "otherStuff5",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
|
||||
import { Migrator } from "../migrator";
|
||||
|
||||
enum KdfType {
|
||||
PBKDF2_SHA256 = 0,
|
||||
Argon2id = 1,
|
||||
}
|
||||
|
||||
class KdfConfig {
|
||||
iterations: number;
|
||||
kdfType: KdfType;
|
||||
memory?: number;
|
||||
parallelism?: number;
|
||||
}
|
||||
|
||||
type ExpectedAccountType = {
|
||||
profile?: {
|
||||
kdfIterations: number;
|
||||
kdfType: KdfType;
|
||||
kdfMemory?: number;
|
||||
kdfParallelism?: number;
|
||||
};
|
||||
};
|
||||
|
||||
const kdfConfigKeyDefinition: KeyDefinitionLike = {
|
||||
key: "kdfConfig",
|
||||
stateDefinition: {
|
||||
name: "kdfConfig",
|
||||
},
|
||||
};
|
||||
|
||||
export class KdfConfigMigrator extends Migrator<58, 59> {
|
||||
async migrate(helper: MigrationHelper): Promise<void> {
|
||||
const accounts = await helper.getAccounts<ExpectedAccountType>();
|
||||
async function migrateAccount(userId: string, account: ExpectedAccountType): Promise<void> {
|
||||
const iterations = account?.profile?.kdfIterations;
|
||||
const kdfType = account?.profile?.kdfType;
|
||||
const memory = account?.profile?.kdfMemory;
|
||||
const parallelism = account?.profile?.kdfParallelism;
|
||||
|
||||
const kdfConfig: KdfConfig = {
|
||||
iterations: iterations,
|
||||
kdfType: kdfType,
|
||||
memory: memory,
|
||||
parallelism: parallelism,
|
||||
};
|
||||
|
||||
if (kdfConfig != null) {
|
||||
await helper.setToUser(userId, kdfConfigKeyDefinition, kdfConfig);
|
||||
delete account?.profile?.kdfIterations;
|
||||
delete account?.profile?.kdfType;
|
||||
delete account?.profile?.kdfMemory;
|
||||
delete account?.profile?.kdfParallelism;
|
||||
}
|
||||
|
||||
await helper.set(userId, account);
|
||||
}
|
||||
await Promise.all([...accounts.map(({ userId, account }) => migrateAccount(userId, account))]);
|
||||
}
|
||||
|
||||
async rollback(helper: MigrationHelper): Promise<void> {
|
||||
const accounts = await helper.getAccounts<ExpectedAccountType>();
|
||||
async function rollbackAccount(userId: string, account: ExpectedAccountType): Promise<void> {
|
||||
const kdfConfig: KdfConfig = await helper.getFromUser(userId, kdfConfigKeyDefinition);
|
||||
|
||||
if (kdfConfig != null) {
|
||||
account.profile.kdfIterations = kdfConfig.iterations;
|
||||
account.profile.kdfType = kdfConfig.kdfType;
|
||||
account.profile.kdfMemory = kdfConfig.memory;
|
||||
account.profile.kdfParallelism = kdfConfig.parallelism;
|
||||
await helper.setToUser(userId, kdfConfigKeyDefinition, null);
|
||||
}
|
||||
await helper.set(userId, account);
|
||||
}
|
||||
|
||||
await Promise.all([...accounts.map(({ userId, account }) => rollbackAccount(userId, account))]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user