mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 15:53:27 +00:00
[PM-23230] Implement KDF Change Service (#15748)
* Add new mp service api * Fix tests * Add test coverage * Add newline * Fix type * Rename to "unwrapUserKeyFromMasterPasswordUnlockData" * Fix build * Fix build on cli * Fix linting * Re-sort spec * Add tests * Fix test and build issues * Fix build * Clean up * Remove introduced function * Clean up comments * Fix abstract class types * Fix comments * Cleanup * Cleanup * Update libs/common/src/key-management/master-password/types/master-password.types.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Update libs/common/src/key-management/master-password/services/master-password.service.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Update libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Update libs/common/src/key-management/master-password/types/master-password.types.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Update libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Add comments * Fix build * Add arg null check * Cleanup * Fix build * Fix build on browser * Implement KDF change service * Deprecate encryptUserKeyWithMasterKey * Update libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Update libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Update libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Add tests for null params * Fix builds * Cleanup and deprecate more functions * Fix formatting * Prettier * Clean up * Update libs/key-management/src/abstractions/key.service.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Make emailToSalt private and expose abstract saltForUser * Add tests * Add docs * Fix build * Fix tests * Fix tests * Address feedback and fix primitive obsession * Consolidate active account checks in change kdf confirmation component * Update libs/common/src/key-management/kdf/services/change-kdf-service.spec.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Add defensive parameter checks * Add tests * Add comment for follow-up epic * Move change kdf service, remove abstraction and add api service * Fix test * Drop redundant null check * Address feedback * Add throw on empty password * Fix tests * Mark change kdf service as internal * Add abstract classes * Switch to abstraction * use sdk EncString in MasterPasswordUnlockData * fix remaining tests --------- Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Co-authored-by: Jake Fink <jfink@bitwarden.com>
This commit is contained in:
@@ -131,8 +131,7 @@ describe("WebSetInitialPasswordService", () => {
|
||||
credentials.newPasswordHint,
|
||||
credentials.orgSsoIdentifier,
|
||||
keysRequest,
|
||||
credentials.kdfConfig.kdfType,
|
||||
credentials.kdfConfig.iterations,
|
||||
credentials.kdfConfig,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@ import { Component, Inject } from "@angular/core";
|
||||
import { FormGroup, FormControl, Validators } from "@angular/forms";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { KdfRequest } from "@bitwarden/common/models/request/kdf.request";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { ChangeKdfService } from "@bitwarden/common/key-management/kdf/change-kdf-service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { DIALOG_DATA, ToastService } from "@bitwarden/components";
|
||||
import { KdfConfig, KdfType, KeyService } from "@bitwarden/key-management";
|
||||
import { KdfConfig, KdfType } from "@bitwarden/key-management";
|
||||
|
||||
@Component({
|
||||
selector: "app-change-kdf-confirmation",
|
||||
@@ -28,13 +28,12 @@ export class ChangeKdfConfirmationComponent {
|
||||
loading = false;
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private i18nService: I18nService,
|
||||
private keyService: KeyService,
|
||||
private messagingService: MessagingService,
|
||||
@Inject(DIALOG_DATA) params: { kdf: KdfType; kdfConfig: KdfConfig },
|
||||
private accountService: AccountService,
|
||||
private toastService: ToastService,
|
||||
private changeKdfService: ChangeKdfService,
|
||||
) {
|
||||
this.kdfConfig = params.kdfConfig;
|
||||
this.masterPassword = null;
|
||||
@@ -56,37 +55,17 @@ export class ChangeKdfConfirmationComponent {
|
||||
};
|
||||
|
||||
private async makeKeyAndSaveAsync() {
|
||||
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
|
||||
if (activeAccount == null) {
|
||||
throw new Error("No active account found.");
|
||||
}
|
||||
const masterPassword = this.form.value.masterPassword;
|
||||
|
||||
// Ensure the KDF config is valid.
|
||||
this.kdfConfig.validateKdfConfigForSetting();
|
||||
|
||||
const request = new KdfRequest();
|
||||
request.kdf = this.kdfConfig.kdfType;
|
||||
request.kdfIterations = this.kdfConfig.iterations;
|
||||
if (this.kdfConfig.kdfType === KdfType.Argon2id) {
|
||||
request.kdfMemory = this.kdfConfig.memory;
|
||||
request.kdfParallelism = this.kdfConfig.parallelism;
|
||||
}
|
||||
const masterKey = await this.keyService.getOrDeriveMasterKey(masterPassword, activeAccount.id);
|
||||
request.masterPasswordHash = await this.keyService.hashMasterKey(masterPassword, masterKey);
|
||||
const activeAccountId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||
|
||||
const newMasterKey = await this.keyService.makeMasterKey(
|
||||
await this.changeKdfService.updateUserKdfParams(
|
||||
masterPassword,
|
||||
activeAccount.email,
|
||||
this.kdfConfig,
|
||||
activeAccountId,
|
||||
);
|
||||
request.newMasterPasswordHash = await this.keyService.hashMasterKey(
|
||||
masterPassword,
|
||||
newMasterKey,
|
||||
);
|
||||
const newUserKey = await this.keyService.encryptUserKeyWithMasterKey(newMasterKey);
|
||||
request.key = newUserKey[1].encryptedString;
|
||||
|
||||
await this.apiService.postAccountKdf(request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { firstValueFrom, Observable } from "rxjs";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { Account } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
@@ -7,6 +7,7 @@ import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/a
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction";
|
||||
import { firstValueFromOrThrow } from "@bitwarden/common/key-management/utils";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -103,18 +104,18 @@ export class UserKeyRotationService {
|
||||
}
|
||||
|
||||
// Read current cryptographic state / settings
|
||||
const masterKeyKdfConfig: KdfConfig = (await this.firstValueFromOrThrow(
|
||||
const masterKeyKdfConfig: KdfConfig = (await firstValueFromOrThrow(
|
||||
this.kdfConfigService.getKdfConfig$(user.id),
|
||||
"KDF config",
|
||||
))!;
|
||||
// The masterkey salt used for deriving the masterkey always needs to be trimmed and lowercased.
|
||||
const masterKeySalt = user.email.trim().toLowerCase();
|
||||
const currentUserKey: UserKey = (await this.firstValueFromOrThrow(
|
||||
const currentUserKey: UserKey = (await firstValueFromOrThrow(
|
||||
this.keyService.userKey$(user.id),
|
||||
"User key",
|
||||
))!;
|
||||
const currentUserKeyWrappedPrivateKey = new EncString(
|
||||
(await this.firstValueFromOrThrow(
|
||||
(await firstValueFromOrThrow(
|
||||
this.keyService.userEncryptedPrivateKey$(user.id),
|
||||
"User encrypted private key",
|
||||
))!,
|
||||
@@ -515,12 +516,4 @@ export class UserKeyRotationService {
|
||||
HashPurpose.ServerAuthorization,
|
||||
);
|
||||
}
|
||||
|
||||
async firstValueFromOrThrow<T>(value: Observable<T>, name: string): Promise<T> {
|
||||
const result = await firstValueFrom(value);
|
||||
if (result == null) {
|
||||
throw new Error(`Failed to get ${name}`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user