1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 22:03:36 +00:00

[PM-12806] Fix minimum KDF validation (#11786)

* Fix minimum KDF validation

* Add better error messages

* Fix tests

* Fix tests
This commit is contained in:
Bernd Schoolmann
2024-10-30 17:35:15 +01:00
committed by GitHub
parent dd6def2f52
commit 912ff886bc
3 changed files with 22 additions and 67 deletions

View File

@@ -295,7 +295,7 @@ describe("LoginStrategyService", () => {
new IdentityTokenResponse({ new IdentityTokenResponse({
ForcePasswordReset: false, ForcePasswordReset: false,
Kdf: KdfType.PBKDF2_SHA256, Kdf: KdfType.PBKDF2_SHA256,
KdfIterations: PBKDF2KdfConfig.PRELOGIN_ITERATIONS.min - 1, KdfIterations: PBKDF2KdfConfig.PRELOGIN_ITERATIONS_MIN - 1,
Key: "KEY", Key: "KEY",
PrivateKey: "PRIVATE_KEY", PrivateKey: "PRIVATE_KEY",
ResetMasterPassword: false, ResetMasterPassword: false,
@@ -309,7 +309,7 @@ describe("LoginStrategyService", () => {
apiService.postPrelogin.mockResolvedValue( apiService.postPrelogin.mockResolvedValue(
new PreloginResponse({ new PreloginResponse({
Kdf: KdfType.PBKDF2_SHA256, Kdf: KdfType.PBKDF2_SHA256,
KdfIterations: PBKDF2KdfConfig.PRELOGIN_ITERATIONS.min - 1, KdfIterations: PBKDF2KdfConfig.PRELOGIN_ITERATIONS_MIN - 1,
}), }),
); );
@@ -321,7 +321,7 @@ describe("LoginStrategyService", () => {
}); });
await expect(sut.logIn(credentials)).rejects.toThrow( await expect(sut.logIn(credentials)).rejects.toThrow(
`PBKDF2 iterations must be between ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS.min} and ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS.max}`, `PBKDF2 iterations must be at least ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS_MIN}, but was ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS_MIN - 1}; possible pre-login downgrade attack detected.`,
); );
}); });
}); });

View File

