1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-18 17:23:37 +00:00

[PM-14445] TS strict for Key Management KDF (#13007)

* PM-14445: TS strict for Key Management KDF

* state deserializer can return null
This commit is contained in:
Maciej Zieniuk
2025-01-23 10:45:33 +01:00
committed by GitHub
parent 9f524d4b91
commit abb18881b6
10 changed files with 161 additions and 107 deletions

View File

@@ -1,18 +1,15 @@
import { firstValueFrom } from "rxjs";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import {
FakeAccountService,
FakeStateProvider,
mockAccountServiceWith,
} from "@bitwarden/common/spec";
// FIXME: remove `src` and fix import
// eslint-disable-next-line no-restricted-imports
import { UserId } from "@bitwarden/common/src/types/guid";
import { UserId } from "@bitwarden/common/types/guid";
// FIXME: remove `src` and fix import
// eslint-disable-next-line no-restricted-imports
import { Utils } from "../../common/src/platform/misc/utils";
import { DefaultKdfConfigService } from "./kdf-config.service";
import { Argon2KdfConfig, PBKDF2KdfConfig } from "./models/kdf-config";
import { DefaultKdfConfigService, KDF_CONFIG } from "./kdf-config.service";
import { Argon2KdfConfig, KdfConfig, PBKDF2KdfConfig } from "./models/kdf-config";
describe("KdfConfigService", () => {
let sutKdfConfigService: DefaultKdfConfigService;
@@ -29,36 +26,58 @@ describe("KdfConfigService", () => {
sutKdfConfigService = new DefaultKdfConfigService(fakeStateProvider);
});
it("setKdfConfig(): should set the KDF config", async () => {
const kdfConfig: PBKDF2KdfConfig = new PBKDF2KdfConfig(600_000);
it("setKdfConfig(): should set the PBKDF2KdfConfig config", async () => {
const kdfConfig: KdfConfig = new PBKDF2KdfConfig(500_000);
await sutKdfConfigService.setKdfConfig(mockUserId, kdfConfig);
await expect(sutKdfConfigService.getKdfConfig()).resolves.toEqual(kdfConfig);
expect(fakeStateProvider.mock.setUserState).toHaveBeenCalledWith(
KDF_CONFIG,
kdfConfig,
mockUserId,
);
});
it("setKdfConfig(): should get the KDF config", async () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(3, 64, 4);
it("setKdfConfig(): should set the Argon2KdfConfig config", async () => {
const kdfConfig: KdfConfig = new Argon2KdfConfig(2, 63, 3);
await sutKdfConfigService.setKdfConfig(mockUserId, kdfConfig);
await expect(sutKdfConfigService.getKdfConfig()).resolves.toEqual(kdfConfig);
expect(fakeStateProvider.mock.setUserState).toHaveBeenCalledWith(
KDF_CONFIG,
kdfConfig,
mockUserId,
);
});
it("setKdfConfig(): should throw error KDF cannot be null", async () => {
const kdfConfig: Argon2KdfConfig = null;
try {
await sutKdfConfigService.setKdfConfig(mockUserId, kdfConfig);
await sutKdfConfigService.setKdfConfig(mockUserId, null as unknown as KdfConfig);
} catch (e) {
expect(e).toEqual(new Error("kdfConfig cannot be null"));
}
});
it("setKdfConfig(): should throw error userId cannot be null", async () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(3, 64, 4);
const kdfConfig: KdfConfig = new Argon2KdfConfig(3, 64, 4);
try {
await sutKdfConfigService.setKdfConfig(null, kdfConfig);
await sutKdfConfigService.setKdfConfig(null as unknown as UserId, kdfConfig);
} catch (e) {
expect(e).toEqual(new Error("userId cannot be null"));
}
});
it("getKdfConfig(): should get KdfConfig of active user", async () => {
const kdfConfig: KdfConfig = new PBKDF2KdfConfig(500_000);
await fakeStateProvider.setUserState(KDF_CONFIG, kdfConfig, mockUserId);
await expect(sutKdfConfigService.getKdfConfig()).resolves.toEqual(kdfConfig);
});
it("getKdfConfig(): should throw error KdfConfig can only be retrieved when there is active user", async () => {
fakeAccountService.activeAccountSubject.next(null);
try {
await sutKdfConfigService.getKdfConfig();
} catch (e) {
expect(e).toEqual(new Error("KdfConfig can only be retrieved when there is active user"));
}
});
it("getKdfConfig(): should throw error KdfConfig for active user account state is null", async () => {
try {
await sutKdfConfigService.getKdfConfig();
@@ -67,75 +86,30 @@ describe("KdfConfigService", () => {
}
});
it("validateKdfConfigForSetting(): should validate the PBKDF2 KDF config", () => {
const kdfConfig: PBKDF2KdfConfig = new PBKDF2KdfConfig(600_000);
expect(() => kdfConfig.validateKdfConfigForSetting()).not.toThrow();
});
it("validateKdfConfigForSetting(): should validate the Argon2id KDF config", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(3, 64, 4);
expect(() => kdfConfig.validateKdfConfigForSetting()).not.toThrow();
});
it("validateKdfConfigForSetting(): should throw an error for invalid PBKDF2 iterations", () => {
const kdfConfig: PBKDF2KdfConfig = new PBKDF2KdfConfig(100000);
expect(() => kdfConfig.validateKdfConfigForSetting()).toThrow(
`PBKDF2 iterations must be between ${PBKDF2KdfConfig.ITERATIONS.min} and ${PBKDF2KdfConfig.ITERATIONS.max}`,
it("getKdfConfig$(UserId): should get KdfConfig of provided user", async () => {
await expect(firstValueFrom(sutKdfConfigService.getKdfConfig$(mockUserId))).resolves.toBeNull();
const kdfConfig: KdfConfig = new PBKDF2KdfConfig(500_000);
await fakeStateProvider.setUserState(KDF_CONFIG, kdfConfig, mockUserId);
await expect(firstValueFrom(sutKdfConfigService.getKdfConfig$(mockUserId))).resolves.toEqual(
kdfConfig,
);
});
it("validateKdfConfigForSetting(): should throw an error for invalid Argon2 iterations", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(11, 64, 4);
expect(() => kdfConfig.validateKdfConfigForSetting()).toThrow(
`Argon2 iterations must be between ${Argon2KdfConfig.ITERATIONS.min} and ${Argon2KdfConfig.ITERATIONS.max}`,
it("getKdfConfig$(UserId): should get KdfConfig of provided user after changed", async () => {
await expect(firstValueFrom(sutKdfConfigService.getKdfConfig$(mockUserId))).resolves.toBeNull();
await fakeStateProvider.setUserState(KDF_CONFIG, new PBKDF2KdfConfig(500_000), mockUserId);
const kdfConfigChanged: KdfConfig = new PBKDF2KdfConfig(500_001);
await fakeStateProvider.setUserState(KDF_CONFIG, kdfConfigChanged, mockUserId);
await expect(firstValueFrom(sutKdfConfigService.getKdfConfig$(mockUserId))).resolves.toEqual(
kdfConfigChanged,
);
});
it("validateKdfConfigForSetting(): should throw an error for invalid Argon2 parallelism", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(3, 64, 17);
expect(() => kdfConfig.validateKdfConfigForSetting()).toThrow(
`Argon2 parallelism must be between ${Argon2KdfConfig.PARALLELISM.min} and ${Argon2KdfConfig.PARALLELISM.max}`,
);
});
it("validateKdfConfigForPrelogin(): should validate the PBKDF2 KDF config", () => {
const kdfConfig: PBKDF2KdfConfig = new PBKDF2KdfConfig(600_000);
expect(() => kdfConfig.validateKdfConfigForPrelogin()).not.toThrow();
});
it("validateKdfConfigForPrelogin(): should validate the Argon2id KDF config", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(3, 64, 4);
expect(() => kdfConfig.validateKdfConfigForPrelogin()).not.toThrow();
});
it("validateKdfConfigForPrelogin(): should throw an error for too low PBKDF2 iterations", () => {
const kdfConfig: PBKDF2KdfConfig = new PBKDF2KdfConfig(
PBKDF2KdfConfig.PRELOGIN_ITERATIONS_MIN - 1,
);
expect(() => kdfConfig.validateKdfConfigForPrelogin()).toThrow(
`PBKDF2 iterations must be at least ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS_MIN}, but was ${kdfConfig.iterations}; possible pre-login downgrade attack detected.`,
);
});
it("validateKdfConfigForPrelogin(): should throw an error for too low Argon2 iterations", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(
Argon2KdfConfig.PRELOGIN_ITERATIONS_MIN - 1,
64,
4,
);
expect(() => kdfConfig.validateKdfConfigForPrelogin()).toThrow(
`Argon2 iterations must be at least ${Argon2KdfConfig.PRELOGIN_ITERATIONS_MIN}, but was ${kdfConfig.iterations}; possible pre-login downgrade attack detected.`,
);
});
it("validateKdfConfigForPrelogin(): should throw an error for too low Argon2 memory", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(
3,
Argon2KdfConfig.PRELOGIN_MEMORY_MIN - 1,
4,
);
expect(() => kdfConfig.validateKdfConfigForPrelogin()).toThrow(
`Argon2 memory must be at least ${Argon2KdfConfig.PRELOGIN_MEMORY_MIN} MiB, but was ${kdfConfig.memory} MiB; possible pre-login downgrade attack detected.`,
);
it("getKdfConfig$(UserId): should throw error userId cannot be null", async () => {
try {
sutKdfConfigService.getKdfConfig$(null as unknown as UserId);
} catch (e) {
expect(e).toEqual(new Error("userId cannot be null"));
}
});
});