diff --git a/libs/auth/src/angular/change-existing-password/change-existing-password.component.ts b/libs/auth/src/angular/change-existing-password/change-existing-password.component.ts index a11592553fa..4625fb70d80 100644 --- a/libs/auth/src/angular/change-existing-password/change-existing-password.component.ts +++ b/libs/auth/src/angular/change-existing-password/change-existing-password.component.ts @@ -4,6 +4,10 @@ import { firstValueFrom, map } from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; +import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; +import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { KeyService } from "@bitwarden/key-management"; import { InputPasswordComponent, @@ -25,6 +29,9 @@ export class ChangeExistingPasswordComponent implements OnInit { constructor( private accountService: AccountService, + private keyService: KeyService, + private masterPasswordApiService: MasterPasswordApiService, + private masterPasswordService: MasterPasswordServiceAbstraction, private policyService: PolicyService, ) {} @@ -38,7 +45,24 @@ export class ChangeExistingPasswordComponent implements OnInit { ); } - handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) { + async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) { // TODO-rr-bw: Full Sync if Rotate User Key is true (see setupSubmitActions) + // const request = new PasswordRequest(); + // // request.masterPasswordHash = await this.keyService.hashMasterKey(passwordInputResult.currentPassword) + // request.masterPasswordHash = passwordInputResult.hint; + // request.newMasterPasswordHash = passwordInputResult.masterKeyHash; + // // request.key = + // await this.masterPasswordApiService.postPassword(request); + // if (passwordInputResult.rotateAccountEncryptionKey) { + // // We need to save this for local masterkey verification during rotation + // await this.masterPasswordService.setMasterKeyHash(newLocalKeyHash, userId as UserId); + // await this.masterPasswordService.setMasterKey(newMasterKey, userId as UserId); + // return this.updateKey(); + // } } + + // private async updateKey() { + // const user = await firstValueFrom(this.accountService.activeAccount$); + // await this.userKeyRotationService.rotateUserKeyAndEncryptedData(this.masterPassword, user); + // } } diff --git a/libs/auth/src/angular/input-password/input-password.component.ts b/libs/auth/src/angular/input-password/input-password.component.ts index bffb8a56b5e..c890682ff5b 100644 --- a/libs/auth/src/angular/input-password/input-password.component.ts +++ b/libs/auth/src/angular/input-password/input-password.component.ts @@ -1,5 +1,6 @@ import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { ReactiveFormsModule, FormBuilder, Validators, FormGroup } from "@angular/forms"; +import { firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { @@ -9,6 +10,9 @@ import { import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { HashPurpose } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -23,7 +27,7 @@ import { ToastService, Translation, } from "@bitwarden/components"; -import { DEFAULT_KDF_CONFIG, KeyService } from "@bitwarden/key-management"; +import { DEFAULT_KDF_CONFIG, KdfConfigService, KeyService } from "@bitwarden/key-management"; // FIXME: remove `src` and fix import // eslint-disable-next-line no-restricted-imports @@ -127,12 +131,39 @@ export class InputPasswordComponent implements OnInit { }, ); + get currentPassword() { + return this.formGroup.controls.currentPassword.value; + } + + get newPassword() { + return this.formGroup.controls.newPassword.value; + } + + get confirmNewPassword() { + return this.formGroup.controls.confirmNewPassword.value; + } + + get hint() { + return this.formGroup.controls.hint.value; + } + + get checkForBreaches() { + return this.formGroup.controls.checkForBreaches.value; + } + + get rotateAccountEncryptionKey() { + return this.formGroup.controls.rotateAccountEncryptionKey.value; + } + constructor( + private accountService: AccountService, private auditService: AuditService, - private keyService: KeyService, private dialogService: DialogService, private formBuilder: FormBuilder, private i18nService: I18nService, + private kdfConfigService: KdfConfigService, + private keyService: KeyService, + private masterPasswordService: MasterPasswordServiceAbstraction, private policyService: PolicyService, private toastService: ToastService, ) {} @@ -195,46 +226,53 @@ export class InputPasswordComponent implements OnInit { return; } - const newPassword = this.formGroup.controls.newPassword.value; + // 1. Evaluate current password + if ( + this.inputPasswordFlow === InputPasswordFlow.ChangeExistingPassword || + this.inputPasswordFlow === + InputPasswordFlow.ChangeExistingPasswordAndOptionallyRotateAccountEncryptionKey + ) { + const currentPasswordEvaluatedSuccessfully = await this.evaluateCurrentPassword(); + if (!currentPasswordEvaluatedSuccessfully) { + return; + } + } - const passwordEvaluatedSuccessfully = await this.evaluateNewPassword( - newPassword, + // 2. Evaluate new password + const newPasswordEvaluatedSuccessfully = await this.evaluateNewPassword( + this.newPassword, this.passwordStrengthScore, - this.formGroup.controls.checkForBreaches.value, + this.checkForBreaches, ); - - if (!passwordEvaluatedSuccessfully) { + if (!newPasswordEvaluatedSuccessfully) { return; } - // Create and hash new master key - const kdfConfig = DEFAULT_KDF_CONFIG; - + // 3. Create cryptographic keys if (this.email == null) { throw new Error("Email is required to create master key."); } + const kdfConfig = (await this.kdfConfigService.getKdfConfig()) || DEFAULT_KDF_CONFIG; // TODO-rr-bw: confirm this + const masterKey = await this.keyService.makeMasterKey( - newPassword, + this.newPassword, this.email.trim().toLowerCase(), kdfConfig, ); - const serverMasterKeyHash = await this.keyService.hashMasterKey( - newPassword, - masterKey, - HashPurpose.ServerAuthorization, - ); + const masterKeyHash = await this.keyService.hashMasterKey(this.newPassword, masterKey); const localMasterKeyHash = await this.keyService.hashMasterKey( - newPassword, + this.newPassword, masterKey, HashPurpose.LocalAuthorization, ); + // 3. Emit cryptographic keys and other password related properties const passwordInputResult: PasswordInputResult = { - newPassword, - hint: this.formGroup.controls.hint.value, + newPassword: this.newPassword, + hint: this.hint, kdfConfig, masterKey, serverMasterKeyHash, @@ -245,22 +283,50 @@ export class InputPasswordComponent implements OnInit { this.inputPasswordFlow === InputPasswordFlow.ChangePassword || this.inputPasswordFlow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation ) { - passwordInputResult.currentPassword = this.formGroup.get("currentPassword")?.value; + passwordInputResult.currentPassword = this.currentPassword; } - if (this.inputPasswordFlow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation) { - passwordInputResult.rotateUserKey = this.formGroup.get("rotateUserKey")?.value; + if ( + this.inputPasswordFlow === + InputPasswordFlow.ChangeExistingPasswordAndOptionallyRotateAccountEncryptionKey + ) { + passwordInputResult.rotateAccountEncryptionKey = this.rotateAccountEncryptionKey; } this.onPasswordFormSubmit.emit(passwordInputResult); }; + // Returns true if the current password is correct, false otherwise + private async evaluateCurrentPassword(): Promise { + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + + const masterKey = await this.keyService.makeMasterKey( + this.currentPassword, + await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.email))), + await this.kdfConfigService.getKdfConfig(), + ); + + const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(masterKey, userId); + + if (userKey == null) { + this.toastService.showToast({ + variant: "error", + title: null, + message: this.i18nService.t("invalidMasterPassword"), + }); + + return false; + } + + return true; + } + // Returns true if the password passes all checks, false otherwise private async evaluateNewPassword( newPassword: string, passwordStrengthScore: PasswordStrengthScore, checkForBreaches: boolean, - ) { + ): Promise { // Check if the password is breached, weak, or both const passwordIsBreached = checkForBreaches && (await this.auditService.passwordLeaked(newPassword)); diff --git a/libs/auth/src/angular/input-password/password-input-result.ts b/libs/auth/src/angular/input-password/password-input-result.ts index c64225e2eb5..9f29d61e9ca 100644 --- a/libs/auth/src/angular/input-password/password-input-result.ts +++ b/libs/auth/src/angular/input-password/password-input-result.ts @@ -1,10 +1,10 @@ import { MasterKey } from "@bitwarden/common/types/key"; -import { PBKDF2KdfConfig } from "@bitwarden/key-management"; +import { KdfConfig } from "@bitwarden/key-management"; export interface PasswordInputResult { newPassword: string; hint: string; - kdfConfig: PBKDF2KdfConfig; + kdfConfig: KdfConfig; masterKey: MasterKey; serverMasterKeyHash: string; localMasterKeyHash: string; diff --git a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts index 2f96972d88d..3e800f57403 100644 --- a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts +++ b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts @@ -75,7 +75,7 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService { hint, orgSsoIdentifier, keysRequest, - kdfConfig.kdfType, // kdfConfig is always DEFAULT_KDF_CONFIG (see InputPasswordComponent) + kdfConfig.kdfType, kdfConfig.iterations, );