1
0
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:
Bernd Schoolmann
2023-01-30 15:07:51 +01:00
committed by GitHub
parent b1a1068906
commit 01091fe260
35 changed files with 329 additions and 143 deletions

View File

@@ -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> {

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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);

View File

@@ -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())