mirror of
https://github.com/bitwarden/browser
synced 2025-12-22 03:03:43 +00:00
[PS-2365] Kdf Configuration Options for Argon2 (#4578)
* Implement argon2 config * Remove argon2 webassembly warning * Replace magic numbers by enum * Implement kdf configuration * Update UI according to design feedback * Further updates to follow design feedback * Add oxford comma in argon2 description * Fix typos in argon2 descriptions * move key creation into promise with API call * change casing on PBKDF2 * general improvements * kdf config on set pin component * SHA-256 hash argon2 salt * Change argon2 defaults * Change argon2 salt hash to cryptoFunctionService * Fix isLowKdfIteration check --------- Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com> Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
This commit is contained in:
@@ -22,6 +22,7 @@ import { PasswordlessLogInStrategy } from "../misc/logInStrategies/passwordlessL
|
||||
import { SsoLogInStrategy } from "../misc/logInStrategies/ssoLogin.strategy";
|
||||
import { UserApiLogInStrategy } from "../misc/logInStrategies/user-api-login.strategy";
|
||||
import { AuthResult } from "../models/domain/auth-result";
|
||||
import { KdfConfig } from "../models/domain/kdf-config";
|
||||
import {
|
||||
UserApiLogInCredentials,
|
||||
PasswordLogInCredentials,
|
||||
@@ -247,19 +248,23 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
async makePreloginKey(masterPassword: string, email: string): Promise<SymmetricCryptoKey> {
|
||||
email = email.trim().toLowerCase();
|
||||
let kdf: KdfType = null;
|
||||
let kdfIterations: number = null;
|
||||
let kdfConfig: KdfConfig = null;
|
||||
try {
|
||||
const preloginResponse = await this.apiService.postPrelogin(new PreloginRequest(email));
|
||||
if (preloginResponse != null) {
|
||||
kdf = preloginResponse.kdf;
|
||||
kdfIterations = preloginResponse.kdfIterations;
|
||||
kdfConfig = new KdfConfig(
|
||||
preloginResponse.kdfIterations,
|
||||
preloginResponse.kdfMemory,
|
||||
preloginResponse.kdfParallelism
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e == null || e.statusCode !== 404) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return this.cryptoService.makeKey(masterPassword, email, kdf, kdfIterations);
|
||||
return this.cryptoService.makeKey(masterPassword, email, kdf, kdfConfig);
|
||||
}
|
||||
|
||||
async authResponsePushNotifiction(notification: AuthRequestPushNotification): Promise<any> {
|
||||
|
||||
@@ -8,7 +8,12 @@ import { PlatformUtilsService } from "../abstractions/platformUtils.service";
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
import { EncryptionType } from "../enums/encryptionType";
|
||||
import { HashPurpose } from "../enums/hashPurpose";
|
||||
import { DEFAULT_ARGON2_ITERATIONS, KdfType } from "../enums/kdfType";
|
||||
import {
|
||||
DEFAULT_ARGON2_ITERATIONS,
|
||||
DEFAULT_ARGON2_MEMORY,
|
||||
DEFAULT_ARGON2_PARALLELISM,
|
||||
KdfType,
|
||||
} from "../enums/kdfType";
|
||||
import { KeySuffixOptions } from "../enums/keySuffixOptions";
|
||||
import { sequentialize } from "../misc/sequentialize";
|
||||
import { Utils } from "../misc/utils";
|
||||
@@ -17,6 +22,7 @@ import { EncryptedOrganizationKeyData } from "../models/data/encrypted-organizat
|
||||
import { EncArrayBuffer } from "../models/domain/enc-array-buffer";
|
||||
import { EncString } from "../models/domain/enc-string";
|
||||
import { BaseEncryptedOrganizationKey } from "../models/domain/encrypted-organization-key";
|
||||
import { KdfConfig } from "../models/domain/kdf-config";
|
||||
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
||||
import { ProfileOrganizationResponse } from "../models/response/profile-organization.response";
|
||||
import { ProfileProviderOrganizationResponse } from "../models/response/profile-provider-organization.response";
|
||||
@@ -408,28 +414,44 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
password: string,
|
||||
salt: string,
|
||||
kdf: KdfType,
|
||||
kdfIterations: number
|
||||
kdfConfig: KdfConfig
|
||||
): Promise<SymmetricCryptoKey> {
|
||||
let key: ArrayBuffer = null;
|
||||
if (kdf == null || kdf === KdfType.PBKDF2_SHA256) {
|
||||
if (kdfIterations == null) {
|
||||
kdfIterations = 5000;
|
||||
} else if (kdfIterations < 5000) {
|
||||
if (kdfConfig.iterations == null) {
|
||||
kdfConfig.iterations = 5000;
|
||||
} else if (kdfConfig.iterations < 5000) {
|
||||
throw new Error("PBKDF2 iteration minimum is 5000.");
|
||||
}
|
||||
key = await this.cryptoFunctionService.pbkdf2(password, salt, "sha256", kdfIterations);
|
||||
key = await this.cryptoFunctionService.pbkdf2(password, salt, "sha256", kdfConfig.iterations);
|
||||
} else if (kdf == KdfType.Argon2id) {
|
||||
if (kdfIterations == null) {
|
||||
kdfIterations = DEFAULT_ARGON2_ITERATIONS;
|
||||
} else if (kdfIterations < 2) {
|
||||
if (kdfConfig.iterations == null) {
|
||||
kdfConfig.iterations = DEFAULT_ARGON2_ITERATIONS;
|
||||
} else if (kdfConfig.iterations < 2) {
|
||||
throw new Error("Argon2 iteration minimum is 2.");
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
if (kdfConfig.parallelism == null) {
|
||||
kdfConfig.parallelism = DEFAULT_ARGON2_PARALLELISM;
|
||||
} else if (kdfConfig.parallelism < 1) {
|
||||
throw new Error("Argon2 parallelism minimum is 1.");
|
||||
}
|
||||
|
||||
const saltHash = await this.cryptoFunctionService.hash(salt, "sha256");
|
||||
key = await this.cryptoFunctionService.argon2(
|
||||
password,
|
||||
salt,
|
||||
kdfIterations,
|
||||
16 * 1024, // convert to KiB from MiB
|
||||
2
|
||||
saltHash,
|
||||
kdfConfig.iterations,
|
||||
kdfConfig.memory * 1024, // convert to KiB from MiB
|
||||
kdfConfig.parallelism
|
||||
);
|
||||
} else {
|
||||
throw new Error("Unknown Kdf.");
|
||||
@@ -441,7 +463,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
pin: string,
|
||||
salt: string,
|
||||
kdf: KdfType,
|
||||
kdfIterations: number,
|
||||
kdfConfig: KdfConfig,
|
||||
protectedKeyCs: EncString = null
|
||||
): Promise<SymmetricCryptoKey> {
|
||||
if (protectedKeyCs == null) {
|
||||
@@ -451,7 +473,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
protectedKeyCs = new EncString(pinProtectedKey);
|
||||
}
|
||||
const pinKey = await this.makePinKey(pin, salt, kdf, kdfIterations);
|
||||
const pinKey = await this.makePinKey(pin, salt, kdf, kdfConfig);
|
||||
const decKey = await this.decryptToBytes(protectedKeyCs, pinKey);
|
||||
return new SymmetricCryptoKey(decKey);
|
||||
}
|
||||
@@ -474,9 +496,9 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
pin: string,
|
||||
salt: string,
|
||||
kdf: KdfType,
|
||||
kdfIterations: number
|
||||
kdfConfig: KdfConfig
|
||||
): Promise<SymmetricCryptoKey> {
|
||||
const pinKey = await this.makeKey(pin, salt, kdf, kdfIterations);
|
||||
const pinKey = await this.makeKey(pin, salt, kdf, kdfConfig);
|
||||
return await this.stretchKey(pinKey);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import { CollectionData } from "../models/data/collection.data";
|
||||
import { Cipher } from "../models/domain/cipher";
|
||||
import { Collection } from "../models/domain/collection";
|
||||
import { Folder } from "../models/domain/folder";
|
||||
import { KdfConfig } from "../models/domain/kdf-config";
|
||||
import { CipherWithIdExport as CipherExport } from "../models/export/cipher-with-ids.export";
|
||||
import { CollectionWithIdExport as CollectionExport } from "../models/export/collection-with-id.export";
|
||||
import { EventExport } from "../models/export/event.export";
|
||||
@@ -54,12 +55,12 @@ export class ExportService implements ExportServiceAbstraction {
|
||||
: await this.getExport("json");
|
||||
|
||||
const salt = Utils.fromBufferToB64(await this.cryptoFunctionService.randomBytes(16));
|
||||
const kdfIterations = DEFAULT_PBKDF2_ITERATIONS;
|
||||
const kdfConfig = new KdfConfig(DEFAULT_PBKDF2_ITERATIONS);
|
||||
const key = await this.cryptoService.makePinKey(
|
||||
password,
|
||||
salt,
|
||||
KdfType.PBKDF2_SHA256,
|
||||
kdfIterations
|
||||
kdfConfig
|
||||
);
|
||||
|
||||
const encKeyValidation = await this.cryptoService.encrypt(Utils.newGuid(), key);
|
||||
@@ -69,7 +70,7 @@ export class ExportService implements ExportServiceAbstraction {
|
||||
encrypted: true,
|
||||
passwordProtected: true,
|
||||
salt: salt,
|
||||
kdfIterations: kdfIterations,
|
||||
kdfIterations: kdfConfig.iterations,
|
||||
kdfType: KdfType.PBKDF2_SHA256,
|
||||
encKeyValidation_DO_NOT_EDIT: encKeyValidation.encryptedString,
|
||||
data: encText.encryptedString,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { StateService } from "../abstractions/state.service";
|
||||
import { TokenService } from "../abstractions/token.service";
|
||||
import { OrganizationUserType } from "../enums/organizationUserType";
|
||||
import { Utils } from "../misc/utils";
|
||||
import { KdfConfig } from "../models/domain/kdf-config";
|
||||
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
||||
import { SetKeyConnectorKeyRequest } from "../models/request/account/set-key-connector-key.request";
|
||||
import { KeyConnectorUserKeyRequest } from "../models/request/key-connector-user-key.request";
|
||||
@@ -82,14 +83,14 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
|
||||
}
|
||||
|
||||
async convertNewSsoUserToKeyConnector(tokenResponse: IdentityTokenResponse, orgId: string) {
|
||||
const { kdf, kdfIterations, keyConnectorUrl } = tokenResponse;
|
||||
const { kdf, kdfIterations, kdfMemory, kdfParallelism, keyConnectorUrl } = tokenResponse;
|
||||
const password = await this.cryptoFunctionService.randomBytes(64);
|
||||
|
||||
const k = await this.cryptoService.makeKey(
|
||||
Utils.fromBufferToB64(password),
|
||||
await this.tokenService.getEmail(),
|
||||
kdf,
|
||||
kdfIterations
|
||||
new KdfConfig(kdfIterations, kdfMemory, kdfParallelism)
|
||||
);
|
||||
const keyConnectorRequest = new KeyConnectorUserKeyRequest(k.encKeyB64);
|
||||
await this.cryptoService.setKey(k);
|
||||
|
||||
@@ -36,6 +36,7 @@ import { EncString } from "../models/domain/enc-string";
|
||||
import { EnvironmentUrls } from "../models/domain/environment-urls";
|
||||
import { GeneratedPasswordHistory } from "../models/domain/generated-password-history";
|
||||
import { GlobalState } from "../models/domain/global-state";
|
||||
import { KdfConfig } from "../models/domain/kdf-config";
|
||||
import { Policy } from "../models/domain/policy";
|
||||
import { State } from "../models/domain/state";
|
||||
import { StorageOptions } from "../models/domain/storage-options";
|
||||
@@ -1657,17 +1658,26 @@ export class StateService<
|
||||
return (await this.getAccessToken(options)) != null && (await this.getUserId(options)) != null;
|
||||
}
|
||||
|
||||
async getKdfIterations(options?: StorageOptions): Promise<number> {
|
||||
return (
|
||||
async getKdfConfig(options?: StorageOptions): Promise<KdfConfig> {
|
||||
const iterations = (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||
)?.profile?.kdfIterations;
|
||||
const memory = (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||
)?.profile?.kdfMemory;
|
||||
const parallelism = (
|
||||
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
|
||||
)?.profile?.kdfParallelism;
|
||||
return new KdfConfig(iterations, memory, parallelism);
|
||||
}
|
||||
|
||||
async setKdfIterations(value: number, options?: StorageOptions): Promise<void> {
|
||||
async setKdfConfig(config: KdfConfig, options?: StorageOptions): Promise<void> {
|
||||
const account = await this.getAccount(
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||
);
|
||||
account.profile.kdfIterations = value;
|
||||
account.profile.kdfIterations = config.iterations;
|
||||
account.profile.kdfMemory = config.memory;
|
||||
account.profile.kdfParallelism = config.parallelism;
|
||||
await this.saveAccount(
|
||||
account,
|
||||
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||
|
||||
Reference in New Issue
Block a user