@@ -13,7 +13,7 @@ export type KdfConfig = PBKDF2KdfConfig | Argon2KdfConfig;
*/ */
export class PBKDF2KdfConfig { export class PBKDF2KdfConfig {
static ITERATIONS = new RangeWithDefault(600_000, 2_000_000, 600_000); static ITERATIONS = new RangeWithDefault(600_000, 2_000_000, 600_000);
static PRELOGIN_ITERATIONS = new RangeWithDefault(5000, 2_000_000, 600_000); static PRELOGIN_ITERATIONS_MIN = 5000;
kdfType: KdfType.PBKDF2_SHA256 = KdfType.PBKDF2_SHA256; kdfType: KdfType.PBKDF2_SHA256 = KdfType.PBKDF2_SHA256;
iterations: number; iterations: number;
@@ -38,9 +38,9 @@ export class PBKDF2KdfConfig {
* A Valid PBKDF2 KDF configuration has KDF iterations between the 5000 and 2_000_000. * A Valid PBKDF2 KDF configuration has KDF iterations between the 5000 and 2_000_000.
*/ */
validateKdfConfigForPrelogin(): void { validateKdfConfigForPrelogin(): void {
if (!PBKDF2KdfConfig.PRELOGIN_ITERATIONS.inRange(this.iterations)) { if (PBKDF2KdfConfig.PRELOGIN_ITERATIONS_MIN > this.iterations) {
throw new Error( throw new Error(
`PBKDF2 iterations must be between ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS.min} and ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS.max}`, `PBKDF2 iterations must be at least ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS_MIN}, but was ${this.iterations}; possible pre-login downgrade attack detected.`,
); );
} }
} }
@@ -58,9 +58,9 @@ export class Argon2KdfConfig {
static PARALLELISM = new RangeWithDefault(1, 16, 4); static PARALLELISM = new RangeWithDefault(1, 16, 4);
static ITERATIONS = new RangeWithDefault(2, 10, 3); static ITERATIONS = new RangeWithDefault(2, 10, 3);
static PRELOGIN_MEMORY = Argon2KdfConfig.MEMORY; static PRELOGIN_MEMORY_MIN = 16;
static PRELOGIN_PARALLELISM = Argon2KdfConfig.PARALLELISM; static PRELOGIN_PARALLELISM_MIN = 1;
static PRELOGIN_ITERATIONS = Argon2KdfConfig.ITERATIONS; static PRELOGIN_ITERATIONS_MIN = 2;
kdfType: KdfType.Argon2id = KdfType.Argon2id; kdfType: KdfType.Argon2id = KdfType.Argon2id;
iterations: number; iterations: number;
@@ -86,7 +86,7 @@ export class Argon2KdfConfig {
if (!Argon2KdfConfig.MEMORY.inRange(this.memory)) { if (!Argon2KdfConfig.MEMORY.inRange(this.memory)) {
throw new Error( throw new Error(
`Argon2 memory must be between ${Argon2KdfConfig.MEMORY.min}mb and ${Argon2KdfConfig.MEMORY.max}mb`, `Argon2 memory must be between ${Argon2KdfConfig.MEMORY.min} MiB and ${Argon2KdfConfig.MEMORY.max} MiB`,
); );
} }
@@ -101,21 +101,21 @@ export class Argon2KdfConfig {
* Validates the Argon2 KDF configuration for pre-login. * Validates the Argon2 KDF configuration for pre-login.
*/ */
validateKdfConfigForPrelogin(): void { validateKdfConfigForPrelogin(): void {
if (!Argon2KdfConfig.PRELOGIN_ITERATIONS.inRange(this.iterations)) { if (Argon2KdfConfig.PRELOGIN_ITERATIONS_MIN > this.iterations) {
throw new Error( throw new Error(
`Argon2 iterations must be between ${Argon2KdfConfig.PRELOGIN_ITERATIONS.min} and ${Argon2KdfConfig.PRELOGIN_ITERATIONS.max}`, `Argon2 iterations must be at least ${Argon2KdfConfig.PRELOGIN_ITERATIONS_MIN}, but was ${this.iterations}; possible pre-login downgrade attack detected.`,
); );
} }
if (!Argon2KdfConfig.PRELOGIN_MEMORY.inRange(this.memory)) { if (Argon2KdfConfig.PRELOGIN_MEMORY_MIN > this.memory) {
throw new Error( throw new Error(
`Argon2 memory must be between ${Argon2KdfConfig.PRELOGIN_MEMORY.min}mb and ${Argon2KdfConfig.PRELOGIN_MEMORY.max}mb`, `Argon2 memory must be at least ${Argon2KdfConfig.PRELOGIN_MEMORY_MIN} MiB, but was ${this.memory} MiB; possible pre-login downgrade attack detected.`,
); );
} }
if (!Argon2KdfConfig.PRELOGIN_PARALLELISM.inRange(this.parallelism)) { if (Argon2KdfConfig.PRELOGIN_PARALLELISM_MIN > this.parallelism) {
throw new Error( throw new Error(
`Argon2 parallelism must be between ${Argon2KdfConfig.PRELOGIN_PARALLELISM.min} and ${Argon2KdfConfig.PRELOGIN_PARALLELISM.max}.`, `Argon2 parallelism must be at least ${Argon2KdfConfig.PRELOGIN_PARALLELISM_MIN}, but was ${this.parallelism}; possible pre-login downgrade attack detected.`,
); );
} }
} }

View File

@@ -82,13 +82,6 @@ describe("KdfConfigService", () => {
); );
}); });
it("validateKdfConfigForSetting(): should throw an error for invalid Argon2 memory", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(3, 1025, 4);
expect(() => kdfConfig.validateKdfConfigForSetting()).toThrow(
`Argon2 memory must be between ${Argon2KdfConfig.MEMORY.min}mb and ${Argon2KdfConfig.MEMORY.max}mb`,
);
});
it("validateKdfConfigForSetting(): should throw an error for invalid Argon2 parallelism", () => { it("validateKdfConfigForSetting(): should throw an error for invalid Argon2 parallelism", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(3, 64, 17); const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(3, 64, 17);
expect(() => kdfConfig.validateKdfConfigForSetting()).toThrow( expect(() => kdfConfig.validateKdfConfigForSetting()).toThrow(
@@ -108,70 +101,32 @@ describe("KdfConfigService", () => {
it("validateKdfConfigForPrelogin(): should throw an error for too low PBKDF2 iterations", () => { it("validateKdfConfigForPrelogin(): should throw an error for too low PBKDF2 iterations", () => {
const kdfConfig: PBKDF2KdfConfig = new PBKDF2KdfConfig( const kdfConfig: PBKDF2KdfConfig = new PBKDF2KdfConfig(
PBKDF2KdfConfig.PRELOGIN_ITERATIONS.min - 1, PBKDF2KdfConfig.PRELOGIN_ITERATIONS_MIN - 1,
); );
expect(() => kdfConfig.validateKdfConfigForPrelogin()).toThrow( expect(() => kdfConfig.validateKdfConfigForPrelogin()).toThrow(
`PBKDF2 iterations must be between ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS.min} and ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS.max}`, `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 high PBKDF2 iterations", () => {
const kdfConfig: PBKDF2KdfConfig = new PBKDF2KdfConfig(
PBKDF2KdfConfig.PRELOGIN_ITERATIONS.max + 1,
);
expect(() => kdfConfig.validateKdfConfigForPrelogin()).toThrow(
`PBKDF2 iterations must be between ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS.min} and ${PBKDF2KdfConfig.PRELOGIN_ITERATIONS.max}`,
); );
}); });
it("validateKdfConfigForPrelogin(): should throw an error for too low Argon2 iterations", () => { it("validateKdfConfigForPrelogin(): should throw an error for too low Argon2 iterations", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig( const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(
Argon2KdfConfig.ITERATIONS.min - 1, Argon2KdfConfig.PRELOGIN_ITERATIONS_MIN - 1,
64, 64,
4, 4,
); );
expect(() => kdfConfig.validateKdfConfigForPrelogin()).toThrow( expect(() => kdfConfig.validateKdfConfigForPrelogin()).toThrow(
`Argon2 iterations must be between ${Argon2KdfConfig.ITERATIONS.min} and ${Argon2KdfConfig.ITERATIONS.max}`, `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 high Argon2 iterations", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(
Argon2KdfConfig.PRELOGIN_ITERATIONS.max + 1,
64,
4,
);
expect(() => kdfConfig.validateKdfConfigForPrelogin()).toThrow(
`Argon2 iterations must be between ${Argon2KdfConfig.ITERATIONS.min} and ${Argon2KdfConfig.ITERATIONS.max}`,
); );
}); });
it("validateKdfConfigForPrelogin(): should throw an error for too low Argon2 memory", () => { it("validateKdfConfigForPrelogin(): should throw an error for too low Argon2 memory", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig( const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(
3, 3,
Argon2KdfConfig.PRELOGIN_MEMORY.min - 1, Argon2KdfConfig.PRELOGIN_MEMORY_MIN - 1,
4, 4,
); );
expect(() => kdfConfig.validateKdfConfigForPrelogin()).toThrow( expect(() => kdfConfig.validateKdfConfigForPrelogin()).toThrow(
`Argon2 memory must be between ${Argon2KdfConfig.PRELOGIN_MEMORY.min}mb and ${Argon2KdfConfig.PRELOGIN_MEMORY.max}mb`, `Argon2 memory must be at least ${Argon2KdfConfig.PRELOGIN_MEMORY_MIN} MiB, but was ${kdfConfig.memory} MiB; possible pre-login downgrade attack detected.`,
);
});
it("validateKdfConfigForPrelogin(): should throw an error for too high Argon2 memory", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(
3,
Argon2KdfConfig.PRELOGIN_MEMORY.max + 1,
4,
);
expect(() => kdfConfig.validateKdfConfigForPrelogin()).toThrow(
`Argon2 memory must be between ${Argon2KdfConfig.PRELOGIN_MEMORY.min}mb and ${Argon2KdfConfig.PRELOGIN_MEMORY.max}mb`,
);
});
it("validateKdfConfigForPrelogin(): should throw an error for too high Argon2 parallelism", () => {
const kdfConfig: Argon2KdfConfig = new Argon2KdfConfig(3, 64, 17);
expect(() => kdfConfig.validateKdfConfigForPrelogin()).toThrow(
`Argon2 parallelism must be between ${Argon2KdfConfig.PRELOGIN_PARALLELISM.min} and ${Argon2KdfConfig.PRELOGIN_PARALLELISM.max}`,
); );
}); });
}); });