1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 00:03:56 +00:00

[PM-3565] Enforce higher minimum KDF (#6440)

Changes minimum iterations for PBKDF2 to 600 000. Also converts the constants into ranges to ensure there is only a single place for all checks.
This commit is contained in:
Oscar Hinton
2023-12-05 16:55:12 +01:00
committed by GitHub
parent 56860482b1
commit 7bbdee9daa
10 changed files with 150 additions and 45 deletions

View File

@@ -17,10 +17,11 @@ import {
KeySuffixOptions,
HashPurpose,
KdfType,
DEFAULT_ARGON2_ITERATIONS,
DEFAULT_ARGON2_MEMORY,
DEFAULT_ARGON2_PARALLELISM,
ARGON2_ITERATIONS,
ARGON2_MEMORY,
ARGON2_PARALLELISM,
EncryptionType,
PBKDF2_ITERATIONS,
} from "../enums";
import { sequentialize } from "../misc/sequentialize";
import { EFFLongWordList } from "../misc/wordlist";
@@ -175,6 +176,12 @@ export class CryptoService implements CryptoServiceAbstraction {
));
}
/**
* Derive a master key from a password and email.
*
* @remarks
* Does not validate the kdf config to ensure it satisfies the minimum requirements for the given kdf type.
*/
async makeMasterKey(
password: string,
email: string,
@@ -841,6 +848,43 @@ export class CryptoService implements CryptoServiceAbstraction {
return null;
}
/**
* Validate that the KDF config follows the requirements for the given KDF type.
*
* @remarks
* Should always be called before updating a users KDF config.
*/
validateKdfConfig(kdf: KdfType, kdfConfig: KdfConfig): void {
switch (kdf) {
case KdfType.PBKDF2_SHA256:
if (!PBKDF2_ITERATIONS.inRange(kdfConfig.iterations)) {
throw new Error(
`PBKDF2 iterations must be between ${PBKDF2_ITERATIONS.min} and ${PBKDF2_ITERATIONS.max}`,
);
}
break;
case KdfType.Argon2id:
if (!ARGON2_ITERATIONS.inRange(kdfConfig.iterations)) {
throw new Error(
`Argon2 iterations must be between ${ARGON2_ITERATIONS.min} and ${ARGON2_ITERATIONS.max}`,
);
}
if (!ARGON2_MEMORY.inRange(kdfConfig.memory)) {
throw new Error(
`Argon2 memory must be between ${ARGON2_MEMORY.min}mb and ${ARGON2_MEMORY.max}mb`,
);
}
if (!ARGON2_PARALLELISM.inRange(kdfConfig.parallelism)) {
throw new Error(
`Argon2 parallelism must be between ${ARGON2_PARALLELISM.min} and ${ARGON2_PARALLELISM.max}.`,
);
}
break;
}
}
protected async clearAllStoredUserKeys(userId?: string): Promise<void> {
await this.stateService.setUserKeyAutoUnlock(null, { userId: userId });
await this.stateService.setPinKeyEncryptedUserKeyEphemeral(null, { userId: userId });
@@ -900,30 +944,21 @@ export class CryptoService implements CryptoServiceAbstraction {
let key: Uint8Array = null;
if (kdf == null || kdf === KdfType.PBKDF2_SHA256) {
if (kdfConfig.iterations == null) {
kdfConfig.iterations = 5000;
} else if (kdfConfig.iterations < 5000) {
throw new Error("PBKDF2 iteration minimum is 5000.");
kdfConfig.iterations = PBKDF2_ITERATIONS.defaultValue;
}
key = await this.cryptoFunctionService.pbkdf2(password, salt, "sha256", kdfConfig.iterations);
} else if (kdf == KdfType.Argon2id) {
if (kdfConfig.iterations == null) {
kdfConfig.iterations = DEFAULT_ARGON2_ITERATIONS;
} else if (kdfConfig.iterations < 2) {
throw new Error("Argon2 iteration minimum is 2.");
kdfConfig.iterations = ARGON2_ITERATIONS.defaultValue;
}
if (kdfConfig.memory == null) {
kdfConfig.memory = DEFAULT_ARGON2_MEMORY;
} else if (kdfConfig.memory < 16) {
throw new Error("Argon2 memory minimum is 16 MB");
} else if (kdfConfig.memory > 1024) {
throw new Error("Argon2 memory maximum is 1024 MB");
kdfConfig.memory = ARGON2_MEMORY.defaultValue;
}
if (kdfConfig.parallelism == null) {
kdfConfig.parallelism = DEFAULT_ARGON2_PARALLELISM;
} else if (kdfConfig.parallelism < 1) {
throw new Error("Argon2 parallelism minimum is 1.");
kdfConfig.parallelism = ARGON2_PARALLELISM.defaultValue;
}
const saltHash = await this.cryptoFunctionService.hash(salt, "sha256");