From f2edf37cf3be066af85a4860c33f495344954124 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 23 Jul 2025 12:54:54 +0200 Subject: [PATCH] tmp --- .../auth/popup/set-password.component.html | 160 ---------- .../src/auth/popup/set-password.component.ts | 10 - .../popup/update-temp-password.component.html | 142 --------- .../popup/update-temp-password.component.ts | 30 -- .../src/auth/set-password.component.html | 169 ---------- .../src/auth/set-password.component.ts | 111 ------- .../auth/update-temp-password.component.html | 136 -------- .../auth/update-temp-password.component.ts | 10 - ...rganization-user-reset-password.service.ts | 56 ++-- .../web-registration-finish.service.spec.ts | 22 +- .../src/app/auth/set-password.component.html | 130 -------- .../src/app/auth/set-password.component.ts | 30 -- .../account/change-email.component.ts | 58 ++-- .../settings/change-password.component.html | 129 -------- .../settings/change-password.component.ts | 258 --------------- .../emergency-access.component.ts | 4 - .../emergency-access-takeover.component.html | 54 ---- .../emergency-access-takeover.component.ts | 145 --------- .../change-kdf-confirmation.component.ts | 34 +- .../password-settings.component.ts | 5 +- .../security/security-routing.module.ts | 16 +- .../src/app/auth/settings/settings.module.ts | 7 +- .../two-factor-setup-method-base.component.ts | 5 +- .../app/auth/update-password.component.html | 90 ------ .../src/app/auth/update-password.component.ts | 24 -- .../auth/update-temp-password.component.html | 96 ------ .../auth/update-temp-password.component.ts | 10 - apps/web/src/app/oss-routing.module.ts | 66 +--- .../src/app/shared/loose-components.module.ts | 14 +- .../organization-options.component.ts | 5 +- .../organization-auth-request.service.ts | 2 +- ...rganization-user-reset-password.request.ts | 16 +- .../responses/organization-user.response.ts | 36 ++- .../components/change-password.component.ts | 232 -------------- .../auth/components/set-password.component.ts | 300 ------------------ .../components/update-password.component.ts | 141 -------- .../update-temp-password.component.ts | 232 -------------- .../change-password.component.html | 28 -- .../change-password.component.ts | 203 ------------ .../default-change-password.service.spec.ts | 2 +- .../default-change-password.service.ts | 85 ++--- .../change-password/index.ts | 1 - ...initial-password.service.implementation.ts | 101 +++--- ...fault-set-initial-password.service.spec.ts | 2 - .../set-initial-password.component.ts | 26 +- ...et-initial-password.service.abstraction.ts | 16 +- .../src/services/jslib-services.module.ts | 13 +- libs/auth/src/angular/index.ts | 1 - .../input-password.component.ts | 57 +--- .../input-password/password-input-result.ts | 13 +- ...efault-registration-finish.service.spec.ts | 6 +- .../default-registration-finish.service.ts | 37 ++- .../default-set-password-jit.service.spec.ts | 8 +- .../default-set-password-jit.service.ts | 86 +++-- .../set-password-jit.component.html | 24 -- .../set-password-jit.component.ts | 135 -------- .../two-factor-auth-email.component.ts | 5 +- .../models/request/email-token.request.ts | 1 - .../src/auth/models/request/email.request.ts | 15 +- .../auth/models/request/password.request.ts | 23 +- .../registration/register-finish.request.ts | 42 ++- .../request/secret-verification.request.ts | 5 +- .../models/request/set-password.request.ts | 56 ++-- .../user-verification.service.ts | 22 +- libs/common/src/auth/types/verification.ts | 5 +- .../crypto/models/enc-string.ts | 15 + .../device-trust.service.abstraction.ts | 3 +- .../device-trust.service.implementation.ts | 7 +- .../kdf/abstractions/change-kdf-service.ts | 8 + .../kdf/services/change-kdf-service.ts | 23 ++ .../master-password.service.abstraction.ts | 4 + .../services/fake-master-password.service.ts | 4 + .../services/master-password.service.ts | 11 +- libs/common/src/models/request/kdf.request.ts | 27 +- .../src/abstractions/key.service.ts | 8 + libs/key-management/src/key.service.ts | 5 + libs/key-management/src/models/kdf-config.ts | 15 + 77 files changed, 541 insertions(+), 3592 deletions(-) delete mode 100644 apps/browser/src/auth/popup/set-password.component.html delete mode 100644 apps/browser/src/auth/popup/set-password.component.ts delete mode 100644 apps/browser/src/auth/popup/update-temp-password.component.html delete mode 100644 apps/browser/src/auth/popup/update-temp-password.component.ts delete mode 100644 apps/desktop/src/auth/set-password.component.html delete mode 100644 apps/desktop/src/auth/set-password.component.ts delete mode 100644 apps/desktop/src/auth/update-temp-password.component.html delete mode 100644 apps/desktop/src/auth/update-temp-password.component.ts delete mode 100644 apps/web/src/app/auth/set-password.component.html delete mode 100644 apps/web/src/app/auth/set-password.component.ts delete mode 100644 apps/web/src/app/auth/settings/change-password.component.html delete mode 100644 apps/web/src/app/auth/settings/change-password.component.ts delete mode 100644 apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.html delete mode 100644 apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts delete mode 100644 apps/web/src/app/auth/update-password.component.html delete mode 100644 apps/web/src/app/auth/update-password.component.ts delete mode 100644 apps/web/src/app/auth/update-temp-password.component.html delete mode 100644 apps/web/src/app/auth/update-temp-password.component.ts delete mode 100644 libs/angular/src/auth/components/change-password.component.ts delete mode 100644 libs/angular/src/auth/components/set-password.component.ts delete mode 100644 libs/angular/src/auth/components/update-password.component.ts delete mode 100644 libs/angular/src/auth/components/update-temp-password.component.ts delete mode 100644 libs/angular/src/auth/password-management/change-password/change-password.component.html delete mode 100644 libs/angular/src/auth/password-management/change-password/change-password.component.ts delete mode 100644 libs/auth/src/angular/set-password-jit/set-password-jit.component.html delete mode 100644 libs/auth/src/angular/set-password-jit/set-password-jit.component.ts create mode 100644 libs/common/src/key-management/kdf/abstractions/change-kdf-service.ts create mode 100644 libs/common/src/key-management/kdf/services/change-kdf-service.ts diff --git a/apps/browser/src/auth/popup/set-password.component.html b/apps/browser/src/auth/popup/set-password.component.html deleted file mode 100644 index 71a2e3ac588..00000000000 --- a/apps/browser/src/auth/popup/set-password.component.html +++ /dev/null @@ -1,160 +0,0 @@ -
-
-
- -
-

- {{ "setMasterPassword" | i18n }} -

-
- -
-
-
-
- -
-
-
-

- {{ "orgPermissionsUpdatedMustSetPassword" | i18n }} -

- - -

{{ "orgRequiresYouToSetPassword" | i18n }}

-
- - - {{ "resetPasswordAutoEnrollInviteWarning" | i18n }} - - - -
-
-
-
-
-
- - -
-
- -
-
- - - -
-
- -
-
-
-
-
-
- - -
-
- -
-
-
-
-
-
-
-
- - -
-
- -
-
-
-
diff --git a/apps/browser/src/auth/popup/set-password.component.ts b/apps/browser/src/auth/popup/set-password.component.ts deleted file mode 100644 index 2a796854531..00000000000 --- a/apps/browser/src/auth/popup/set-password.component.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Component } from "@angular/core"; - -import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component"; - -@Component({ - selector: "app-set-password", - templateUrl: "set-password.component.html", - standalone: false, -}) -export class SetPasswordComponent extends BaseSetPasswordComponent {} diff --git a/apps/browser/src/auth/popup/update-temp-password.component.html b/apps/browser/src/auth/popup/update-temp-password.component.html deleted file mode 100644 index 0ce82aa20cf..00000000000 --- a/apps/browser/src/auth/popup/update-temp-password.component.html +++ /dev/null @@ -1,142 +0,0 @@ -
-
-
- -
-

- {{ "updateMasterPassword" | i18n }} -

-
- -
-
-
- - {{ masterPasswordWarningText }} - - - -
-
-
-
-
- - -
-
-
-
-
-
-
-
-
-
- - -
-
- -
-
- - -
-
-
-
-
-
-
- - -
-
- -
-
-
-
-
-
-
- - -
-
- -
-
-
diff --git a/apps/browser/src/auth/popup/update-temp-password.component.ts b/apps/browser/src/auth/popup/update-temp-password.component.ts deleted file mode 100644 index e8cf64b7548..00000000000 --- a/apps/browser/src/auth/popup/update-temp-password.component.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Component } from "@angular/core"; -import { firstValueFrom } from "rxjs"; - -import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "@bitwarden/angular/auth/components/update-temp-password.component"; - -import { postLogoutMessageListener$ } from "./utils/post-logout-message-listener"; - -@Component({ - selector: "app-update-temp-password", - templateUrl: "update-temp-password.component.html", - standalone: false, -}) -export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent { - onSuccessfulChangePassword: () => Promise = this.doOnSuccessfulChangePassword.bind(this); - - private async doOnSuccessfulChangePassword() { - // start listening for "switchAccountFinish" or "doneLoggingOut" - const messagePromise = firstValueFrom(postLogoutMessageListener$); - this.messagingService.send("logout"); - // wait for messages - const command = await messagePromise; - - // doneLoggingOut already has a message handler that will navigate us - if (command === "switchAccountFinish") { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["/"]); - } - } -} diff --git a/apps/desktop/src/auth/set-password.component.html b/apps/desktop/src/auth/set-password.component.html deleted file mode 100644 index 46d954327f8..00000000000 --- a/apps/desktop/src/auth/set-password.component.html +++ /dev/null @@ -1,169 +0,0 @@ -
-
- Bitwarden -

{{ "setMasterPassword" | i18n }}

-
- - {{ "loading" | i18n }} -
-
-
-

- {{ "orgPermissionsUpdatedMustSetPassword" | i18n }} -

- - -

{{ "orgRequiresYouToSetPassword" | i18n }}

-
- - - {{ "resetPasswordAutoEnrollInviteWarning" | i18n }} - - - -
- -
-
-
-
-
- - -
-
- -
-
- - -
-
- -
-
-
-
-
-
- - -
-
- -
-
-
-
-
-
-
-
- - -
-
- -
-
- - -
- -
-
- diff --git a/apps/desktop/src/auth/set-password.component.ts b/apps/desktop/src/auth/set-password.component.ts deleted file mode 100644 index d45fc111a97..00000000000 --- a/apps/desktop/src/auth/set-password.component.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; - -import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; -import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component"; -import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; -import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; -import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { MasterKey, UserKey } from "@bitwarden/common/types/key"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KdfConfigService, KeyService } from "@bitwarden/key-management"; - -const BroadcasterSubscriptionId = "SetPasswordComponent"; - -@Component({ - selector: "app-set-password", - templateUrl: "set-password.component.html", - standalone: false, -}) -export class SetPasswordComponent extends BaseSetPasswordComponent implements OnInit, OnDestroy { - constructor( - protected accountService: AccountService, - protected dialogService: DialogService, - protected encryptService: EncryptService, - protected i18nService: I18nService, - protected kdfConfigService: KdfConfigService, - protected keyService: KeyService, - protected masterPasswordApiService: MasterPasswordApiService, - protected masterPasswordService: InternalMasterPasswordServiceAbstraction, - protected messagingService: MessagingService, - protected organizationApiService: OrganizationApiServiceAbstraction, - protected organizationUserApiService: OrganizationUserApiService, - protected platformUtilsService: PlatformUtilsService, - protected policyApiService: PolicyApiServiceAbstraction, - protected policyService: PolicyService, - protected route: ActivatedRoute, - protected router: Router, - protected ssoLoginService: SsoLoginServiceAbstraction, - protected syncService: SyncService, - protected toastService: ToastService, - protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, - private broadcasterService: BroadcasterService, - private ngZone: NgZone, - ) { - super( - accountService, - dialogService, - encryptService, - i18nService, - kdfConfigService, - keyService, - masterPasswordApiService, - masterPasswordService, - messagingService, - organizationApiService, - organizationUserApiService, - platformUtilsService, - policyApiService, - policyService, - route, - router, - ssoLoginService, - syncService, - toastService, - userDecryptionOptionsService, - ); - } - - async ngOnInit() { - await super.ngOnInit(); - this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message) => { - this.ngZone.run(() => { - switch (message.command) { - case "windowHidden": - this.onWindowHidden(); - break; - default: - } - }); - }); - } - - ngOnDestroy() { - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - } - - onWindowHidden() { - this.showPassword = false; - } - - protected async onSetPasswordSuccess( - masterKey: MasterKey, - userKey: [UserKey, EncString], - keyPair: [string, EncString], - ): Promise { - await super.onSetPasswordSuccess(masterKey, userKey, keyPair); - this.messagingService.send("redrawMenu"); - } -} diff --git a/apps/desktop/src/auth/update-temp-password.component.html b/apps/desktop/src/auth/update-temp-password.component.html deleted file mode 100644 index 11b8ad4361b..00000000000 --- a/apps/desktop/src/auth/update-temp-password.component.html +++ /dev/null @@ -1,136 +0,0 @@ -
-
- - {{ masterPasswordWarningText }} - - - -
-
-
-
-
- - -
-
-
-
-
-
-
-
-
-
- - -
-
- -
-
- - -
-
-
-
-
-
-
- - -
-
- -
-
-
-
-
-
-
- - -
-
- -
-
- - -
-
-
diff --git a/apps/desktop/src/auth/update-temp-password.component.ts b/apps/desktop/src/auth/update-temp-password.component.ts deleted file mode 100644 index ead10660b92..00000000000 --- a/apps/desktop/src/auth/update-temp-password.component.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Component } from "@angular/core"; - -import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "@bitwarden/angular/auth/components/update-temp-password.component"; - -@Component({ - selector: "app-update-temp-password", - templateUrl: "update-temp-password.component.html", - standalone: false, -}) -export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {} diff --git a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts index a1727a8cc59..3dde22abbd6 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts @@ -15,17 +15,15 @@ import { EncryptedString, EncString, } from "@bitwarden/common/key-management/crypto/models/enc-string"; +import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { MasterPasswordSalt } from "@bitwarden/common/key-management/master-password/types/master-password.types"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; import { - Argon2KdfConfig, - KdfConfig, - PBKDF2KdfConfig, UserKeyRotationKeyRecoveryProvider, KeyService, - KdfType, } from "@bitwarden/key-management"; import { OrganizationUserResetPasswordEntry } from "./organization-user-reset-password-entry"; @@ -35,11 +33,10 @@ import { OrganizationUserResetPasswordEntry } from "./organization-user-reset-pa }) export class OrganizationUserResetPasswordService implements - UserKeyRotationKeyRecoveryProvider< - OrganizationUserResetPasswordWithIdRequest, - OrganizationUserResetPasswordEntry - > -{ + UserKeyRotationKeyRecoveryProvider< + OrganizationUserResetPasswordWithIdRequest, + OrganizationUserResetPasswordEntry + > { constructor( private keyService: KeyService, private encryptService: EncryptService, @@ -47,7 +44,8 @@ export class OrganizationUserResetPasswordService private organizationUserApiService: OrganizationUserApiService, private organizationApiService: OrganizationApiServiceAbstraction, private i18nService: I18nService, - ) {} + private masterPasswordService: MasterPasswordServiceAbstraction, + ) { } /** * Builds a recovery key for a user to recover their account. @@ -109,48 +107,34 @@ export class OrganizationUserResetPasswordService if (response == null) { throw new Error(this.i18nService.t("resetPasswordDetailsError")); } + // TODO: Salt should come from server, since it will be decoupled from email + const salt = email.toLowerCase().trim() as MasterPasswordSalt; // Decrypt Organization's encrypted Private Key with org key const orgSymKey = await this.keyService.getOrgKey(orgId); if (orgSymKey == null) { throw new Error("No org key found"); } + const decPrivateKey = await this.encryptService.unwrapDecapsulationKey( - new EncString(response.encryptedPrivateKey), + EncString.fromEncryptedString(response.encryptedPrivateKey), orgSymKey, ); // Decrypt User's Reset Password Key to get UserKey const userKey = await this.encryptService.decapsulateKeyUnsigned( - new EncString(response.resetPasswordKey), + EncString.fromEncryptedString(response.resetPasswordKey), decPrivateKey, - ); - const existingUserKey = userKey as UserKey; + ) as UserKey; - // determine Kdf Algorithm - const kdfConfig: KdfConfig = - response.kdf === KdfType.PBKDF2_SHA256 - ? new PBKDF2KdfConfig(response.kdfIterations) - : new Argon2KdfConfig(response.kdfIterations, response.kdfMemory, response.kdfParallelism); - - // Create new master key and hash new password - const newMasterKey = await this.keyService.makeMasterKey( - newMasterPassword, - email.trim().toLowerCase(), - kdfConfig, - ); - const newMasterKeyHash = await this.keyService.hashMasterKey(newMasterPassword, newMasterKey); - - // Create new encrypted user key for the User - const newUserKey = await this.keyService.encryptUserKeyWithMasterKey( - newMasterKey, - existingUserKey, - ); + const authenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData(newMasterPassword, response.kdf, salt); + const unlockData = await this.masterPasswordService.makeMasterPasswordUnlockData(newMasterPassword, response.kdf, salt, userKey); // Create request - const request = new OrganizationUserResetPasswordRequest(); - request.key = newUserKey[1].encryptedString; - request.newMasterPasswordHash = newMasterKeyHash; + const request = new OrganizationUserResetPasswordRequest( + authenticationData, + unlockData, + ); // Change user's password await this.organizationUserApiService.putOrganizationUserResetPassword( diff --git a/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts b/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts index 69a2f27a322..b3b4ed720bd 100644 --- a/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts +++ b/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts @@ -184,7 +184,7 @@ describe("WebRegistrationFinishService", () => { newMasterKey: masterKey, newServerMasterKeyHash: "newServerMasterKeyHash", newLocalMasterKeyHash: "newLocalMasterKeyHash", - kdfConfig: DEFAULT_KDF_CONFIG, + kdf: DEFAULT_KDF_CONFIG, newPasswordHint: "newPasswordHint", newPassword: "newPassword", }; @@ -234,8 +234,8 @@ describe("WebRegistrationFinishService", () => { publicKey: userKeyPair[0], encryptedPrivateKey: userKeyPair[1].encryptedString, }, - kdf: passwordInputResult.kdfConfig.kdfType, - kdfIterations: passwordInputResult.kdfConfig.iterations, + kdf: passwordInputResult.kdf.kdfType, + kdfIterations: passwordInputResult.kdf.iterations, kdfMemory: undefined, kdfParallelism: undefined, orgInviteToken: undefined, @@ -270,8 +270,8 @@ describe("WebRegistrationFinishService", () => { publicKey: userKeyPair[0], encryptedPrivateKey: userKeyPair[1].encryptedString, }, - kdf: passwordInputResult.kdfConfig.kdfType, - kdfIterations: passwordInputResult.kdfConfig.iterations, + kdf: passwordInputResult.kdf.kdfType, + kdfIterations: passwordInputResult.kdf.iterations, kdfMemory: undefined, kdfParallelism: undefined, orgInviteToken: orgInvite.token, @@ -311,8 +311,8 @@ describe("WebRegistrationFinishService", () => { publicKey: userKeyPair[0], encryptedPrivateKey: userKeyPair[1].encryptedString, }, - kdf: passwordInputResult.kdfConfig.kdfType, - kdfIterations: passwordInputResult.kdfConfig.iterations, + kdf: passwordInputResult.kdf.kdfType, + kdfIterations: passwordInputResult.kdf.iterations, kdfMemory: undefined, kdfParallelism: undefined, orgInviteToken: undefined, @@ -354,8 +354,8 @@ describe("WebRegistrationFinishService", () => { publicKey: userKeyPair[0], encryptedPrivateKey: userKeyPair[1].encryptedString, }, - kdf: passwordInputResult.kdfConfig.kdfType, - kdfIterations: passwordInputResult.kdfConfig.iterations, + kdf: passwordInputResult.kdf.kdfType, + kdfIterations: passwordInputResult.kdf.iterations, kdfMemory: undefined, kdfParallelism: undefined, orgInviteToken: undefined, @@ -399,8 +399,8 @@ describe("WebRegistrationFinishService", () => { publicKey: userKeyPair[0], encryptedPrivateKey: userKeyPair[1].encryptedString, }, - kdf: passwordInputResult.kdfConfig.kdfType, - kdfIterations: passwordInputResult.kdfConfig.iterations, + kdf: passwordInputResult.kdf.kdfType, + kdfIterations: passwordInputResult.kdf.iterations, kdfMemory: undefined, kdfParallelism: undefined, orgInviteToken: undefined, diff --git a/apps/web/src/app/auth/set-password.component.html b/apps/web/src/app/auth/set-password.component.html deleted file mode 100644 index 252893d22cb..00000000000 --- a/apps/web/src/app/auth/set-password.component.html +++ /dev/null @@ -1,130 +0,0 @@ -
-
-
-

{{ "setMasterPassword" | i18n }}

-
-
- - {{ "loading" | i18n }} -
-
-

- {{ "orgPermissionsUpdatedMustSetPassword" | i18n }} -

- - -

{{ "orgRequiresYouToSetPassword" | i18n }}

-
- - - {{ "resetPasswordAutoEnrollInviteWarning" | i18n }} - -
- - - -
-
- - - -
-
- - -
-
- {{ "masterPassDesc" | i18n }} -
-
- -
- - -
-
-
- - - {{ "masterPassHintDesc" | i18n }} -
-
-
- - -
-
-
-
-
-
diff --git a/apps/web/src/app/auth/set-password.component.ts b/apps/web/src/app/auth/set-password.component.ts deleted file mode 100644 index a2044e298a5..00000000000 --- a/apps/web/src/app/auth/set-password.component.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Component, inject } from "@angular/core"; - -import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component"; -import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service"; -import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; -import { MasterKey, UserKey } from "@bitwarden/common/types/key"; - -import { RouterService } from "../core"; - -@Component({ - selector: "app-set-password", - templateUrl: "set-password.component.html", - standalone: false, -}) -export class SetPasswordComponent extends BaseSetPasswordComponent { - routerService = inject(RouterService); - organizationInviteService = inject(OrganizationInviteService); - - protected override async onSetPasswordSuccess( - masterKey: MasterKey, - userKey: [UserKey, EncString], - keyPair: [string, EncString], - ): Promise { - await super.onSetPasswordSuccess(masterKey, userKey, keyPair); - // SSO JIT accepts org invites when setting their MP, meaning - // we can clear the deep linked url for accepting it. - await this.routerService.getAndClearLoginRedirectUrl(); - await this.organizationInviteService.clearOrganizationInvitation(); - } -} diff --git a/apps/web/src/app/auth/settings/account/change-email.component.ts b/apps/web/src/app/auth/settings/account/change-email.component.ts index a55846a5c0f..47188406a20 100644 --- a/apps/web/src/app/auth/settings/account/change-email.component.ts +++ b/apps/web/src/app/auth/settings/account/change-email.component.ts @@ -8,6 +8,8 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-p import { EmailTokenRequest } from "@bitwarden/common/auth/models/request/email-token.request"; import { EmailRequest } from "@bitwarden/common/auth/models/request/email.request"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { MasterPasswordSalt } from "@bitwarden/common/key-management/master-password/types/master-password.types"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { UserId } from "@bitwarden/common/types/guid"; @@ -43,7 +45,8 @@ export class ChangeEmailComponent implements OnInit { private formBuilder: FormBuilder, private kdfConfigService: KdfConfigService, private toastService: ToastService, - ) {} + private masterPasswordService: MasterPasswordServiceAbstraction, + ) { } async ngOnInit() { this.userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); @@ -79,15 +82,20 @@ export class ChangeEmailComponent implements OnInit { throw new Error("Missing email or password"); } - const existingHash = await this.keyService.hashMasterKey( - masterPassword, - await this.keyService.getOrDeriveMasterKey(masterPassword, this.userId), - ); + const kdfConfig = await firstValueFrom(this.kdfConfigService.getKdfConfig$(this.userId)); + if (kdfConfig == null) { + throw new Error("Missing kdf config"); + } + const salt = await firstValueFrom(this.masterPasswordService.saltForAccount$(this.userId)); + if (salt == null) { + throw new Error("Missing salt"); + } + const existingAuthenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData(masterPassword, kdfConfig, salt); if (!this.tokenSent) { const request = new EmailTokenRequest(); request.newEmail = newEmail; - request.masterPasswordHash = existingHash; + request.masterPasswordHash = existingAuthenticationData.masterPasswordAuthenticationHash; await this.apiService.postEmailToken(request); this.activateStep2(); } else { @@ -95,31 +103,29 @@ export class ChangeEmailComponent implements OnInit { if (token == null) { throw new Error("Missing token"); } - const request = new EmailRequest(); - request.token = token; - request.newEmail = newEmail; - request.masterPasswordHash = existingHash; - - const kdfConfig = await firstValueFrom(this.kdfConfigService.getKdfConfig$(this.userId)); - if (kdfConfig == null) { - throw new Error("Missing kdf config"); - } - const newMasterKey = await this.keyService.makeMasterKey(masterPassword, newEmail, kdfConfig); - request.newMasterPasswordHash = await this.keyService.hashMasterKey( - masterPassword, - newMasterKey, - ); const userKey = await firstValueFrom(this.keyService.userKey$(this.userId)); if (userKey == null) { throw new Error("Can't find UserKey"); } - const newUserKey = await this.keyService.encryptUserKeyWithMasterKey(newMasterKey, userKey); - const encryptedUserKey = newUserKey[1]?.encryptedString; - if (encryptedUserKey == null) { - throw new Error("Missing Encrypted User Key"); - } - request.key = encryptedUserKey; + + const newSalt = newEmail.toLowerCase().trim() as MasterPasswordSalt; + const newAuthenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData( + masterPassword, + kdfConfig, + newSalt, + ); + const newUnlockData = await this.masterPasswordService.makeMasterPasswordUnlockData( + masterPassword, + kdfConfig, + newSalt, + userKey + ); + + const request = new EmailRequest(newAuthenticationData, newUnlockData); + request.token = token; + request.newEmail = newEmail; + request.masterPasswordHash = existingAuthenticationData.masterPasswordAuthenticationHash; await this.apiService.postEmail(request); this.reset(); diff --git a/apps/web/src/app/auth/settings/change-password.component.html b/apps/web/src/app/auth/settings/change-password.component.html deleted file mode 100644 index 34bb74ee473..00000000000 --- a/apps/web/src/app/auth/settings/change-password.component.html +++ /dev/null @@ -1,129 +0,0 @@ -
-

{{ "changeMasterPassword" | i18n }}

-
- -{{ "loggedOutWarning" | i18n }} - - - -
-
-
-
- - -
-
-
-
-
-
- - - - {{ "important" | i18n }} - {{ "masterPassImportant" | i18n }} {{ characterMinimumMessage }} - - - -
-
-
-
- - -
-
-
-
-
- - -
-
-
-
- - - - - -
-
-
- - -
- -
- - diff --git a/apps/web/src/app/auth/settings/change-password.component.ts b/apps/web/src/app/auth/settings/change-password.component.ts deleted file mode 100644 index ce10a0e5a34..00000000000 --- a/apps/web/src/app/auth/settings/change-password.component.ts +++ /dev/null @@ -1,258 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnDestroy, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; -import { firstValueFrom, map } from "rxjs"; - -import { ChangePasswordComponent as BaseChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component"; -import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; -import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KdfConfigService, KeyService } from "@bitwarden/key-management"; - -import { UserKeyRotationService } from "../../key-management/key-rotation/user-key-rotation.service"; - -/** - * @deprecated use the auth `PasswordSettingsComponent` instead - */ -@Component({ - selector: "app-change-password", - templateUrl: "change-password.component.html", - standalone: false, -}) -export class ChangePasswordComponent - extends BaseChangePasswordComponent - implements OnInit, OnDestroy -{ - loading = false; - rotateUserKey = false; - currentMasterPassword: string; - masterPasswordHint: string; - checkForBreaches = true; - characterMinimumMessage = ""; - - constructor( - private auditService: AuditService, - private cipherService: CipherService, - private keyRotationService: UserKeyRotationService, - private masterPasswordApiService: MasterPasswordApiService, - private router: Router, - private syncService: SyncService, - private userVerificationService: UserVerificationService, - protected accountService: AccountService, - protected dialogService: DialogService, - protected i18nService: I18nService, - protected kdfConfigService: KdfConfigService, - protected keyService: KeyService, - protected masterPasswordService: InternalMasterPasswordServiceAbstraction, - protected messagingService: MessagingService, - protected platformUtilsService: PlatformUtilsService, - protected policyService: PolicyService, - protected toastService: ToastService, - ) { - super( - accountService, - dialogService, - i18nService, - kdfConfigService, - keyService, - masterPasswordService, - messagingService, - platformUtilsService, - policyService, - toastService, - ); - } - - async ngOnInit() { - if (!(await this.userVerificationService.hasMasterPassword())) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["/settings/security/two-factor"]); - } - - await super.ngOnInit(); - - this.characterMinimumMessage = this.i18nService.t("characterMinimum", this.minimumLength); - } - - async rotateUserKeyClicked() { - if (this.rotateUserKey) { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - - const ciphers = await this.cipherService.getAllDecrypted(activeUserId); - let hasOldAttachments = false; - if (ciphers != null) { - for (let i = 0; i < ciphers.length; i++) { - if (ciphers[i].organizationId == null && ciphers[i].hasOldAttachments) { - hasOldAttachments = true; - break; - } - } - } - - if (hasOldAttachments) { - const learnMore = await this.dialogService.openSimpleDialog({ - title: { key: "warning" }, - content: { key: "oldAttachmentsNeedFixDesc" }, - acceptButtonText: { key: "learnMore" }, - cancelButtonText: { key: "close" }, - type: "warning", - }); - - if (learnMore) { - this.platformUtilsService.launchUri( - "https://bitwarden.com/help/attachments/#add-storage-space", - ); - } - this.rotateUserKey = false; - return; - } - - const result = await this.dialogService.openSimpleDialog({ - title: { key: "rotateEncKeyTitle" }, - content: - this.i18nService.t("updateEncryptionKeyWarning") + - " " + - this.i18nService.t("updateEncryptionKeyAccountExportWarning") + - " " + - this.i18nService.t("rotateEncKeyConfirmation"), - type: "warning", - }); - - if (!result) { - this.rotateUserKey = false; - } - } - } - - async submit() { - this.loading = true; - if (this.currentMasterPassword == null || this.currentMasterPassword === "") { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("masterPasswordRequired"), - }); - this.loading = false; - return; - } - - if ( - this.masterPasswordHint != null && - this.masterPasswordHint.toLowerCase() === this.masterPassword.toLowerCase() - ) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("hintEqualsPassword"), - }); - this.loading = false; - return; - } - - this.leakedPassword = false; - if (this.checkForBreaches) { - this.leakedPassword = (await this.auditService.passwordLeaked(this.masterPassword)) > 0; - } - - if (!(await this.strongPassword())) { - this.loading = false; - return; - } - - try { - if (this.rotateUserKey) { - await this.syncService.fullSync(true); - const user = await firstValueFrom(this.accountService.activeAccount$); - await this.keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData( - this.currentMasterPassword, - this.masterPassword, - user, - this.masterPasswordHint, - ); - } else { - await this.updatePassword(this.masterPassword); - } - } catch (e) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: e.message, - }); - } finally { - this.loading = false; - } - } - - // todo: move this to a service - // https://bitwarden.atlassian.net/browse/PM-17108 - private async updatePassword(newMasterPassword: string) { - const currentMasterPassword = this.currentMasterPassword; - const { userId, email } = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => ({ userId: a?.id, email: a?.email }))), - ); - const kdfConfig = await firstValueFrom(this.kdfConfigService.getKdfConfig$(userId)); - - const currentMasterKey = await this.keyService.makeMasterKey( - currentMasterPassword, - email, - kdfConfig, - ); - const decryptedUserKey = await this.masterPasswordService.decryptUserKeyWithMasterKey( - currentMasterKey, - userId, - ); - if (decryptedUserKey == null) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("invalidMasterPassword"), - }); - return; - } - - const newMasterKey = await this.keyService.makeMasterKey(newMasterPassword, email, kdfConfig); - const newMasterKeyEncryptedUserKey = await this.keyService.encryptUserKeyWithMasterKey( - newMasterKey, - decryptedUserKey, - ); - - const request = new PasswordRequest(); - request.masterPasswordHash = await this.keyService.hashMasterKey( - this.currentMasterPassword, - currentMasterKey, - ); - request.masterPasswordHint = this.masterPasswordHint; - request.newMasterPasswordHash = await this.keyService.hashMasterKey( - newMasterPassword, - newMasterKey, - ); - request.key = newMasterKeyEncryptedUserKey[1].encryptedString; - try { - await this.masterPasswordApiService.postPassword(request); - this.toastService.showToast({ - variant: "success", - message: this.i18nService.t("masterPasswordChanged"), - }); - this.messagingService.send("logout"); - } catch { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("errorOccurred"), - }); - } - } -} diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts index 1d78bb7dd17..0c81d1a8b9a 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts @@ -40,10 +40,6 @@ import { EmergencyAccessTakeoverDialogComponent, EmergencyAccessTakeoverDialogResultType, } from "./takeover/emergency-access-takeover-dialog.component"; -import { - EmergencyAccessTakeoverComponent, - EmergencyAccessTakeoverResultType, -} from "./takeover/emergency-access-takeover.component"; @Component({ selector: "emergency-access", diff --git a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.html b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.html deleted file mode 100644 index 64b35344455..00000000000 --- a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.html +++ /dev/null @@ -1,54 +0,0 @@ -
- - - {{ "takeover" | i18n }} - {{ params.name }} - -
- {{ "loggedOutWarning" | i18n }} - - -
-
- - {{ "newMasterPass" | i18n }} - - - - - -
-
- - {{ "confirmNewMasterPass" | i18n }} - - - -
-
-
- - - - -
-
diff --git a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts deleted file mode 100644 index ede60887725..00000000000 --- a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts +++ /dev/null @@ -1,145 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnDestroy, OnInit, Inject, Input } from "@angular/core"; -import { FormBuilder, Validators } from "@angular/forms"; -import { switchMap, takeUntil } from "rxjs"; - -import { ChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { - DialogConfig, - DialogRef, - DIALOG_DATA, - DialogService, - ToastService, -} from "@bitwarden/components"; -import { KdfType, KdfConfigService, KeyService } from "@bitwarden/key-management"; - -import { EmergencyAccessService } from "../../../emergency-access"; - -// FIXME: update to use a const object instead of a typescript enum -// eslint-disable-next-line @bitwarden/platform/no-enums -export enum EmergencyAccessTakeoverResultType { - Done = "done", -} -type EmergencyAccessTakeoverDialogData = { - /** display name of the account requesting emergency access takeover */ - name: string; - /** email of the account requesting emergency access takeover */ - email: string; - /** traces a unique emergency request */ - emergencyAccessId: string; -}; -@Component({ - selector: "emergency-access-takeover", - templateUrl: "emergency-access-takeover.component.html", - standalone: false, -}) -export class EmergencyAccessTakeoverComponent - extends ChangePasswordComponent - implements OnInit, OnDestroy -{ - @Input() kdf: KdfType; - @Input() kdfIterations: number; - takeoverForm = this.formBuilder.group({ - masterPassword: ["", [Validators.required]], - masterPasswordRetype: ["", [Validators.required]], - }); - - constructor( - @Inject(DIALOG_DATA) protected params: EmergencyAccessTakeoverDialogData, - private formBuilder: FormBuilder, - i18nService: I18nService, - keyService: KeyService, - messagingService: MessagingService, - platformUtilsService: PlatformUtilsService, - policyService: PolicyService, - private emergencyAccessService: EmergencyAccessService, - private logService: LogService, - dialogService: DialogService, - private dialogRef: DialogRef, - kdfConfigService: KdfConfigService, - masterPasswordService: InternalMasterPasswordServiceAbstraction, - accountService: AccountService, - protected toastService: ToastService, - ) { - super( - accountService, - dialogService, - i18nService, - kdfConfigService, - keyService, - masterPasswordService, - messagingService, - platformUtilsService, - policyService, - toastService, - ); - } - - async ngOnInit() { - const policies = await this.emergencyAccessService.getGrantorPolicies( - this.params.emergencyAccessId, - ); - this.accountService.activeAccount$ - .pipe( - getUserId, - switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId, policies)), - takeUntil(this.destroy$), - ) - .subscribe((enforcedPolicyOptions) => (this.enforcedPolicyOptions = enforcedPolicyOptions)); - } - - ngOnDestroy(): void { - super.ngOnDestroy(); - } - - submit = async () => { - if (this.takeoverForm.invalid) { - this.takeoverForm.markAllAsTouched(); - return; - } - this.masterPassword = this.takeoverForm.get("masterPassword").value; - this.masterPasswordRetype = this.takeoverForm.get("masterPasswordRetype").value; - if (!(await this.strongPassword())) { - return; - } - - try { - await this.emergencyAccessService.takeover( - this.params.emergencyAccessId, - this.masterPassword, - this.params.email, - ); - } catch (e) { - this.logService.error(e); - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("unexpectedError"), - }); - } - this.dialogRef.close(EmergencyAccessTakeoverResultType.Done); - }; - /** - * Strongly typed helper to open a EmergencyAccessTakeoverComponent - * @param dialogService Instance of the dialog service that will be used to open the dialog - * @param config Configuration for the dialog - */ - static open = ( - dialogService: DialogService, - config: DialogConfig, - ) => { - return dialogService.open( - EmergencyAccessTakeoverComponent, - config, - ); - }; -} diff --git a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf-confirmation.component.ts b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf-confirmation.component.ts index 0bfc46eea96..99c9e2fc876 100644 --- a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf-confirmation.component.ts +++ b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf-confirmation.component.ts @@ -4,14 +4,12 @@ import { Component, Inject } from "@angular/core"; import { FormGroup, FormControl, Validators } from "@angular/forms"; import { firstValueFrom, map } 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 { ChangeKdfServiceAbstraction } from "@bitwarden/common/key-management/kdf/abstractions/change-kdf-service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.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", @@ -29,14 +27,12 @@ export class ChangeKdfConfirmationComponent { loading = false; constructor( - private apiService: ApiService, private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, - private keyService: KeyService, private messagingService: MessagingService, @Inject(DIALOG_DATA) params: { kdf: KdfType; kdfConfig: KdfConfig }, private accountService: AccountService, private toastService: ToastService, + private changeKdfService: ChangeKdfServiceAbstraction, ) { this.kdfConfig = params.kdfConfig; this.masterPassword = null; @@ -63,27 +59,7 @@ export class ChangeKdfConfirmationComponent { // 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); - request.masterPasswordHash = await this.keyService.hashMasterKey(masterPassword, masterKey); - const email = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.email)), - ); - - const newMasterKey = await this.keyService.makeMasterKey(masterPassword, email, this.kdfConfig); - 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); + const activeAccountId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))); + await this.changeKdfService.updateUserKdfParams(masterPassword, this.kdfConfig, activeAccountId); } } diff --git a/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.ts b/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.ts index 0698ffe1f8d..beca0269b0a 100644 --- a/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.ts +++ b/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.ts @@ -2,7 +2,6 @@ import { Component, OnInit } from "@angular/core"; import { Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; -import { ChangePasswordComponent } from "@bitwarden/angular/auth/password-management/change-password"; import { InputPasswordFlow } from "@bitwarden/auth/angular"; import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; import { CalloutModule } from "@bitwarden/components"; @@ -13,7 +12,7 @@ import { WebauthnLoginSettingsModule } from "../../webauthn-login-settings"; @Component({ selector: "app-password-settings", templateUrl: "password-settings.component.html", - imports: [CalloutModule, ChangePasswordComponent, I18nPipe, WebauthnLoginSettingsModule], + imports: [CalloutModule, I18nPipe, WebauthnLoginSettingsModule], }) export class PasswordSettingsComponent implements OnInit { inputPasswordFlow = InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation; @@ -22,7 +21,7 @@ export class PasswordSettingsComponent implements OnInit { constructor( private router: Router, private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, - ) {} + ) { } async ngOnInit() { const userHasMasterPassword = await firstValueFrom( diff --git a/apps/web/src/app/auth/settings/security/security-routing.module.ts b/apps/web/src/app/auth/settings/security/security-routing.module.ts index 2ec1be5cb7f..249a41af417 100644 --- a/apps/web/src/app/auth/settings/security/security-routing.module.ts +++ b/apps/web/src/app/auth/settings/security/security-routing.module.ts @@ -6,7 +6,6 @@ import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag import { featureFlaggedRoute } from "@bitwarden/angular/platform/utils/feature-flagged-route"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ChangePasswordComponent } from "../change-password.component"; import { TwoFactorSetupComponent } from "../two-factor/two-factor-setup.component"; import { DeviceManagementOldComponent } from "./device-management-old.component"; @@ -21,19 +20,6 @@ const routes: Routes = [ data: { titleId: "security" }, children: [ { path: "", pathMatch: "full", redirectTo: "password" }, - { - path: "change-password", - component: ChangePasswordComponent, - canActivate: [ - canAccessFeature( - FeatureFlag.PM16117_ChangeExistingPasswordRefactor, - false, - "/settings/security/password", - false, - ), - ], - data: { titleId: "masterPassword" }, - }, { path: "password", component: PasswordSettingsComponent, @@ -74,4 +60,4 @@ const routes: Routes = [ imports: [RouterModule.forChild(routes)], exports: [RouterModule], }) -export class SecurityRoutingModule {} +export class SecurityRoutingModule { } diff --git a/apps/web/src/app/auth/settings/settings.module.ts b/apps/web/src/app/auth/settings/settings.module.ts index 437711f4aa6..7d101950c04 100644 --- a/apps/web/src/app/auth/settings/settings.module.ts +++ b/apps/web/src/app/auth/settings/settings.module.ts @@ -6,7 +6,6 @@ import { UserKeyRotationModule } from "../../key-management/key-rotation/user-ke import { SharedModule } from "../../shared"; import { EmergencyAccessModule } from "../emergency-access"; -import { ChangePasswordComponent } from "./change-password.component"; import { WebauthnLoginSettingsModule } from "./webauthn-login-settings"; @NgModule({ @@ -17,8 +16,8 @@ import { WebauthnLoginSettingsModule } from "./webauthn-login-settings"; PasswordCalloutComponent, UserKeyRotationModule, ], - declarations: [ChangePasswordComponent], + declarations: [], providers: [], - exports: [ChangePasswordComponent], + exports: [], }) -export class AuthSettingsModule {} +export class AuthSettingsModule { } diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts index 7569577e781..b207ee7d356 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts @@ -7,6 +7,7 @@ import { VerificationType } from "@bitwarden/common/auth/enums/verification-type import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; import { TwoFactorProviderRequest } from "@bitwarden/common/auth/models/request/two-factor-provider.request"; import { AuthResponseBase } from "@bitwarden/common/auth/types/auth-response"; +import { MasterPasswordAuthenticationHash } from "@bitwarden/common/key-management/master-password/types/master-password.types"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -37,7 +38,7 @@ export abstract class TwoFactorSetupMethodBaseComponent { protected userVerificationService: UserVerificationService, protected dialogService: DialogService, protected toastService: ToastService, - ) {} + ) { } protected auth(authResponse: AuthResponseBase) { this.hashedSecret = authResponse.secret; @@ -132,7 +133,7 @@ export abstract class TwoFactorSetupMethodBaseComponent { } return this.userVerificationService.buildRequest( { - secret: this.hashedSecret, + secret: this.hashedSecret as MasterPasswordAuthenticationHash, type: this.verificationType, }, requestClass, diff --git a/apps/web/src/app/auth/update-password.component.html b/apps/web/src/app/auth/update-password.component.html deleted file mode 100644 index 7fb3e1fa491..00000000000 --- a/apps/web/src/app/auth/update-password.component.html +++ /dev/null @@ -1,90 +0,0 @@ -
-
-
-

{{ "updateMasterPassword" | i18n }}

-
-
- {{ "masterPasswordInvalidWarning" | i18n }} - - - -
-
-
- - -
-
-
-
-
-
- - - -
-
-
-
- - -
-
-
- - - -
-
-
-
- diff --git a/apps/web/src/app/auth/update-password.component.ts b/apps/web/src/app/auth/update-password.component.ts deleted file mode 100644 index bc53f824228..00000000000 --- a/apps/web/src/app/auth/update-password.component.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Component, inject } from "@angular/core"; - -import { UpdatePasswordComponent as BaseUpdatePasswordComponent } from "@bitwarden/angular/auth/components/update-password.component"; -import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service"; - -import { RouterService } from "../core"; - -@Component({ - selector: "app-update-password", - templateUrl: "update-password.component.html", - standalone: false, -}) -export class UpdatePasswordComponent extends BaseUpdatePasswordComponent { - private routerService = inject(RouterService); - private organizationInviteService = inject(OrganizationInviteService); - - override async cancel() { - // clearing the login redirect url so that the user - // does not join the organization if they cancel - await this.routerService.getAndClearLoginRedirectUrl(); - await this.organizationInviteService.clearOrganizationInvitation(); - await super.cancel(); - } -} diff --git a/apps/web/src/app/auth/update-temp-password.component.html b/apps/web/src/app/auth/update-temp-password.component.html deleted file mode 100644 index 4fd0ea72b5f..00000000000 --- a/apps/web/src/app/auth/update-temp-password.component.html +++ /dev/null @@ -1,96 +0,0 @@ -
-
-
-

{{ "updateMasterPassword" | i18n }}

-
- {{ masterPasswordWarningText }} - - - - {{ "currentMasterPass" | i18n }} - - - -
- - {{ "newMasterPass" | i18n }} - - - - - -
- - {{ "confirmNewMasterPass" | i18n }} - - - - - {{ "masterPassHint" | i18n }} - - {{ "masterPassHintDesc" | i18n }} - -
-
- - -
-
-
-
-
diff --git a/apps/web/src/app/auth/update-temp-password.component.ts b/apps/web/src/app/auth/update-temp-password.component.ts deleted file mode 100644 index ead10660b92..00000000000 --- a/apps/web/src/app/auth/update-temp-password.component.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Component } from "@angular/core"; - -import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from "@bitwarden/angular/auth/components/update-temp-password.component"; - -@Component({ - selector: "app-update-temp-password", - templateUrl: "update-temp-password.component.html", - standalone: false, -}) -export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {} diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index 8a2270113a9..fac6d8328f8 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -10,7 +10,6 @@ import { unauthGuardFn, activeAuthGuard, } from "@bitwarden/angular/auth/guards"; -import { ChangePasswordComponent } from "@bitwarden/angular/auth/password-management/change-password"; import { SetInitialPasswordComponent } from "@bitwarden/angular/auth/password-management/set-initial-password/set-initial-password.component"; import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; import { @@ -19,7 +18,6 @@ import { RegistrationStartComponent, RegistrationStartSecondaryComponent, RegistrationStartSecondaryComponentData, - SetPasswordJitComponent, RegistrationLinkExpiredComponent, LoginComponent, LoginSecondaryContentComponent, @@ -55,13 +53,10 @@ import { LoginViaWebAuthnComponent } from "./auth/login/login-via-webauthn/login import { AcceptOrganizationComponent } from "./auth/organization-invite/accept-organization.component"; import { RecoverDeleteComponent } from "./auth/recover-delete.component"; import { RecoverTwoFactorComponent } from "./auth/recover-two-factor.component"; -import { SetPasswordComponent } from "./auth/set-password.component"; import { AccountComponent } from "./auth/settings/account/account.component"; import { EmergencyAccessComponent } from "./auth/settings/emergency-access/emergency-access.component"; import { EmergencyAccessViewComponent } from "./auth/settings/emergency-access/view/emergency-access-view.component"; import { SecurityRoutingModule } from "./auth/settings/security/security-routing.module"; -import { UpdatePasswordComponent } from "./auth/update-password.component"; -import { UpdateTempPasswordComponent } from "./auth/update-temp-password.component"; import { VerifyEmailTokenComponent } from "./auth/verify-email-token.component"; import { VerifyRecoverDeleteComponent } from "./auth/verify-recover-delete.component"; import { SponsoredFamiliesComponent } from "./billing/settings/sponsored-families.component"; @@ -114,11 +109,6 @@ const routes: Routes = [ component: LoginViaWebAuthnComponent, data: { titleId: "logInWithPasskey" } satisfies RouteDataProperties, }, - { - path: "set-password", - component: SetPasswordComponent, - data: { titleId: "setMasterPassword" } satisfies RouteDataProperties, - }, { path: "verify-email", component: VerifyEmailTokenComponent }, { path: "accept-organization", @@ -142,34 +132,6 @@ const routes: Routes = [ canActivate: [unauthGuardFn()], data: { titleId: "deleteOrganization" }, }, - { - path: "update-temp-password", - component: UpdateTempPasswordComponent, - canActivate: [ - canAccessFeature( - FeatureFlag.PM16117_ChangeExistingPasswordRefactor, - false, - "change-password", - false, - ), - authGuard, - ], - data: { titleId: "updateTempPassword" } satisfies RouteDataProperties, - }, - { - path: "update-password", - component: UpdatePasswordComponent, - canActivate: [ - canAccessFeature( - FeatureFlag.PM16117_ChangeExistingPasswordRefactor, - false, - "change-password", - false, - ), - authGuard, - ], - data: { titleId: "updatePassword" } satisfies RouteDataProperties, - }, ], }, { @@ -334,18 +296,6 @@ const routes: Routes = [ maxWidth: "lg", } satisfies AnonLayoutWrapperData, }, - { - path: "set-password-jit", - component: SetPasswordJitComponent, - data: { - pageTitle: { - key: "joinOrganization", - }, - pageSubtitle: { - key: "finishJoiningThisOrganizationBySettingAMasterPassword", - }, - } satisfies AnonLayoutWrapperData, - }, { path: "signup-link-expired", canActivate: [unauthGuardFn()], @@ -597,14 +547,6 @@ const routes: Routes = [ }, ], }, - { - path: "change-password", - component: ChangePasswordComponent, - canActivate: [ - canAccessFeature(FeatureFlag.PM16117_ChangeExistingPasswordRefactor), - authGuard, - ], - }, { path: "setup-extension", data: { @@ -756,13 +698,13 @@ const routes: Routes = [ ], exports: [RouterModule], }) -export class OssRoutingModule {} +export class OssRoutingModule { } export function buildFlaggedRoute(flagName: keyof Flags, route: Route): Route { return flagEnabled(flagName) ? route : { - path: route.path, - redirectTo: "/", - }; + path: route.path, + redirectTo: "/", + }; } diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index 97c3fa0375c..f9691be488f 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -14,16 +14,12 @@ import { VerifyRecoverDeleteOrgComponent } from "../admin-console/organizations/ import { AcceptFamilySponsorshipComponent } from "../admin-console/organizations/sponsorships/accept-family-sponsorship.component"; import { RecoverDeleteComponent } from "../auth/recover-delete.component"; import { RecoverTwoFactorComponent } from "../auth/recover-two-factor.component"; -import { SetPasswordComponent } from "../auth/set-password.component"; import { DangerZoneComponent } from "../auth/settings/account/danger-zone.component"; import { EmergencyAccessConfirmComponent } from "../auth/settings/emergency-access/confirm/emergency-access-confirm.component"; import { EmergencyAccessAddEditComponent } from "../auth/settings/emergency-access/emergency-access-add-edit.component"; import { EmergencyAccessComponent } from "../auth/settings/emergency-access/emergency-access.component"; -import { EmergencyAccessTakeoverComponent } from "../auth/settings/emergency-access/takeover/emergency-access-takeover.component"; import { EmergencyAccessViewComponent } from "../auth/settings/emergency-access/view/emergency-access-view.component"; import { UserVerificationModule } from "../auth/shared/components/user-verification"; -import { UpdatePasswordComponent } from "../auth/update-password.component"; -import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component"; import { VerifyEmailTokenComponent } from "../auth/verify-email-token.component"; import { VerifyRecoverDeleteComponent } from "../auth/verify-recover-delete.component"; import { FreeBitwardenFamiliesComponent } from "../billing/members/free-bitwarden-families.component"; @@ -73,7 +69,6 @@ import { SharedModule } from "./shared.module"; EmergencyAccessAddEditComponent, EmergencyAccessComponent, EmergencyAccessConfirmComponent, - EmergencyAccessTakeoverComponent, EmergencyAccessViewComponent, OrgEventsComponent, OrgExposedPasswordsReportComponent, @@ -85,12 +80,9 @@ import { SharedModule } from "./shared.module"; RecoverDeleteComponent, RecoverTwoFactorComponent, RemovePasswordComponent, - SetPasswordComponent, SponsoredFamiliesComponent, FreeBitwardenFamiliesComponent, SponsoringOrgRowComponent, - UpdatePasswordComponent, - UpdateTempPasswordComponent, VerifyEmailTokenComponent, VerifyRecoverDeleteComponent, ], @@ -100,7 +92,6 @@ import { SharedModule } from "./shared.module"; EmergencyAccessAddEditComponent, EmergencyAccessComponent, EmergencyAccessConfirmComponent, - EmergencyAccessTakeoverComponent, EmergencyAccessViewComponent, OrganizationLayoutComponent, OrgEventsComponent, @@ -114,16 +105,13 @@ import { SharedModule } from "./shared.module"; RecoverDeleteComponent, RecoverTwoFactorComponent, RemovePasswordComponent, - SetPasswordComponent, SponsoredFamiliesComponent, FreeBitwardenFamiliesComponent, SponsoringOrgRowComponent, - UpdateTempPasswordComponent, - UpdatePasswordComponent, VerifyEmailTokenComponent, VerifyRecoverDeleteComponent, HeaderModule, DangerZoneComponent, ], }) -export class LooseComponentsModule {} +export class LooseComponentsModule { } diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts index e95ea669725..24f0d43ef04 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts @@ -16,6 +16,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { MasterPasswordAuthenticationHash } from "@bitwarden/common/key-management/master-password/types/master-password.types"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -65,7 +66,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy { private keyService: KeyService, private accountService: AccountService, private linkSsoService: LinkSsoService, - ) {} + ) { } async ngOnInit() { const resetPasswordPolicies$ = this.accountService.activeAccount$.pipe( @@ -215,7 +216,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy { } else { // Remove reset password const request = new OrganizationUserResetPasswordEnrollmentRequest(); - request.masterPasswordHash = "ignored"; + request.masterPasswordHash = "ignored" as MasterPasswordAuthenticationHash; request.resetPasswordKey = ""; this.actionPromise = this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment( diff --git a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts index 941e56e7891..e070367a38a 100644 --- a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts +++ b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts @@ -20,7 +20,7 @@ export class OrganizationAuthRequestService { private keyService: KeyService, private encryptService: EncryptService, private organizationUserApiService: OrganizationUserApiService, - ) {} + ) { } async listPendingRequests(organizationId: string): Promise { return await this.organizationAuthRequestApiService.listPendingRequests(organizationId); diff --git a/libs/admin-console/src/common/organization-user/models/requests/organization-user-reset-password.request.ts b/libs/admin-console/src/common/organization-user/models/requests/organization-user-reset-password.request.ts index 7d060e3390e..d0ef8304f09 100644 --- a/libs/admin-console/src/common/organization-user/models/requests/organization-user-reset-password.request.ts +++ b/libs/admin-console/src/common/organization-user/models/requests/organization-user-reset-password.request.ts @@ -1,6 +1,14 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore +import { EncryptedString } from "@bitwarden/common/key-management/crypto/models/enc-string"; +import { MasterPasswordAuthenticationData, MasterPasswordAuthenticationHash, MasterPasswordUnlockData } from "@bitwarden/common/key-management/master-password/types/master-password.types"; + export class OrganizationUserResetPasswordRequest { - newMasterPasswordHash: string; - key: string; + /** @deprecated */ + newMasterPasswordHash: MasterPasswordAuthenticationHash; + /** @deprecated */ + key: EncryptedString; + + constructor(masterPasswordAuthenticationData: MasterPasswordAuthenticationData, masterPasswordUnlockData: MasterPasswordUnlockData) { + this.newMasterPasswordHash = masterPasswordAuthenticationData.masterPasswordAuthenticationHash; + this.key = masterPasswordUnlockData.masterKeyWrappedUserKey.encryptedString; + } } diff --git a/libs/admin-console/src/common/organization-user/models/responses/organization-user.response.ts b/libs/admin-console/src/common/organization-user/models/responses/organization-user.response.ts index 97ff1637382..43300846b59 100644 --- a/libs/admin-console/src/common/organization-user/models/responses/organization-user.response.ts +++ b/libs/admin-console/src/common/organization-user/models/responses/organization-user.response.ts @@ -1,11 +1,14 @@ +// @ts-strict-ignore + import { OrganizationUserStatusType, OrganizationUserType, } from "@bitwarden/common/admin-console/enums"; import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api"; import { SelectionReadOnlyResponse } from "@bitwarden/common/admin-console/models/response/selection-read-only.response"; +import { EncryptedString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { BaseResponse } from "@bitwarden/common/models/response/base.response"; -import { KdfType } from "@bitwarden/key-management"; +import { Argon2KdfConfig, KdfConfig, KdfType, PBKDF2KdfConfig } from "@bitwarden/key-management"; export class OrganizationUserResponse extends BaseResponse { id: string; @@ -75,20 +78,31 @@ export class OrganizationUserDetailsResponse extends OrganizationUserResponse { export class OrganizationUserResetPasswordDetailsResponse extends BaseResponse { organizationUserId: string; - kdf: KdfType; - kdfIterations: number; - kdfMemory?: number; - kdfParallelism?: number; - resetPasswordKey: string; - encryptedPrivateKey: string; + + kdf: KdfConfig; + + // OrgPublicKeyEncapsulatedUnsignedSharedUserKey + resetPasswordKey: EncryptedString; + // UserKeyEncryptedPrivateKey + encryptedPrivateKey: EncryptedString; constructor(response: any) { super(response); this.organizationUserId = this.getResponseProperty("OrganizationUserId"); - this.kdf = this.getResponseProperty("Kdf"); - this.kdfIterations = this.getResponseProperty("KdfIterations"); - this.kdfMemory = this.getResponseProperty("KdfMemory"); - this.kdfParallelism = this.getResponseProperty("KdfParallelism"); + + const kdf = this.getResponseProperty("Kdf"); + const kdfIterations = this.getResponseProperty("KdfIterations"); + const kdfMemory = this.getResponseProperty("KdfMemory"); + const kdfParallelism = this.getResponseProperty("KdfParallelism"); + if (kdf === KdfType.PBKDF2_SHA256) { + this.kdf = new PBKDF2KdfConfig(kdfIterations); + } else if (kdf === KdfType.Argon2id) { + this.kdf = new Argon2KdfConfig(kdfIterations, kdfMemory, kdfParallelism); + } else { + throw new Error(`Unsupported KDF type: ${kdf}`); + } + + // "ResetPasswordKey" is just the this.resetPasswordKey = this.getResponseProperty("ResetPasswordKey"); this.encryptedPrivateKey = this.getResponseProperty("EncryptedPrivateKey"); } diff --git a/libs/angular/src/auth/components/change-password.component.ts b/libs/angular/src/auth/components/change-password.component.ts deleted file mode 100644 index 8a564d68fd4..00000000000 --- a/libs/angular/src/auth/components/change-password.component.ts +++ /dev/null @@ -1,232 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive, OnDestroy, OnInit } from "@angular/core"; -import { Subject, firstValueFrom, map, switchMap, takeUntil } 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 { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { UserKey, MasterKey } from "@bitwarden/common/types/key"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KdfConfig, KdfConfigService, KeyService } from "@bitwarden/key-management"; - -import { PasswordColorText } from "../../tools/password-strength/password-strength.component"; - -@Directive() -export class ChangePasswordComponent implements OnInit, OnDestroy { - masterPassword: string; - masterPasswordRetype: string; - formPromise: Promise; - enforcedPolicyOptions: MasterPasswordPolicyOptions; - passwordStrengthResult: any; - color: string; - text: string; - leakedPassword: boolean; - minimumLength = Utils.minimumPasswordLength; - - protected email: string; - protected kdfConfig: KdfConfig; - - protected destroy$ = new Subject(); - - constructor( - protected accountService: AccountService, - protected dialogService: DialogService, - protected i18nService: I18nService, - protected kdfConfigService: KdfConfigService, - protected keyService: KeyService, - protected masterPasswordService: InternalMasterPasswordServiceAbstraction, - protected messagingService: MessagingService, - protected platformUtilsService: PlatformUtilsService, - protected policyService: PolicyService, - protected toastService: ToastService, - ) {} - - async ngOnInit() { - this.email = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.email)), - ); - this.accountService.activeAccount$ - .pipe( - getUserId, - switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId)), - takeUntil(this.destroy$), - ) - .subscribe( - (enforcedPasswordPolicyOptions) => - (this.enforcedPolicyOptions ??= enforcedPasswordPolicyOptions), - ); - - if (this.enforcedPolicyOptions?.minLength) { - this.minimumLength = this.enforcedPolicyOptions.minLength; - } - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - async submit() { - if (!(await this.strongPassword())) { - return; - } - - if (!(await this.setupSubmitActions())) { - return; - } - - const [userId, email] = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => [a?.id, a?.email])), - ); - - if (this.kdfConfig == null) { - this.kdfConfig = await this.kdfConfigService.getKdfConfig(userId); - } - - // Create new master key - const newMasterKey = await this.keyService.makeMasterKey( - this.masterPassword, - email.trim().toLowerCase(), - this.kdfConfig, - ); - const newMasterKeyHash = await this.keyService.hashMasterKey(this.masterPassword, newMasterKey); - - let newProtectedUserKey: [UserKey, EncString] = null; - const userKey = await this.keyService.getUserKey(); - if (userKey == null) { - newProtectedUserKey = await this.keyService.makeUserKey(newMasterKey); - } else { - newProtectedUserKey = await this.keyService.encryptUserKeyWithMasterKey(newMasterKey); - } - - await this.performSubmitActions(newMasterKeyHash, newMasterKey, newProtectedUserKey); - } - - async setupSubmitActions(): Promise { - // Override in sub-class - // Can be used for additional validation and/or other processes the should occur before changing passwords - return true; - } - - async performSubmitActions( - newMasterKeyHash: string, - newMasterKey: MasterKey, - newUserKey: [UserKey, EncString], - ) { - // Override in sub-class - } - - async strongPassword(): Promise { - if (this.masterPassword == null || this.masterPassword === "") { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("masterPasswordRequired"), - }); - return false; - } - if (this.masterPassword.length < this.minimumLength) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("masterPasswordMinimumlength", this.minimumLength), - }); - return false; - } - if (this.masterPassword !== this.masterPasswordRetype) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("masterPassDoesntMatch"), - }); - return false; - } - - const strengthResult = this.passwordStrengthResult; - - if ( - this.enforcedPolicyOptions != null && - !this.policyService.evaluateMasterPassword( - strengthResult.score, - this.masterPassword, - this.enforcedPolicyOptions, - ) - ) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("masterPasswordPolicyRequirementsNotMet"), - }); - return false; - } - - const weakPassword = strengthResult != null && strengthResult.score < 3; - - if (weakPassword && this.leakedPassword) { - const result = await this.dialogService.openSimpleDialog({ - title: { key: "weakAndExposedMasterPassword" }, - content: { key: "weakAndBreachedMasterPasswordDesc" }, - type: "warning", - }); - - if (!result) { - return false; - } - } else { - if (weakPassword) { - const result = await this.dialogService.openSimpleDialog({ - title: { key: "weakMasterPassword" }, - content: { key: "weakMasterPasswordDesc" }, - type: "warning", - }); - - if (!result) { - return false; - } - } - if (this.leakedPassword) { - const result = await this.dialogService.openSimpleDialog({ - title: { key: "exposedMasterPassword" }, - content: { key: "exposedMasterPasswordDesc" }, - type: "warning", - }); - - if (!result) { - return false; - } - } - } - - return true; - } - - async logOut() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "logOut" }, - content: { key: "logOutConfirmation" }, - acceptButtonText: { key: "logOut" }, - type: "warning", - }); - - if (confirmed) { - this.messagingService.send("logout"); - } - } - - getStrengthResult(result: any) { - this.passwordStrengthResult = result; - } - - getPasswordScoreText(event: PasswordColorText) { - this.color = event.color; - this.text = event.text; - } -} diff --git a/libs/angular/src/auth/components/set-password.component.ts b/libs/angular/src/auth/components/set-password.component.ts deleted file mode 100644 index 03cbdbc625a..00000000000 --- a/libs/angular/src/auth/components/set-password.component.ts +++ /dev/null @@ -1,300 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom, of } from "rxjs"; -import { filter, first, switchMap, tap } from "rxjs/operators"; - -// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. -// eslint-disable-next-line no-restricted-imports -import { - OrganizationUserApiService, - OrganizationUserResetPasswordEnrollmentRequest, -} from "@bitwarden/admin-console/common"; -// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. -// eslint-disable-next-line no-restricted-imports -import { InternalUserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; -import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; -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 { OrganizationAutoEnrollStatusResponse } from "@bitwarden/common/admin-console/models/response/organization-auto-enroll-status.response"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; -import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; -import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request"; -import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { HashPurpose } from "@bitwarden/common/platform/enums"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { UserId } from "@bitwarden/common/types/guid"; -import { MasterKey, UserKey } from "@bitwarden/common/types/key"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { DEFAULT_KDF_CONFIG, KdfConfigService, KeyService } from "@bitwarden/key-management"; - -import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component"; - -@Directive() -export class SetPasswordComponent extends BaseChangePasswordComponent implements OnInit { - syncLoading = true; - showPassword = false; - hint = ""; - orgSsoIdentifier: string = null; - orgId: string; - resetPasswordAutoEnroll = false; - onSuccessfulChangePassword: () => Promise; - successRoute = "vault"; - activeUserId: UserId; - - forceSetPasswordReason: ForceSetPasswordReason = ForceSetPasswordReason.None; - ForceSetPasswordReason = ForceSetPasswordReason; - - constructor( - protected accountService: AccountService, - protected dialogService: DialogService, - protected encryptService: EncryptService, - protected i18nService: I18nService, - protected kdfConfigService: KdfConfigService, - protected keyService: KeyService, - protected masterPasswordApiService: MasterPasswordApiService, - protected masterPasswordService: InternalMasterPasswordServiceAbstraction, - protected messagingService: MessagingService, - protected organizationApiService: OrganizationApiServiceAbstraction, - protected organizationUserApiService: OrganizationUserApiService, - protected platformUtilsService: PlatformUtilsService, - protected policyApiService: PolicyApiServiceAbstraction, - protected policyService: PolicyService, - protected route: ActivatedRoute, - protected router: Router, - protected ssoLoginService: SsoLoginServiceAbstraction, - protected syncService: SyncService, - protected toastService: ToastService, - protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, - ) { - super( - accountService, - dialogService, - i18nService, - kdfConfigService, - keyService, - masterPasswordService, - messagingService, - platformUtilsService, - policyService, - toastService, - ); - } - - async ngOnInit() { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - super.ngOnInit(); - - await this.syncService.fullSync(true); - this.syncLoading = false; - - this.activeUserId = (await firstValueFrom(this.accountService.activeAccount$))?.id; - - this.forceSetPasswordReason = await firstValueFrom( - this.masterPasswordService.forceSetPasswordReason$(this.activeUserId), - ); - - this.route.queryParams - .pipe( - first(), - switchMap((qParams) => { - if (qParams.identifier != null) { - return of(qParams.identifier); - } else { - // Try to get orgSsoId from state as fallback - // Note: this is primarily for the TDE user w/out MP obtains admin MP reset permission scenario. - return this.ssoLoginService.getActiveUserOrganizationSsoIdentifier(this.activeUserId); - } - }), - filter((orgSsoId) => orgSsoId != null), - tap((orgSsoId: string) => { - this.orgSsoIdentifier = orgSsoId; - }), - switchMap((orgSsoId: string) => this.organizationApiService.getAutoEnrollStatus(orgSsoId)), - tap((orgAutoEnrollStatusResponse: OrganizationAutoEnrollStatusResponse) => { - this.orgId = orgAutoEnrollStatusResponse.id; - this.resetPasswordAutoEnroll = orgAutoEnrollStatusResponse.resetPasswordEnabled; - }), - switchMap((orgAutoEnrollStatusResponse: OrganizationAutoEnrollStatusResponse) => - // Must get org id from response to get master password policy options - this.policyApiService.getMasterPasswordPolicyOptsForOrgUser( - orgAutoEnrollStatusResponse.id, - ), - ), - tap((masterPasswordPolicyOptions: MasterPasswordPolicyOptions) => { - this.enforcedPolicyOptions = masterPasswordPolicyOptions; - }), - ) - .subscribe({ - error: () => { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("errorOccurred"), - }); - }, - }); - } - - async setupSubmitActions() { - this.kdfConfig = DEFAULT_KDF_CONFIG; - return true; - } - - async performSubmitActions( - masterPasswordHash: string, - masterKey: MasterKey, - userKey: [UserKey, EncString], - ) { - let keysRequest: KeysRequest | null = null; - let newKeyPair: [string, EncString] | null = null; - - if ( - this.forceSetPasswordReason != - ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission - ) { - // Existing JIT provisioned user in a MP encryption org setting first password - // Users in this state will not already have a user asymmetric key pair so must create it for them - // We don't want to re-create the user key pair if the user already has one (TDE user case) - - // in case we have a local private key, and are not sure whether it has been posted to the server, we post the local private key instead of generating a new one - const existingUserPrivateKey = (await firstValueFrom( - this.keyService.userPrivateKey$(this.activeUserId), - )) as Uint8Array; - const existingUserPublicKey = await firstValueFrom( - this.keyService.userPublicKey$(this.activeUserId), - ); - if (existingUserPrivateKey != null && existingUserPublicKey != null) { - const existingUserPublicKeyB64 = Utils.fromBufferToB64(existingUserPublicKey); - newKeyPair = [ - existingUserPublicKeyB64, - await this.encryptService.wrapDecapsulationKey(existingUserPrivateKey, userKey[0]), - ]; - } else { - newKeyPair = await this.keyService.makeKeyPair(userKey[0]); - } - keysRequest = new KeysRequest(newKeyPair[0], newKeyPair[1].encryptedString); - } - - const request = new SetPasswordRequest( - masterPasswordHash, - userKey[1].encryptedString, - this.hint, - this.orgSsoIdentifier, - keysRequest, - this.kdfConfig.kdfType, //always PBKDF2 --> see this.setupSubmitActions - this.kdfConfig.iterations, - ); - try { - if (this.resetPasswordAutoEnroll) { - this.formPromise = this.masterPasswordApiService - .setPassword(request) - .then(async () => { - await this.onSetPasswordSuccess(masterKey, userKey, newKeyPair); - return this.organizationApiService.getKeys(this.orgId); - }) - .then(async (response) => { - if (response == null) { - throw new Error(this.i18nService.t("resetPasswordOrgKeysError")); - } - const publicKey = Utils.fromB64ToArray(response.publicKey); - - // RSA Encrypt user key with organization public key - const userKey = await this.keyService.getUserKey(); - const encryptedUserKey = await this.encryptService.encapsulateKeyUnsigned( - userKey, - publicKey, - ); - - const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest(); - resetRequest.masterPasswordHash = masterPasswordHash; - resetRequest.resetPasswordKey = encryptedUserKey.encryptedString; - - return this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment( - this.orgId, - this.activeUserId, - resetRequest, - ); - }); - } else { - this.formPromise = this.masterPasswordApiService.setPassword(request).then(async () => { - await this.onSetPasswordSuccess(masterKey, userKey, newKeyPair); - }); - } - - await this.formPromise; - - if (this.onSuccessfulChangePassword != null) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.onSuccessfulChangePassword(); - } else { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate([this.successRoute]); - } - } catch { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("errorOccurred"), - }); - } - } - - togglePassword(confirmField: boolean) { - this.showPassword = !this.showPassword; - document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus(); - } - - protected async onSetPasswordSuccess( - masterKey: MasterKey, - userKey: [UserKey, EncString], - keyPair: [string, EncString] | null, - ) { - // Clear force set password reason to allow navigation back to vault. - await this.masterPasswordService.setForceSetPasswordReason( - ForceSetPasswordReason.None, - this.activeUserId, - ); - - // User now has a password so update account decryption options in state - const userDecryptionOpts = await firstValueFrom( - this.userDecryptionOptionsService.userDecryptionOptions$, - ); - userDecryptionOpts.hasMasterPassword = true; - await this.userDecryptionOptionsService.setUserDecryptionOptions(userDecryptionOpts); - await this.kdfConfigService.setKdfConfig(this.activeUserId, this.kdfConfig); - await this.masterPasswordService.setMasterKey(masterKey, this.activeUserId); - await this.keyService.setUserKey(userKey[0], this.activeUserId); - - // Set private key only for new JIT provisioned users in MP encryption orgs - // Existing TDE users will have private key set on sync or on login - if ( - keyPair !== null && - this.forceSetPasswordReason != - ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission - ) { - await this.keyService.setPrivateKey(keyPair[1].encryptedString, this.activeUserId); - } - - const localMasterKeyHash = await this.keyService.hashMasterKey( - this.masterPassword, - masterKey, - HashPurpose.LocalAuthorization, - ); - await this.masterPasswordService.setMasterKeyHash(localMasterKeyHash, this.activeUserId); - } -} diff --git a/libs/angular/src/auth/components/update-password.component.ts b/libs/angular/src/auth/components/update-password.component.ts deleted file mode 100644 index fa3ab58db69..00000000000 --- a/libs/angular/src/auth/components/update-password.component.ts +++ /dev/null @@ -1,141 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive } from "@angular/core"; -import { Router } from "@angular/router"; -import { firstValueFrom } 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 { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; -import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; -import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { Verification } from "@bitwarden/common/auth/types/verification"; -import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { MasterKey, UserKey } from "@bitwarden/common/types/key"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KdfConfigService, KeyService } from "@bitwarden/key-management"; - -import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component"; - -@Directive() -export class UpdatePasswordComponent extends BaseChangePasswordComponent { - hint: string; - key: string; - enforcedPolicyOptions: MasterPasswordPolicyOptions; - showPassword = false; - currentMasterPassword: string; - - onSuccessfulChangePassword: () => Promise; - - constructor( - protected router: Router, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - policyService: PolicyService, - keyService: KeyService, - messagingService: MessagingService, - private masterPasswordApiService: MasterPasswordApiService, - private userVerificationService: UserVerificationService, - private logService: LogService, - dialogService: DialogService, - kdfConfigService: KdfConfigService, - masterPasswordService: InternalMasterPasswordServiceAbstraction, - accountService: AccountService, - toastService: ToastService, - ) { - super( - accountService, - dialogService, - i18nService, - kdfConfigService, - keyService, - masterPasswordService, - messagingService, - platformUtilsService, - policyService, - toastService, - ); - } - - togglePassword(confirmField: boolean) { - this.showPassword = !this.showPassword; - document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus(); - } - - async cancel() { - await this.router.navigate(["/vault"]); - } - - async setupSubmitActions(): Promise { - if (this.currentMasterPassword == null || this.currentMasterPassword === "") { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("masterPasswordRequired"), - }); - return false; - } - - const secret: Verification = { - type: VerificationType.MasterPassword, - secret: this.currentMasterPassword, - }; - try { - await this.userVerificationService.verifyUser(secret); - } catch (e) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: e.message, - }); - return false; - } - const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - this.kdfConfig = await this.kdfConfigService.getKdfConfig(userId); - return true; - } - - async performSubmitActions( - newMasterKeyHash: string, - newMasterKey: MasterKey, - newUserKey: [UserKey, EncString], - ) { - try { - // Create Request - const request = new PasswordRequest(); - request.masterPasswordHash = await this.keyService.hashMasterKey( - this.currentMasterPassword, - await this.keyService.getOrDeriveMasterKey(this.currentMasterPassword), - ); - request.newMasterPasswordHash = newMasterKeyHash; - request.key = newUserKey[1].encryptedString; - - // Update user's password - await this.masterPasswordApiService.postPassword(request); - - this.toastService.showToast({ - variant: "success", - title: this.i18nService.t("masterPasswordChanged"), - message: this.i18nService.t("logBackIn"), - }); - - if (this.onSuccessfulChangePassword != null) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.onSuccessfulChangePassword(); - } else { - this.messagingService.send("logout"); - } - } catch (e) { - this.logService.error(e); - } - } -} diff --git a/libs/angular/src/auth/components/update-temp-password.component.ts b/libs/angular/src/auth/components/update-temp-password.component.ts deleted file mode 100644 index 681f69b083a..00000000000 --- a/libs/angular/src/auth/components/update-temp-password.component.ts +++ /dev/null @@ -1,232 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; -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 { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; -import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; -import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; -import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; -import { UpdateTdeOffboardingPasswordRequest } from "@bitwarden/common/auth/models/request/update-tde-offboarding-password.request"; -import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request"; -import { MasterPasswordVerification } from "@bitwarden/common/auth/types/verification"; -import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { MasterKey, UserKey } from "@bitwarden/common/types/key"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KdfConfigService, KeyService } from "@bitwarden/key-management"; - -import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component"; - -@Directive() -export class UpdateTempPasswordComponent extends BaseChangePasswordComponent implements OnInit { - hint: string; - key: string; - enforcedPolicyOptions: MasterPasswordPolicyOptions; - showPassword = false; - reason: ForceSetPasswordReason = ForceSetPasswordReason.None; - verification: MasterPasswordVerification = { - type: VerificationType.MasterPassword, - secret: "", - }; - - onSuccessfulChangePassword: () => Promise; - - get requireCurrentPassword(): boolean { - return this.reason === ForceSetPasswordReason.WeakMasterPassword; - } - - constructor( - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - policyService: PolicyService, - keyService: KeyService, - messagingService: MessagingService, - private masterPasswordApiService: MasterPasswordApiService, - private syncService: SyncService, - private logService: LogService, - private userVerificationService: UserVerificationService, - protected router: Router, - dialogService: DialogService, - kdfConfigService: KdfConfigService, - accountService: AccountService, - masterPasswordService: InternalMasterPasswordServiceAbstraction, - toastService: ToastService, - ) { - super( - accountService, - dialogService, - i18nService, - kdfConfigService, - keyService, - masterPasswordService, - messagingService, - platformUtilsService, - policyService, - toastService, - ); - } - - async ngOnInit() { - await this.syncService.fullSync(true); - - const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; - this.reason = await firstValueFrom(this.masterPasswordService.forceSetPasswordReason$(userId)); - - // If we somehow end up here without a reason, go back to the home page - if (this.reason == ForceSetPasswordReason.None) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["/"]); - return; - } - - await super.ngOnInit(); - } - - get masterPasswordWarningText(): string { - if (this.reason == ForceSetPasswordReason.WeakMasterPassword) { - return this.i18nService.t("updateWeakMasterPasswordWarning"); - } else if (this.reason == ForceSetPasswordReason.TdeOffboarding) { - return this.i18nService.t("tdeDisabledMasterPasswordRequired"); - } else { - return this.i18nService.t("updateMasterPasswordWarning"); - } - } - - togglePassword(confirmField: boolean) { - this.showPassword = !this.showPassword; - document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus(); - } - - async setupSubmitActions(): Promise { - const [userId, email] = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => [a?.id, a?.email])), - ); - this.email = email; - this.kdfConfig = await this.kdfConfigService.getKdfConfig(userId); - return true; - } - - async submit() { - // Validation - if (!(await this.strongPassword())) { - return; - } - - if (!(await this.setupSubmitActions())) { - return; - } - - try { - // Create new key and hash new password - const newMasterKey = await this.keyService.makeMasterKey( - this.masterPassword, - this.email.trim().toLowerCase(), - this.kdfConfig, - ); - const newPasswordHash = await this.keyService.hashMasterKey( - this.masterPassword, - newMasterKey, - ); - - // Grab user key - const userKey = await this.keyService.getUserKey(); - - // Encrypt user key with new master key - const newProtectedUserKey = await this.keyService.encryptUserKeyWithMasterKey( - newMasterKey, - userKey, - ); - - await this.performSubmitActions(newPasswordHash, newMasterKey, newProtectedUserKey); - } catch (e) { - this.logService.error(e); - } - } - - async performSubmitActions( - masterPasswordHash: string, - masterKey: MasterKey, - userKey: [UserKey, EncString], - ) { - try { - switch (this.reason) { - case ForceSetPasswordReason.AdminForcePasswordReset: - this.formPromise = this.updateTempPassword(masterPasswordHash, userKey); - break; - case ForceSetPasswordReason.WeakMasterPassword: - this.formPromise = this.updatePassword(masterPasswordHash, userKey); - break; - case ForceSetPasswordReason.TdeOffboarding: - this.formPromise = this.updateTdeOffboardingPassword(masterPasswordHash, userKey); - break; - } - - await this.formPromise; - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("updatedMasterPassword"), - }); - - const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; - await this.masterPasswordService.setForceSetPasswordReason( - ForceSetPasswordReason.None, - userId, - ); - - if (this.onSuccessfulChangePassword != null) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.onSuccessfulChangePassword(); - } else { - this.messagingService.send("logout"); - } - } catch (e) { - this.logService.error(e); - } - } - private async updateTempPassword(masterPasswordHash: string, userKey: [UserKey, EncString]) { - const request = new UpdateTempPasswordRequest(); - request.key = userKey[1].encryptedString; - request.newMasterPasswordHash = masterPasswordHash; - request.masterPasswordHint = this.hint; - - return this.masterPasswordApiService.putUpdateTempPassword(request); - } - - private async updatePassword(newMasterPasswordHash: string, userKey: [UserKey, EncString]) { - const request = await this.userVerificationService.buildRequest( - this.verification, - PasswordRequest, - ); - request.masterPasswordHint = this.hint; - request.newMasterPasswordHash = newMasterPasswordHash; - request.key = userKey[1].encryptedString; - - return this.masterPasswordApiService.postPassword(request); - } - - private async updateTdeOffboardingPassword( - masterPasswordHash: string, - userKey: [UserKey, EncString], - ) { - const request = new UpdateTdeOffboardingPasswordRequest(); - request.key = userKey[1].encryptedString; - request.newMasterPasswordHash = masterPasswordHash; - request.masterPasswordHint = this.hint; - - return this.masterPasswordApiService.putUpdateTdeOffboardingPassword(request); - } -} diff --git a/libs/angular/src/auth/password-management/change-password/change-password.component.html b/libs/angular/src/auth/password-management/change-password/change-password.component.html deleted file mode 100644 index 7604ffacea7..00000000000 --- a/libs/angular/src/auth/password-management/change-password/change-password.component.html +++ /dev/null @@ -1,28 +0,0 @@ -@if (initializing) { - - {{ "loading" | i18n }} -} @else { - {{ "changePasswordWarning" | i18n }} - - - -} diff --git a/libs/angular/src/auth/password-management/change-password/change-password.component.ts b/libs/angular/src/auth/password-management/change-password/change-password.component.ts deleted file mode 100644 index 02738d33321..00000000000 --- a/libs/angular/src/auth/password-management/change-password/change-password.component.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component, Input, OnInit } from "@angular/core"; -import { firstValueFrom } from "rxjs"; - -// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. -// eslint-disable-next-line no-restricted-imports -import { - InputPasswordComponent, - InputPasswordFlow, - PasswordInputResult, -} from "@bitwarden/auth/angular"; -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 { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; -import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { SyncService } from "@bitwarden/common/platform/sync"; -import { UserId } from "@bitwarden/common/types/guid"; -import { - AnonLayoutWrapperDataService, - DialogService, - ToastService, - Icons, - CalloutComponent, -} from "@bitwarden/components"; -import { I18nPipe } from "@bitwarden/ui-common"; - -import { ChangePasswordService } from "./change-password.service.abstraction"; - -/** - * Change Password Component - * - * NOTE: The change password component uses the input-password component which will show the - * current password input form in some flows, although it could be left off. This is intentional - * and by design to maintain a strong security posture as some flows could have the user - * end up at a change password without having one before. - */ -@Component({ - selector: "auth-change-password", - templateUrl: "change-password.component.html", - imports: [InputPasswordComponent, I18nPipe, CalloutComponent, CommonModule], -}) -export class ChangePasswordComponent implements OnInit { - @Input() inputPasswordFlow: InputPasswordFlow = InputPasswordFlow.ChangePassword; - - activeAccount: Account | null = null; - email?: string; - userId?: UserId; - masterPasswordPolicyOptions?: MasterPasswordPolicyOptions; - initializing = true; - submitting = false; - formPromise?: Promise; - forceSetPasswordReason: ForceSetPasswordReason = ForceSetPasswordReason.None; - - protected readonly ForceSetPasswordReason = ForceSetPasswordReason; - - constructor( - private accountService: AccountService, - private changePasswordService: ChangePasswordService, - private i18nService: I18nService, - private masterPasswordService: InternalMasterPasswordServiceAbstraction, - private anonLayoutWrapperDataService: AnonLayoutWrapperDataService, - private organizationInviteService: OrganizationInviteService, - private messagingService: MessagingService, - private policyService: PolicyService, - private toastService: ToastService, - private syncService: SyncService, - private dialogService: DialogService, - private logService: LogService, - ) {} - - async ngOnInit() { - this.activeAccount = await firstValueFrom(this.accountService.activeAccount$); - - if (!this.activeAccount) { - throw new Error("No active active account found while trying to change passwords."); - } - - this.userId = this.activeAccount.id; - this.email = this.activeAccount.email; - - if (!this.userId) { - throw new Error("userId not found"); - } - - this.masterPasswordPolicyOptions = await firstValueFrom( - this.policyService.masterPasswordPolicyOptions$(this.userId), - ); - - this.forceSetPasswordReason = await firstValueFrom( - this.masterPasswordService.forceSetPasswordReason$(this.userId), - ); - - if (this.forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset) { - this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ - pageIcon: Icons.LockIcon, - pageTitle: { key: "updateMasterPassword" }, - pageSubtitle: { key: "accountRecoveryUpdateMasterPasswordSubtitle" }, - }); - } else if (this.forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword) { - this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ - pageIcon: Icons.LockIcon, - pageTitle: { key: "updateMasterPassword" }, - pageSubtitle: { key: "updateMasterPasswordSubtitle" }, - maxWidth: "lg", - }); - } - - this.initializing = false; - } - - async logOut() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "logOut" }, - content: { key: "logOutConfirmation" }, - acceptButtonText: { key: "logOut" }, - type: "warning", - }); - - if (confirmed) { - await this.organizationInviteService.clearOrganizationInvitation(); - - if (this.changePasswordService.clearDeeplinkState) { - await this.changePasswordService.clearDeeplinkState(); - } - - // TODO: PM-23515 eventually use the logout service instead of messaging service once it is available without circular dependencies - this.messagingService.send("logout"); - } - } - - async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) { - this.submitting = true; - - try { - if (passwordInputResult.rotateUserKey) { - if (this.activeAccount == null) { - throw new Error("activeAccount not found"); - } - - if ( - passwordInputResult.currentPassword == null || - passwordInputResult.newPasswordHint == null - ) { - throw new Error("currentPassword or newPasswordHint not found"); - } - - await this.syncService.fullSync(true); - - await this.changePasswordService.rotateUserKeyMasterPasswordAndEncryptedData( - passwordInputResult.currentPassword, - passwordInputResult.newPassword, - this.activeAccount, - passwordInputResult.newPasswordHint, - ); - } else { - if (!this.userId) { - throw new Error("userId not found"); - } - - if (this.forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset) { - await this.changePasswordService.changePasswordForAccountRecovery( - passwordInputResult, - this.userId, - ); - } else { - await this.changePasswordService.changePassword(passwordInputResult, this.userId); - } - - this.toastService.showToast({ - variant: "success", - message: this.i18nService.t("masterPasswordChanged"), - }); - - // TODO: PM-23515 eventually use the logout service instead of messaging service once it is available without circular dependencies - this.messagingService.send("logout"); - } - } catch (error) { - this.logService.error(error); - this.toastService.showToast({ - variant: "error", - title: "", - message: this.i18nService.t("errorOccurred"), - }); - } finally { - this.submitting = false; - } - } - - /** - * Shows the logout button in the case of admin force reset password or weak password upon login. - */ - protected secondaryButtonText(): { key: string } | undefined { - return this.forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset || - this.forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword - ? { key: "logOut" } - : undefined; - } -} diff --git a/libs/angular/src/auth/password-management/change-password/default-change-password.service.spec.ts b/libs/angular/src/auth/password-management/change-password/default-change-password.service.spec.ts index d14e33c1fdc..60220de3e26 100644 --- a/libs/angular/src/auth/password-management/change-password/default-change-password.service.spec.ts +++ b/libs/angular/src/auth/password-management/change-password/default-change-password.service.spec.ts @@ -41,7 +41,7 @@ describe("DefaultChangePasswordService", () => { newServerMasterKeyHash: "newServerMasterKeyHash", newLocalMasterKeyHash: "newLocalMasterKeyHash", - kdfConfig: new PBKDF2KdfConfig(), + kdf: new PBKDF2KdfConfig(), }; const decryptedUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey; diff --git a/libs/angular/src/auth/password-management/change-password/default-change-password.service.ts b/libs/angular/src/auth/password-management/change-password/default-change-password.service.ts index 21b39873a16..12a0b40fb80 100644 --- a/libs/angular/src/auth/password-management/change-password/default-change-password.service.ts +++ b/libs/angular/src/auth/password-management/change-password/default-change-password.service.ts @@ -1,3 +1,5 @@ +import { firstValueFrom } from "rxjs"; + // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // eslint-disable-next-line no-restricted-imports import { PasswordInputResult } from "@bitwarden/auth/angular"; @@ -5,10 +7,8 @@ import { Account } 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 { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request"; -import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { UserId } from "@bitwarden/common/types/guid"; -import { UserKey } from "@bitwarden/common/types/key"; import { KeyService } from "@bitwarden/key-management"; import { ChangePasswordService } from "./change-password.service.abstraction"; @@ -18,7 +18,7 @@ export class DefaultChangePasswordService implements ChangePasswordService { protected keyService: KeyService, protected masterPasswordApiService: MasterPasswordApiService, protected masterPasswordService: InternalMasterPasswordServiceAbstraction, - ) {} + ) { } async rotateUserKeyMasterPasswordAndEncryptedData( currentPassword: string, @@ -29,60 +29,20 @@ export class DefaultChangePasswordService implements ChangePasswordService { throw new Error("rotateUserKeyMasterPasswordAndEncryptedData() is only implemented in Web"); } - private async preparePasswordChange( - passwordInputResult: PasswordInputResult, - userId: UserId | null, - request: PasswordRequest | UpdateTempPasswordRequest, - ): Promise<[UserKey, EncString]> { - if (!userId) { - throw new Error("userId not found"); - } - if ( - !passwordInputResult.currentMasterKey || - !passwordInputResult.currentServerMasterKeyHash || - !passwordInputResult.newMasterKey || - !passwordInputResult.newServerMasterKeyHash || - passwordInputResult.newPasswordHint == null - ) { - throw new Error("invalid PasswordInputResult credentials, could not change password"); - } - - const decryptedUserKey = await this.masterPasswordService.decryptUserKeyWithMasterKey( - passwordInputResult.currentMasterKey, - userId, - ); - - if (decryptedUserKey == null) { - throw new Error("Could not decrypt user key"); - } - - const newKeyValue = await this.keyService.encryptUserKeyWithMasterKey( - passwordInputResult.newMasterKey, - decryptedUserKey, - ); - - if (request instanceof PasswordRequest) { - request.masterPasswordHash = passwordInputResult.currentServerMasterKeyHash; - request.newMasterPasswordHash = passwordInputResult.newServerMasterKeyHash; - request.masterPasswordHint = passwordInputResult.newPasswordHint; - } else if (request instanceof UpdateTempPasswordRequest) { - request.newMasterPasswordHash = passwordInputResult.newServerMasterKeyHash; - request.masterPasswordHint = passwordInputResult.newPasswordHint; - } - - return newKeyValue; - } - async changePassword(passwordInputResult: PasswordInputResult, userId: UserId | null) { - const request = new PasswordRequest(); + const userKey = await firstValueFrom(this.keyService.userKey$(userId)); + if (userKey == null) { + throw new Error("Can't find UserKey"); + } + const salt = await firstValueFrom(this.masterPasswordService.saltForAccount$(userId)); + const authenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData(passwordInputResult.newPassword, passwordInputResult.kdf, salt); + const unlockData = await this.masterPasswordService.makeMasterPasswordUnlockData(passwordInputResult.newPassword, passwordInputResult.kdf, passwordInputResult.salt, userKey); - const newMasterKeyEncryptedUserKey = await this.preparePasswordChange( - passwordInputResult, - userId, - request, - ); + const request = new PasswordRequest(authenticationData, unlockData); - request.key = newMasterKeyEncryptedUserKey[1].encryptedString as string; + const oldAuthenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData(passwordInputResult.currentPassword, passwordInputResult.kdf, salt); + request.masterPasswordHash = oldAuthenticationData.masterPasswordAuthenticationHash; + request.masterPasswordHint = passwordInputResult.newPasswordHint; try { await this.masterPasswordApiService.postPassword(request); @@ -92,15 +52,16 @@ export class DefaultChangePasswordService implements ChangePasswordService { } async changePasswordForAccountRecovery(passwordInputResult: PasswordInputResult, userId: UserId) { - const request = new UpdateTempPasswordRequest(); + const userKey = await firstValueFrom(this.keyService.userKey$(userId)); + if (userKey == null) { + throw new Error("Can't find UserKey"); + } + const salt = await firstValueFrom(this.masterPasswordService.saltForAccount$(userId)); + const authenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData(passwordInputResult.newPassword, passwordInputResult.kdf, salt); + const unlockData = await this.masterPasswordService.makeMasterPasswordUnlockData(passwordInputResult.newPassword, passwordInputResult.kdf, passwordInputResult.salt, userKey); - const newMasterKeyEncryptedUserKey = await this.preparePasswordChange( - passwordInputResult, - userId, - request, - ); - - request.key = newMasterKeyEncryptedUserKey[1].encryptedString as string; + const request = new UpdateTempPasswordRequest(authenticationData, unlockData); + request.masterPasswordHint = passwordInputResult.newPasswordHint; try { // TODO: PM-23047 will look to consolidate this into the change password endpoint. diff --git a/libs/angular/src/auth/password-management/change-password/index.ts b/libs/angular/src/auth/password-management/change-password/index.ts index 32734d39bc0..4cb8b808251 100644 --- a/libs/angular/src/auth/password-management/change-password/index.ts +++ b/libs/angular/src/auth/password-management/change-password/index.ts @@ -1,3 +1,2 @@ -export * from "./change-password.component"; export * from "./change-password.service.abstraction"; export * from "./default-change-password.service"; diff --git a/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts b/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts index fbab228d6e0..d632360d914 100644 --- a/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts +++ b/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts @@ -18,8 +18,10 @@ import { UpdateTdeOffboardingPasswordRequest } from "@bitwarden/common/auth/mode import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { MasterPasswordAuthenticationData } from "@bitwarden/common/key-management/master-password/types/master-password.types"; import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { HashPurpose } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { UserId } from "@bitwarden/common/types/guid"; import { MasterKey, UserKey } from "@bitwarden/common/types/key"; @@ -44,7 +46,7 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi protected organizationApiService: OrganizationApiServiceAbstraction, protected organizationUserApiService: OrganizationUserApiService, protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, - ) {} + ) { } async setInitialPassword( credentials: SetInitialPasswordCredentials, @@ -52,11 +54,7 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi userId: UserId, ): Promise { const { - newMasterKey, - newServerMasterKeyHash, - newLocalMasterKeyHash, newPasswordHint, - kdfConfig, orgSsoIdentifier, orgId, resetPasswordAutoEnroll, @@ -74,13 +72,18 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi throw new Error("userType not found. Could not set password."); } - const masterKeyEncryptedUserKey = await this.makeMasterKeyEncryptedUserKey( - newMasterKey, - userId, + const userKey = await this.keyService.makeUserKeyV1Raw(); + const authenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData( + credentials.initialPassword, + credentials.kdf, + credentials.salt, + ); + const unlockData = await this.masterPasswordService.makeMasterPasswordUnlockData( + credentials.initialPassword, + credentials.kdf, + credentials.salt, + userKey, ); - if (masterKeyEncryptedUserKey == null || !masterKeyEncryptedUserKey[1].encryptedString) { - throw new Error("masterKeyEncryptedUserKey not found. Could not set password."); - } let keyPair: [string, EncString] | null = null; let keysRequest: KeysRequest | null = null; @@ -113,12 +116,12 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi existingUserPublicKeyB64, await this.encryptService.wrapDecapsulationKey( existingUserPrivateKey, - masterKeyEncryptedUserKey[0], + userKey, ), ]; } else { // New key pair - keyPair = await this.keyService.makeKeyPair(masterKeyEncryptedUserKey[0]); + keyPair = await this.keyService.makeKeyPair(userKey); } if (keyPair == null) { @@ -132,13 +135,11 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi } const request = new SetPasswordRequest( - newServerMasterKeyHash, - masterKeyEncryptedUserKey[1].encryptedString, + authenticationData, + unlockData, newPasswordHint, orgSsoIdentifier, keysRequest, - kdfConfig.kdfType, - kdfConfig.iterations, ); await this.masterPasswordApiService.setPassword(request); @@ -147,10 +148,18 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi await this.masterPasswordService.setForceSetPasswordReason(ForceSetPasswordReason.None, userId); // User now has a password so update account decryption options in state + // TODO: REMOVE when masterKey is no longer used + const masterKey = await this.keyService.makeMasterKey(credentials.initialPassword, credentials.salt, credentials.kdf); + const localMasterKeyHash = await this.keyService.hashMasterKey( + credentials.initialPassword, + masterKey, + HashPurpose.LocalAuthorization + ) as string; + await this.masterPasswordService.setMasterKeyHash(localMasterKeyHash, userId); await this.updateAccountDecryptionProperties( - newMasterKey, - kdfConfig, - masterKeyEncryptedUserKey, + masterKey, + credentials.kdf, + userKey, userId, ); @@ -165,34 +174,17 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi await this.keyService.setPrivateKey(keyPair[1].encryptedString, userId); } - await this.masterPasswordService.setMasterKeyHash(newLocalMasterKeyHash, userId); if (resetPasswordAutoEnroll) { - await this.handleResetPasswordAutoEnroll(newServerMasterKeyHash, orgId, userId); + await this.handleResetPasswordAutoEnroll(authenticationData, orgId, userId); } } - private async makeMasterKeyEncryptedUserKey( - masterKey: MasterKey, - userId: UserId, - ): Promise<[UserKey, EncString]> { - let masterKeyEncryptedUserKey: [UserKey, EncString] | null = null; - - const userKey = await firstValueFrom(this.keyService.userKey$(userId)); - - if (userKey == null) { - masterKeyEncryptedUserKey = await this.keyService.makeUserKey(masterKey); - } else { - masterKeyEncryptedUserKey = await this.keyService.encryptUserKeyWithMasterKey(masterKey); - } - - return masterKeyEncryptedUserKey; - } - private async updateAccountDecryptionProperties( + /** @deprecated */ masterKey: MasterKey, kdfConfig: KdfConfig, - masterKeyEncryptedUserKey: [UserKey, EncString], + userKey: UserKey, userId: UserId, ) { const userDecryptionOpts = await firstValueFrom( @@ -201,12 +193,13 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi userDecryptionOpts.hasMasterPassword = true; await this.userDecryptionOptionsService.setUserDecryptionOptions(userDecryptionOpts); await this.kdfConfigService.setKdfConfig(userId, kdfConfig); + // TODO: Remove when masterKey is no longer used await this.masterPasswordService.setMasterKey(masterKey, userId); - await this.keyService.setUserKey(masterKeyEncryptedUserKey[0], userId); + await this.keyService.setUserKey(userKey, userId); } private async handleResetPasswordAutoEnroll( - masterKeyHash: string, + authenticationData: MasterPasswordAuthenticationData, orgId: string, userId: UserId, ) { @@ -238,7 +231,7 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi } const enrollmentRequest = new OrganizationUserResetPasswordEnrollmentRequest(); - enrollmentRequest.masterPasswordHash = masterKeyHash; + enrollmentRequest.masterPasswordHash = authenticationData.masterPasswordAuthenticationHash; enrollmentRequest.resetPasswordKey = orgPublicKeyEncryptedUserKey.encryptedString; await this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment( @@ -252,7 +245,7 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi credentials: SetInitialPasswordTdeOffboardingCredentials, userId: UserId, ) { - const { newMasterKey, newServerMasterKeyHash, newPasswordHint } = credentials; + const { initialPassword, kdf, salt, newPasswordHint } = credentials; for (const [key, value] of Object.entries(credentials)) { if (value == null) { throw new Error(`${key} not found. Could not set password.`); @@ -262,24 +255,24 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi if (userId == null) { throw new Error("userId not found. Could not set password."); } - const userKey = await firstValueFrom(this.keyService.userKey$(userId)); if (userKey == null) { throw new Error("userKey not found. Could not set password."); } - const newMasterKeyEncryptedUserKey = await this.keyService.encryptUserKeyWithMasterKey( - newMasterKey, + const authenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData( + initialPassword, + kdf, + salt, + ); + const unlockData = await this.masterPasswordService.makeMasterPasswordUnlockData( + initialPassword, + kdf, + salt, userKey, ); - if (!newMasterKeyEncryptedUserKey[1].encryptedString) { - throw new Error("newMasterKeyEncryptedUserKey not found. Could not set password."); - } - - const request = new UpdateTdeOffboardingPasswordRequest(); - request.key = newMasterKeyEncryptedUserKey[1].encryptedString; - request.newMasterPasswordHash = newServerMasterKeyHash; + const request = new UpdateTdeOffboardingPasswordRequest(authenticationData, unlockData); request.masterPasswordHint = newPasswordHint; await this.masterPasswordApiService.putUpdateTdeOffboardingPassword(request); diff --git a/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.spec.ts b/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.spec.ts index d96b4c86fb5..9edee2d4f95 100644 --- a/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.spec.ts +++ b/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.spec.ts @@ -157,8 +157,6 @@ describe("DefaultSetInitialPasswordService", () => { credentials.newPasswordHint, credentials.orgSsoIdentifier, keysRequest, - credentials.kdfConfig.kdfType, - credentials.kdfConfig.iterations, ); enrollmentRequest = new OrganizationUserResetPasswordEnrollmentRequest(); diff --git a/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts b/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts index 27d4c11f692..5adc3b15944 100644 --- a/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts +++ b/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts @@ -85,7 +85,7 @@ export class SetInitialPasswordComponent implements OnInit { private syncService: SyncService, private toastService: ToastService, private validationService: ValidationService, - ) {} + ) { } async ngOnInit() { await this.syncService.fullSync(true); @@ -209,10 +209,9 @@ export class SetInitialPasswordComponent implements OnInit { private async setInitialPassword(passwordInputResult: PasswordInputResult) { const ctx = "Could not set initial password."; - assertTruthy(passwordInputResult.newMasterKey, "newMasterKey", ctx); - assertTruthy(passwordInputResult.newServerMasterKeyHash, "newServerMasterKeyHash", ctx); - assertTruthy(passwordInputResult.newLocalMasterKeyHash, "newLocalMasterKeyHash", ctx); - assertTruthy(passwordInputResult.kdfConfig, "kdfConfig", ctx); + assertTruthy(passwordInputResult.newPassword, "newPassword", ctx); + assertTruthy(passwordInputResult.kdf, "kdf", ctx); + assertTruthy(passwordInputResult.salt, "salt", ctx); assertTruthy(this.orgSsoIdentifier, "orgSsoIdentifier", ctx); assertTruthy(this.orgId, "orgId", ctx); assertTruthy(this.userType, "userType", ctx); @@ -222,11 +221,10 @@ export class SetInitialPasswordComponent implements OnInit { try { const credentials: SetInitialPasswordCredentials = { - newMasterKey: passwordInputResult.newMasterKey, - newServerMasterKeyHash: passwordInputResult.newServerMasterKeyHash, - newLocalMasterKeyHash: passwordInputResult.newLocalMasterKeyHash, + initialPassword: passwordInputResult.newPassword, + kdf: passwordInputResult.kdf, + salt: passwordInputResult.salt, newPasswordHint: passwordInputResult.newPasswordHint, - kdfConfig: passwordInputResult.kdfConfig, orgSsoIdentifier: this.orgSsoIdentifier, orgId: this.orgId, resetPasswordAutoEnroll: this.resetPasswordAutoEnroll, @@ -251,15 +249,17 @@ export class SetInitialPasswordComponent implements OnInit { private async setInitialPasswordTdeOffboarding(passwordInputResult: PasswordInputResult) { const ctx = "Could not set initial password."; - assertTruthy(passwordInputResult.newMasterKey, "newMasterKey", ctx); - assertTruthy(passwordInputResult.newServerMasterKeyHash, "newServerMasterKeyHash", ctx); + assertTruthy(passwordInputResult.newPassword, "newPassword", ctx); + assertTruthy(passwordInputResult.kdf, "kdf", ctx); + assertTruthy(passwordInputResult.salt, "salt", ctx); assertTruthy(this.userId, "userId", ctx); assertNonNullish(passwordInputResult.newPasswordHint, "newPasswordHint", ctx); // can have an empty string as a valid value, so check non-nullish try { const credentials: SetInitialPasswordTdeOffboardingCredentials = { - newMasterKey: passwordInputResult.newMasterKey, - newServerMasterKeyHash: passwordInputResult.newServerMasterKeyHash, + initialPassword: passwordInputResult.newPassword, + kdf: passwordInputResult.kdf, + salt: passwordInputResult.salt, newPasswordHint: passwordInputResult.newPasswordHint, }; diff --git a/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.service.abstraction.ts b/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.service.abstraction.ts index c1f6ba1a5ec..705fa04ae5b 100644 --- a/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.service.abstraction.ts +++ b/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.service.abstraction.ts @@ -1,5 +1,5 @@ +import { MasterPasswordSalt } from "@bitwarden/common/key-management/master-password/types/master-password.types"; import { UserId } from "@bitwarden/common/types/guid"; -import { MasterKey } from "@bitwarden/common/types/key"; import { KdfConfig } from "@bitwarden/key-management"; export const _SetInitialPasswordUserType = { @@ -42,19 +42,21 @@ export const SetInitialPasswordUserType: Readonly<{ }> = Object.freeze(_SetInitialPasswordUserType); export interface SetInitialPasswordCredentials { - newMasterKey: MasterKey; - newServerMasterKeyHash: string; - newLocalMasterKeyHash: string; + initialPassword: string, + kdf: KdfConfig, + salt: MasterPasswordSalt, + newPasswordHint: string; - kdfConfig: KdfConfig; orgSsoIdentifier: string; orgId: string; resetPasswordAutoEnroll: boolean; } export interface SetInitialPasswordTdeOffboardingCredentials { - newMasterKey: MasterKey; - newServerMasterKeyHash: string; + initialPassword: string; + kdf: KdfConfig; + salt: MasterPasswordSalt; + newPasswordHint: string; } diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 1e31b511764..c51b66bfe0e 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -423,11 +423,11 @@ const safeProviders: SafeProvider[] = [ provide: LOGOUT_CALLBACK, useFactory: (messagingService: MessagingServiceAbstraction) => - async (logoutReason: LogoutReason, userId?: string) => { - return Promise.resolve( - messagingService.send("logout", { logoutReason: logoutReason, userId: userId }), - ); - }, + async (logoutReason: LogoutReason, userId?: string) => { + return Promise.resolve( + messagingService.send("logout", { logoutReason: logoutReason, userId: userId }), + ); + }, deps: [MessagingServiceAbstraction], }), safeProvider({ @@ -1024,6 +1024,7 @@ const safeProviders: SafeProvider[] = [ EncryptService, LogService, CryptoFunctionServiceAbstraction, + AccountServiceAbstraction ], }), safeProvider({ @@ -1614,4 +1615,4 @@ const safeProviders: SafeProvider[] = [ // Do not register your dependency here! Add it to the typesafeProviders array using the helper function providers: safeProviders, }) -export class JslibServicesModule {} +export class JslibServicesModule { } diff --git a/libs/auth/src/angular/index.ts b/libs/auth/src/angular/index.ts index aa0041c7ec3..63f827c9f95 100644 --- a/libs/auth/src/angular/index.ts +++ b/libs/auth/src/angular/index.ts @@ -42,7 +42,6 @@ export * from "./registration/registration-finish/registration-finish.service"; export * from "./registration/registration-finish/default-registration-finish.service"; // set password (JIT user) -export * from "./set-password-jit/set-password-jit.component"; export * from "./set-password-jit/set-password-jit.service.abstraction"; export * from "./set-password-jit/default-set-password-jit.service"; 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 5438674a4e2..e47cf451076 100644 --- a/libs/auth/src/angular/input-password/input-password.component.ts +++ b/libs/auth/src/angular/input-password/input-password.component.ts @@ -14,7 +14,6 @@ import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-manageme import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { HashPurpose } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -192,7 +191,7 @@ export class InputPasswordComponent implements OnInit { private policyService: PolicyService, private toastService: ToastService, private validationService: ValidationService, - ) {} + ) { } ngOnInit(): void { this.addFormFieldsIfNecessary(); @@ -300,6 +299,7 @@ export class InputPasswordComponent implements OnInit { if (this.kdfConfig == null) { throw new Error("KdfConfig is required to create master key."); } + const salt = await firstValueFrom(this.masterPasswordService.saltForAccount$(this.userId)); // 2. Verify current password is correct (if necessary) if ( @@ -326,61 +326,14 @@ export class InputPasswordComponent implements OnInit { } // 4. Create cryptographic keys and build a PasswordInputResult object - const newMasterKey = await this.keyService.makeMasterKey( - newPassword, - this.email, - this.kdfConfig, - ); - - const newServerMasterKeyHash = await this.keyService.hashMasterKey( - newPassword, - newMasterKey, - HashPurpose.ServerAuthorization, - ); - - const newLocalMasterKeyHash = await this.keyService.hashMasterKey( - newPassword, - newMasterKey, - HashPurpose.LocalAuthorization, - ); - const passwordInputResult: PasswordInputResult = { + currentPassword, newPassword, - newMasterKey, - newServerMasterKeyHash, - newLocalMasterKeyHash, newPasswordHint, - kdfConfig: this.kdfConfig, + kdf: this.kdfConfig, + salt, }; - if ( - this.flow === InputPasswordFlow.ChangePassword || - this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation - ) { - const currentMasterKey = await this.keyService.makeMasterKey( - currentPassword, - this.email, - this.kdfConfig, - ); - - const currentServerMasterKeyHash = await this.keyService.hashMasterKey( - currentPassword, - currentMasterKey, - HashPurpose.ServerAuthorization, - ); - - const currentLocalMasterKeyHash = await this.keyService.hashMasterKey( - currentPassword, - currentMasterKey, - HashPurpose.LocalAuthorization, - ); - - passwordInputResult.currentPassword = currentPassword; - passwordInputResult.currentMasterKey = currentMasterKey; - passwordInputResult.currentServerMasterKeyHash = currentServerMasterKeyHash; - passwordInputResult.currentLocalMasterKeyHash = currentLocalMasterKeyHash; - } - if (this.flow === InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation) { passwordInputResult.rotateUserKey = this.formGroup.controls.rotateUserKey?.value; } 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 37f337291e5..12ce143ff79 100644 --- a/libs/auth/src/angular/input-password/password-input-result.ts +++ b/libs/auth/src/angular/input-password/password-input-result.ts @@ -1,18 +1,11 @@ -import { MasterKey } from "@bitwarden/common/types/key"; +import { MasterPasswordSalt } from "@bitwarden/common/key-management/master-password/types/master-password.types"; import { KdfConfig } from "@bitwarden/key-management"; export interface PasswordInputResult { currentPassword?: string; - currentMasterKey?: MasterKey; - currentServerMasterKeyHash?: string; - currentLocalMasterKeyHash?: string; - newPassword: string; + kdf?: KdfConfig; + salt?: MasterPasswordSalt; newPasswordHint?: string; - newMasterKey?: MasterKey; - newServerMasterKeyHash?: string; - newLocalMasterKeyHash?: string; - - kdfConfig?: KdfConfig; rotateUserKey?: boolean; } diff --git a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.spec.ts b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.spec.ts index 608e0ac64b9..a7d4315b415 100644 --- a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.spec.ts +++ b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.spec.ts @@ -61,7 +61,7 @@ describe("DefaultRegistrationFinishService", () => { newMasterKey: masterKey, newServerMasterKeyHash: "newServerMasterKeyHash", newLocalMasterKeyHash: "newLocalMasterKeyHash", - kdfConfig: DEFAULT_KDF_CONFIG, + kdf: DEFAULT_KDF_CONFIG, newPasswordHint: "newPasswordHint", newPassword: "newPassword", }; @@ -100,8 +100,8 @@ describe("DefaultRegistrationFinishService", () => { publicKey: userKeyPair[0], encryptedPrivateKey: userKeyPair[1].encryptedString, }, - kdf: passwordInputResult.kdfConfig.kdfType, - kdfIterations: passwordInputResult.kdfConfig.iterations, + kdf: passwordInputResult.kdf.kdfType, + kdfIterations: passwordInputResult.kdf.iterations, kdfMemory: undefined, kdfParallelism: undefined, // Web only fields should be undefined diff --git a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts index b51f45f1b27..4d48b5986dc 100644 --- a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts +++ b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts @@ -4,9 +4,10 @@ import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/mod import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service"; import { RegisterFinishRequest } from "@bitwarden/common/auth/models/request/registration/register-finish.request"; import { - EncryptedString, EncString, } from "@bitwarden/common/key-management/crypto/models/enc-string"; +import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { MasterPasswordAuthenticationData, MasterPasswordUnlockData } from "@bitwarden/common/key-management/master-password/types/master-password.types"; import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; import { KeyService } from "@bitwarden/key-management"; @@ -18,7 +19,8 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi constructor( protected keyService: KeyService, protected accountApiService: AccountApiService, - ) {} + protected masterPasswordService: MasterPasswordServiceAbstraction, + ) { } getOrgNameFromOrgInvite(): Promise { return null; @@ -42,19 +44,25 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi providerInviteToken?: string, providerUserId?: string, ): Promise { - const [newUserKey, newEncUserKey] = await this.keyService.makeUserKey( - passwordInputResult.newMasterKey, - ); - - if (!newUserKey || !newEncUserKey) { - throw new Error("User key could not be created"); - } + const newUserKey = await this.keyService.makeUserKeyV1Raw(); const userAsymmetricKeys = await this.keyService.makeKeyPair(newUserKey); + const authenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData( + passwordInputResult.newPassword, + passwordInputResult.kdf, + passwordInputResult.salt, + ); + const unlockData = await this.masterPasswordService.makeMasterPasswordUnlockData( + passwordInputResult.newPassword, + passwordInputResult.kdf, + passwordInputResult.salt, + newUserKey, + ); const registerRequest = await this.buildRegisterRequest( email, passwordInputResult, - newEncUserKey.encryptedString, + authenticationData, + unlockData, userAsymmetricKeys, emailVerificationToken, orgSponsoredFreeFamilyPlanToken, @@ -70,7 +78,8 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi protected async buildRegisterRequest( email: string, passwordInputResult: PasswordInputResult, - encryptedUserKey: EncryptedString, + masterPasswordAuthenticationData: MasterPasswordAuthenticationData, + masterPasswordUnlockData: MasterPasswordUnlockData, userAsymmetricKeys: [string, EncString], emailVerificationToken?: string, orgSponsoredFreeFamilyPlanToken?: string, // web only @@ -86,12 +95,10 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi const registerFinishRequest = new RegisterFinishRequest( email, - passwordInputResult.newServerMasterKeyHash, passwordInputResult.newPasswordHint, - encryptedUserKey, + masterPasswordAuthenticationData, + masterPasswordUnlockData, userAsymmetricKeysRequest, - passwordInputResult.kdfConfig.kdfType, - passwordInputResult.kdfConfig.iterations, ); if (emailVerificationToken) { diff --git a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts index 6a9a37700cc..0da51b9d40c 100644 --- a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts +++ b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts @@ -113,7 +113,7 @@ describe("DefaultSetPasswordJitService", () => { newServerMasterKeyHash: "newServerMasterKeyHash", newLocalMasterKeyHash: "newLocalMasterKeyHash", newPasswordHint: "newPasswordHint", - kdfConfig: DEFAULT_KDF_CONFIG, + kdf: DEFAULT_KDF_CONFIG, newPassword: "newPassword", }; @@ -122,7 +122,7 @@ describe("DefaultSetPasswordJitService", () => { newServerMasterKeyHash: passwordInputResult.newServerMasterKeyHash, newLocalMasterKeyHash: passwordInputResult.newLocalMasterKeyHash, newPasswordHint: passwordInputResult.newPasswordHint, - kdfConfig: passwordInputResult.kdfConfig, + kdfConfig: passwordInputResult.kdf, orgSsoIdentifier, orgId, resetPasswordAutoEnroll, @@ -138,8 +138,8 @@ describe("DefaultSetPasswordJitService", () => { passwordInputResult.newPasswordHint, orgSsoIdentifier, keysRequest, - passwordInputResult.kdfConfig.kdfType, - passwordInputResult.kdfConfig.iterations, + passwordInputResult.kdf.kdfType, + passwordInputResult.kdf.iterations, ); }); 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 5fc3272b650..2f40cf9c268 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 @@ -16,11 +16,13 @@ import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-pa import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { MasterPasswordAuthenticationData } from "@bitwarden/common/key-management/master-password/types/master-password.types"; import { KeysRequest } from "@bitwarden/common/models/request/keys.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { HashPurpose } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { UserId } from "@bitwarden/common/types/guid"; -import { MasterKey, UserKey } from "@bitwarden/common/types/key"; +import { UserKey } from "@bitwarden/common/types/key"; import { KdfConfigService, KeyService, KdfConfig } from "@bitwarden/key-management"; import { @@ -39,19 +41,18 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService { protected organizationApiService: OrganizationApiServiceAbstraction, protected organizationUserApiService: OrganizationUserApiService, protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, - ) {} + ) { } async setPassword(credentials: SetPasswordCredentials): Promise { const { - newMasterKey, - newServerMasterKeyHash, - newLocalMasterKeyHash, + newPassword, newPasswordHint, - kdfConfig, orgSsoIdentifier, orgId, resetPasswordAutoEnroll, userId, + + kdfConfig, } = credentials; for (const [key, value] of Object.entries(credentials)) { @@ -60,23 +61,30 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService { } } - const protectedUserKey = await this.makeProtectedUserKey(newMasterKey, userId); - if (protectedUserKey == null) { - throw new Error("protectedUserKey not found. Could not set password."); - } + const userKey = await this.keyService.makeUserKeyV1Raw(); + const salt = await firstValueFrom(this.masterPasswordService.saltForAccount$(userId)); + const authenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData( + newPassword, + kdfConfig, + salt, + ); + const unlockData = await this.masterPasswordService.makeMasterPasswordUnlockData( + newPassword, + kdfConfig, + salt, + userKey, + ); // Since this is an existing JIT provisioned user in a MP encryption org setting first password, // they will not already have a user asymmetric key pair so we must create it for them. - const [keyPair, keysRequest] = await this.makeKeyPairAndRequest(protectedUserKey); + const [keyPair, keysRequest] = await this.makeKeyPairAndRequest(userKey); const request = new SetPasswordRequest( - newServerMasterKeyHash, - protectedUserKey[1].encryptedString, + authenticationData, + unlockData, newPasswordHint, orgSsoIdentifier, keysRequest, - kdfConfig.kdfType, - kdfConfig.iterations, ); await this.masterPasswordApiService.setPassword(request); @@ -84,39 +92,31 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService { // Clear force set password reason to allow navigation back to vault. await this.masterPasswordService.setForceSetPasswordReason(ForceSetPasswordReason.None, userId); + { + // TODO: Remove this block once master key and local master key hash are removed from state. This usage is deprecated. + const newMasterKey = await this.keyService.makeMasterKey(newPassword, salt, kdfConfig); + await this.masterPasswordService.setMasterKey(newMasterKey, userId); + const newLocalMasterKeyHash = await this.keyService.hashMasterKey( + newPassword, + newMasterKey, + HashPurpose.LocalAuthorization, + ); + await this.masterPasswordService.setMasterKeyHash(newLocalMasterKeyHash, userId); + } // User now has a password so update account decryption options in state - await this.updateAccountDecryptionProperties(newMasterKey, kdfConfig, protectedUserKey, userId); + await this.updateAccountDecryptionProperties(kdfConfig, userKey, userId); await this.keyService.setPrivateKey(keyPair[1].encryptedString, userId); - await this.masterPasswordService.setMasterKeyHash(newLocalMasterKeyHash, userId); - if (resetPasswordAutoEnroll) { - await this.handleResetPasswordAutoEnroll(newServerMasterKeyHash, orgId, userId); + await this.handleResetPasswordAutoEnroll(authenticationData, orgId, userId); } } - private async makeProtectedUserKey( - masterKey: MasterKey, - userId: UserId, - ): Promise<[UserKey, EncString]> { - let protectedUserKey: [UserKey, EncString] = null; - - const userKey = await firstValueFrom(this.keyService.userKey$(userId)); - - if (userKey == null) { - protectedUserKey = await this.keyService.makeUserKey(masterKey); - } else { - protectedUserKey = await this.keyService.encryptUserKeyWithMasterKey(masterKey); - } - - return protectedUserKey; - } - private async makeKeyPairAndRequest( - protectedUserKey: [UserKey, EncString], + userKey: UserKey, ): Promise<[[string, EncString], KeysRequest]> { - const keyPair = await this.keyService.makeKeyPair(protectedUserKey[0]); + const keyPair = await this.keyService.makeKeyPair(userKey); if (keyPair == null) { throw new Error("keyPair not found. Could not set password."); } @@ -126,9 +126,8 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService { } private async updateAccountDecryptionProperties( - masterKey: MasterKey, kdfConfig: KdfConfig, - protectedUserKey: [UserKey, EncString], + userKey: UserKey, userId: UserId, ) { const userDecryptionOpts = await firstValueFrom( @@ -137,12 +136,11 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService { userDecryptionOpts.hasMasterPassword = true; await this.userDecryptionOptionsService.setUserDecryptionOptions(userDecryptionOpts); await this.kdfConfigService.setKdfConfig(userId, kdfConfig); - await this.masterPasswordService.setMasterKey(masterKey, userId); - await this.keyService.setUserKey(protectedUserKey[0], userId); + await this.keyService.setUserKey(userKey, userId); } private async handleResetPasswordAutoEnroll( - masterKeyHash: string, + masterPasswordAuthenticationData: MasterPasswordAuthenticationData, orgId: string, userId: UserId, ) { @@ -164,7 +162,7 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService { const encryptedUserKey = await this.encryptService.encapsulateKeyUnsigned(userKey, publicKey); const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest(); - resetRequest.masterPasswordHash = masterKeyHash; + resetRequest.masterPasswordHash = masterPasswordAuthenticationData.masterPasswordAuthenticationHash; resetRequest.resetPasswordKey = encryptedUserKey.encryptedString; await this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment( diff --git a/libs/auth/src/angular/set-password-jit/set-password-jit.component.html b/libs/auth/src/angular/set-password-jit/set-password-jit.component.html deleted file mode 100644 index 797f18732cb..00000000000 --- a/libs/auth/src/angular/set-password-jit/set-password-jit.component.html +++ /dev/null @@ -1,24 +0,0 @@ - - - {{ "loading" | i18n }} - - - - - {{ "resetPasswordAutoEnrollInviteWarning" | i18n }} - - - - diff --git a/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts b/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts deleted file mode 100644 index 1a2674cd3d4..00000000000 --- a/libs/auth/src/angular/set-password-jit/set-password-jit.component.ts +++ /dev/null @@ -1,135 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { CommonModule } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { UserId } from "@bitwarden/common/types/guid"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; - -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { ToastService } from "../../../../components/src/toast"; -import { - InputPasswordComponent, - InputPasswordFlow, -} from "../input-password/input-password.component"; -import { PasswordInputResult } from "../input-password/password-input-result"; - -import { - SetPasswordCredentials, - SetPasswordJitService, -} from "./set-password-jit.service.abstraction"; - -@Component({ - selector: "auth-set-password-jit", - templateUrl: "set-password-jit.component.html", - imports: [CommonModule, InputPasswordComponent, JslibModule], -}) -export class SetPasswordJitComponent implements OnInit { - protected inputPasswordFlow = InputPasswordFlow.SetInitialPasswordAuthedUser; - protected email: string; - protected masterPasswordPolicyOptions: MasterPasswordPolicyOptions; - protected orgId: string; - protected orgSsoIdentifier: string; - protected resetPasswordAutoEnroll: boolean; - protected submitting = false; - protected syncLoading = true; - protected userId: UserId; - - constructor( - private accountService: AccountService, - private activatedRoute: ActivatedRoute, - private i18nService: I18nService, - private organizationApiService: OrganizationApiServiceAbstraction, - private policyApiService: PolicyApiServiceAbstraction, - private router: Router, - private setPasswordJitService: SetPasswordJitService, - private syncService: SyncService, - private toastService: ToastService, - private validationService: ValidationService, - ) {} - - async ngOnInit() { - const activeAccount = await firstValueFrom(this.accountService.activeAccount$); - this.userId = activeAccount?.id; - this.email = activeAccount?.email; - - await this.syncService.fullSync(true); - this.syncLoading = false; - - await this.handleQueryParams(); - } - - private async handleQueryParams() { - const qParams = await firstValueFrom(this.activatedRoute.queryParams); - - if (qParams.identifier != null) { - try { - this.orgSsoIdentifier = qParams.identifier; - - const autoEnrollStatus = await this.organizationApiService.getAutoEnrollStatus( - this.orgSsoIdentifier, - ); - this.orgId = autoEnrollStatus.id; - this.resetPasswordAutoEnroll = autoEnrollStatus.resetPasswordEnabled; - this.masterPasswordPolicyOptions = - await this.policyApiService.getMasterPasswordPolicyOptsForOrgUser(autoEnrollStatus.id); - } catch { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("errorOccurred"), - }); - } - } - } - - protected async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) { - this.submitting = true; - - const credentials: SetPasswordCredentials = { - newMasterKey: passwordInputResult.newMasterKey, - newServerMasterKeyHash: passwordInputResult.newServerMasterKeyHash, - newLocalMasterKeyHash: passwordInputResult.newLocalMasterKeyHash, - newPasswordHint: passwordInputResult.newPasswordHint, - kdfConfig: passwordInputResult.kdfConfig, - orgSsoIdentifier: this.orgSsoIdentifier, - orgId: this.orgId, - resetPasswordAutoEnroll: this.resetPasswordAutoEnroll, - userId: this.userId, - }; - - try { - await this.setPasswordJitService.setPassword(credentials); - } catch (e) { - this.validationService.showError(e); - this.submitting = false; - return; - } - - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("accountSuccessfullyCreated"), - }); - - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("inviteAccepted"), - }); - - this.submitting = false; - - await this.router.navigate(["vault"]); - } -} diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts index 9b402f3a956..5c8ee00450d 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts @@ -8,6 +8,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; +import { MasterPasswordAuthenticationHash } from "@bitwarden/common/key-management/master-password/types/master-password.types"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -66,7 +67,7 @@ export class TwoFactorAuthEmailComponent implements OnInit { protected appIdService: AppIdService, private toastService: ToastService, private cacheService: TwoFactorAuthEmailComponentCacheService, - ) {} + ) { } async ngOnInit(): Promise { // Check if email was already sent @@ -125,7 +126,7 @@ export class TwoFactorAuthEmailComponent implements OnInit { const request = new TwoFactorEmailRequest(); request.email = email; - request.masterPasswordHash = (await this.loginStrategyService.getMasterPasswordHash()) ?? ""; + request.masterPasswordHash = ((await this.loginStrategyService.getMasterPasswordHash()) ?? "") as MasterPasswordAuthenticationHash; request.ssoEmail2FaSessionToken = (await this.loginStrategyService.getSsoEmail2FaSessionToken()) ?? ""; request.deviceIdentifier = await this.appIdService.getAppId(); diff --git a/libs/common/src/auth/models/request/email-token.request.ts b/libs/common/src/auth/models/request/email-token.request.ts index 71d7d68b208..d7a658f75fb 100644 --- a/libs/common/src/auth/models/request/email-token.request.ts +++ b/libs/common/src/auth/models/request/email-token.request.ts @@ -4,5 +4,4 @@ import { SecretVerificationRequest } from "./secret-verification.request"; export class EmailTokenRequest extends SecretVerificationRequest { newEmail: string; - masterPasswordHash: string; } diff --git a/libs/common/src/auth/models/request/email.request.ts b/libs/common/src/auth/models/request/email.request.ts index 12fdff149cc..fe6f1461fe4 100644 --- a/libs/common/src/auth/models/request/email.request.ts +++ b/libs/common/src/auth/models/request/email.request.ts @@ -1,9 +1,18 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { EncryptedString } from "@bitwarden/common/key-management/crypto/models/enc-string"; +import { MasterPasswordAuthenticationData, MasterPasswordAuthenticationHash, MasterPasswordUnlockData } from "@bitwarden/common/key-management/master-password/types/master-password.types"; + import { EmailTokenRequest } from "./email-token.request"; export class EmailRequest extends EmailTokenRequest { - newMasterPasswordHash: string; - token: string; - key: string; + newMasterPasswordHash: MasterPasswordAuthenticationHash; + token: string + key: EncryptedString; + + constructor(authenticationData: MasterPasswordAuthenticationData, unlockData: MasterPasswordUnlockData) { + super(); + this.masterPasswordHash = authenticationData.masterPasswordAuthenticationHash; + this.newMasterPasswordHash = authenticationData.masterPasswordAuthenticationHash; + } } diff --git a/libs/common/src/auth/models/request/password.request.ts b/libs/common/src/auth/models/request/password.request.ts index 674754ff41a..b036cc5db9d 100644 --- a/libs/common/src/auth/models/request/password.request.ts +++ b/libs/common/src/auth/models/request/password.request.ts @@ -1,9 +1,26 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore +import { MasterPasswordAuthenticationData, MasterPasswordUnlockData } from "../../../key-management/master-password/types/master-password.types"; + import { SecretVerificationRequest } from "./secret-verification.request"; export class PasswordRequest extends SecretVerificationRequest { + masterPasswordHint: string | null = null; + + masterPasswordUnlockData: MasterPasswordUnlockData; + masterPasswordAuthenticationData: MasterPasswordAuthenticationData; + + /** @deprecated */ newMasterPasswordHash: string; - masterPasswordHint: string; + /** @deprecated */ key: string; + + constructor(masterPasswordAuthenticationData: MasterPasswordAuthenticationData, masterPasswordUnlockData: MasterPasswordUnlockData) { + super(); + this.masterPasswordUnlockData = masterPasswordUnlockData; + this.masterPasswordAuthenticationData = masterPasswordAuthenticationData; + + { // TODO: This will be removed once the deprecated properties are removed + this.newMasterPasswordHash = masterPasswordAuthenticationData.masterPasswordAuthenticationHash; + this.key = masterPasswordUnlockData.masterKeyWrappedUserKey.encryptedString; + } + } } diff --git a/libs/common/src/auth/models/request/registration/register-finish.request.ts b/libs/common/src/auth/models/request/registration/register-finish.request.ts index b388e8aee49..69ad0316b59 100644 --- a/libs/common/src/auth/models/request/registration/register-finish.request.ts +++ b/libs/common/src/auth/models/request/registration/register-finish.request.ts @@ -1,4 +1,6 @@ // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. + +import { MasterPasswordAuthenticationData, MasterPasswordAuthenticationHash, MasterPasswordUnlockData } from "@bitwarden/common/key-management/master-password/types/master-password.types"; // eslint-disable-next-line no-restricted-imports import { KdfType } from "@bitwarden/key-management"; @@ -6,20 +8,20 @@ import { EncryptedString } from "../../../../key-management/crypto/models/enc-st import { KeysRequest } from "../../../../models/request/keys.request"; export class RegisterFinishRequest { + + masterPasswordHash: MasterPasswordAuthenticationHash; + userSymmetricKey: EncryptedString; + kdf: KdfType; + kdfIterations: number; + kdfMemory?: number; + kdfParallelism?: number; + constructor( public email: string, - - public masterPasswordHash: string, public masterPasswordHint: string, - - public userSymmetricKey: EncryptedString, + masterPasswordAuthenticationData: MasterPasswordAuthenticationData, + masterPasswordUnlockData: MasterPasswordUnlockData, public userAsymmetricKeys: KeysRequest, - - public kdf: KdfType, - public kdfIterations: number, - public kdfMemory?: number, - public kdfParallelism?: number, - public emailVerificationToken?: string, public orgSponsoredFreeFamilyPlanToken?: string, public acceptEmergencyAccessInviteToken?: string, @@ -30,5 +32,23 @@ export class RegisterFinishRequest { // Org Invite data (only applies on web) public organizationUserId?: string, public orgInviteToken?: string, - ) {} + ) { + this.masterPasswordHash = masterPasswordAuthenticationData.masterPasswordAuthenticationHash; + this.userSymmetricKey = masterPasswordUnlockData.masterKeyWrappedUserKey.encryptedString; + + const kdf = masterPasswordAuthenticationData.kdf; + if (kdf.kdfType === KdfType.PBKDF2_SHA256) { + this.kdf = KdfType.PBKDF2_SHA256; + this.kdfIterations = kdf.iterations; + this.kdfMemory = undefined; + this.kdfParallelism = undefined; + } else if (kdf.kdfType === KdfType.Argon2id) { + this.kdf = KdfType.Argon2id; + this.kdfIterations = kdf.iterations; + this.kdfMemory = kdf.memory; + this.kdfParallelism = kdf.parallelism; + } else { + throw new Error(`Unsupported KDF type: ${kdf}`); + } + } } diff --git a/libs/common/src/auth/models/request/secret-verification.request.ts b/libs/common/src/auth/models/request/secret-verification.request.ts index bb5d913656e..75892f1130a 100644 --- a/libs/common/src/auth/models/request/secret-verification.request.ts +++ b/libs/common/src/auth/models/request/secret-verification.request.ts @@ -1,7 +1,10 @@ // FIXME: Update this file to be type safe and remove this and next line + +import { MasterPasswordAuthenticationHash } from "@bitwarden/common/key-management/master-password/types/master-password.types"; + // @ts-strict-ignore export class SecretVerificationRequest { - masterPasswordHash: string; + masterPasswordHash: MasterPasswordAuthenticationHash; otp: string; authRequestAccessCode: string; } diff --git a/libs/common/src/auth/models/request/set-password.request.ts b/libs/common/src/auth/models/request/set-password.request.ts index 7206cd98623..56451215b61 100644 --- a/libs/common/src/auth/models/request/set-password.request.ts +++ b/libs/common/src/auth/models/request/set-password.request.ts @@ -1,3 +1,4 @@ +import { MasterKeyWrappedUserKey, MasterPasswordAuthenticationData, MasterPasswordAuthenticationHash, MasterPasswordUnlockData } from "@bitwarden/common/key-management/master-password/types/master-password.types"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // eslint-disable-next-line no-restricted-imports import { KdfType } from "@bitwarden/key-management"; @@ -5,35 +6,50 @@ import { KdfType } from "@bitwarden/key-management"; import { KeysRequest } from "../../../models/request/keys.request"; export class SetPasswordRequest { - masterPasswordHash: string; - key: string; + // TODO: This will be replaced by masterPasswordAuthenticationData in the future + masterPasswordHash: MasterPasswordAuthenticationHash; + // TODO: This will be replaced by masterPasswordAuthenticationData in the future + key: MasterKeyWrappedUserKey; + masterPasswordHint: string; - keys: KeysRequest | null; - kdf: KdfType; - kdfIterations: number; - kdfMemory?: number; - kdfParallelism?: number; orgIdentifier: string; + keys: KeysRequest | null; + + /** @deprecated */ + kdf: KdfType; + /** @deprecated */ + kdfIterations: number; + /** @deprecated */ + kdfMemory?: number; + /** @deprecated */ + kdfParallelism?: number; constructor( - masterPasswordHash: string, - key: string, + masterPasswordAuthenticationData: MasterPasswordAuthenticationData, + masterPasswordUnlockData: MasterPasswordUnlockData, masterPasswordHint: string, orgIdentifier: string, - keys: KeysRequest | null, - kdf: KdfType, - kdfIterations: number, - kdfMemory?: number, - kdfParallelism?: number, + keys: KeysRequest | null ) { - this.masterPasswordHash = masterPasswordHash; - this.key = key; + this.masterPasswordHash = masterPasswordAuthenticationData.masterPasswordAuthenticationHash; + this.key = masterPasswordUnlockData.masterKeyWrappedUserKey; this.masterPasswordHint = masterPasswordHint; - this.kdf = kdf; - this.kdfIterations = kdfIterations; - this.kdfMemory = kdfMemory; - this.kdfParallelism = kdfParallelism; + this.orgIdentifier = orgIdentifier; this.keys = keys; + + // This will be removed when the deprecated properties are removed + const kdf = masterPasswordAuthenticationData.kdf; + if (kdf.kdfType === KdfType.PBKDF2_SHA256) { + this.kdf = KdfType.PBKDF2_SHA256; + this.kdfIterations = kdf.iterations; + } else if (kdf.kdfType === KdfType.Argon2id) { + this.kdf = KdfType.Argon2id; + this.kdfIterations = kdf.iterations; + this.kdfMemory = kdf.memory; + this.kdfParallelism = kdf.parallelism; + } else { + throw new Error(`Unsupported KDF type: ${kdf}`); + } } } diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.ts b/libs/common/src/auth/services/user-verification/user-verification.service.ts index 5837042b93f..2ce67efda9b 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.ts @@ -6,7 +6,8 @@ import { firstValueFrom, map } from "rxjs"; // eslint-disable-next-line no-restricted-imports import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. -// eslint-disable-next-line no-restricted-imports + +import { MasterPasswordAuthenticationHash } from "@bitwarden/common/key-management/master-password/types/master-password.types"; import { BiometricsService, BiometricsStatus, @@ -55,7 +56,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti private pinService: PinServiceAbstraction, private kdfConfigService: KdfConfigService, private biometricsService: BiometricsService, - ) {} + ) { } async getAvailableVerificationOptions( verificationType: keyof UserVerificationOptions, @@ -113,20 +114,15 @@ export class UserVerificationService implements UserVerificationServiceAbstracti if (verification.type === VerificationType.OTP) { request.otp = verification.secret; } else { - const [userId, email] = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => [a?.id, a?.email])), + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); - let masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId)); - if (!masterKey && !alreadyHashed) { - masterKey = await this.keyService.makeMasterKey( - verification.secret, - email, - await this.kdfConfigService.getKdfConfig(userId), - ); - } + const kdf = await this.kdfConfigService.getKdfConfig(userId); + const salt = await firstValueFrom(this.masterPasswordService.saltForAccount$(userId)); + request.masterPasswordHash = alreadyHashed ? verification.secret - : await this.keyService.hashMasterKey(verification.secret, masterKey); + : (await this.masterPasswordService.makeMasterPasswordAuthenticationData(verification.secret, kdf, salt)).masterPasswordAuthenticationHash; } return request; diff --git a/libs/common/src/auth/types/verification.ts b/libs/common/src/auth/types/verification.ts index 4f45a6fdeed..d1f157fed14 100644 --- a/libs/common/src/auth/types/verification.ts +++ b/libs/common/src/auth/types/verification.ts @@ -1,5 +1,6 @@ // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. -// eslint-disable-next-line no-restricted-imports + +import { MasterPasswordAuthenticationHash } from "@bitwarden/common/key-management/master-password/types/master-password.types"; import { KdfConfig } from "@bitwarden/key-management"; import { MasterKey } from "../../types/key"; @@ -7,7 +8,7 @@ import { VerificationType } from "../enums/verification-type"; import { MasterPasswordPolicyResponse } from "../models/response/master-password-policy.response"; export type OtpVerification = { type: VerificationType.OTP; secret: string }; -export type MasterPasswordVerification = { type: VerificationType.MasterPassword; secret: string }; +export type MasterPasswordVerification = { type: VerificationType.MasterPassword; secret: MasterPasswordAuthenticationHash }; export type PinVerification = { type: VerificationType.PIN; secret: string }; export type BiometricsVerification = { type: VerificationType.Biometrics }; diff --git a/libs/common/src/key-management/crypto/models/enc-string.ts b/libs/common/src/key-management/crypto/models/enc-string.ts index 1ff98d1b6b6..6d1b3e740f0 100644 --- a/libs/common/src/key-management/crypto/models/enc-string.ts +++ b/libs/common/src/key-management/crypto/models/enc-string.ts @@ -2,12 +2,15 @@ // @ts-strict-ignore import { Jsonify, Opaque } from "type-fest"; +import { EncString as SdkEncString } from "@bitwarden/sdk-internal"; + import { EncryptionType, EXPECTED_NUM_PARTS_BY_ENCRYPTION_TYPE } from "../../../platform/enums"; import { Encrypted } from "../../../platform/interfaces/encrypted"; import { Utils } from "../../../platform/misc/utils"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { EncryptService } from "../abstractions/encrypt.service"; + export const DECRYPT_ERROR = "[error: cannot decrypt]"; export class EncString implements Encrypted { @@ -55,6 +58,18 @@ export class EncString implements Encrypted { return new EncString(obj); } + static fromSdk(obj: SdkEncString): EncString { + return new EncString(obj); + } + + toSdk(): SdkEncString { + return this.encryptedString as SdkEncString; + } + + static fromEncryptedString(obj: EncryptedString): EncString { + return new EncString(obj); + } + private initFromData(encType: EncryptionType, data: string, iv: string, mac: string) { if (iv != null) { this.encryptedString = (encType + "." + iv + "|" + data) as EncryptedString; diff --git a/libs/common/src/key-management/device-trust/abstractions/device-trust.service.abstraction.ts b/libs/common/src/key-management/device-trust/abstractions/device-trust.service.abstraction.ts index d688c7f366b..3f0747dbd8d 100644 --- a/libs/common/src/key-management/device-trust/abstractions/device-trust.service.abstraction.ts +++ b/libs/common/src/key-management/device-trust/abstractions/device-trust.service.abstraction.ts @@ -8,6 +8,7 @@ import { DeviceResponse } from "../../../auth/abstractions/devices/responses/dev import { UserId } from "../../../types/guid"; import { DeviceKey, UserKey } from "../../../types/key"; import { EncString } from "../../crypto/models/enc-string"; +import { MasterPasswordAuthenticationData } from "../../master-password/types/master-password.types"; export abstract class DeviceTrustServiceAbstraction { /** @@ -50,7 +51,7 @@ export abstract class DeviceTrustServiceAbstraction { rotateDevicesTrust: ( userId: UserId, newUserKey: UserKey, - masterPasswordHash: string, + masterPasswordAuthenticationData: MasterPasswordAuthenticationData, ) => Promise; /** * Notifies the server that the device has a device key, but didn't receive any associated decryption keys. diff --git a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts index 372b3282a72..40e5da185c3 100644 --- a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts @@ -33,6 +33,7 @@ import { UserKey, DeviceKey } from "../../../types/key"; import { CryptoFunctionService } from "../../crypto/abstractions/crypto-function.service"; import { EncryptService } from "../../crypto/abstractions/encrypt.service"; import { EncString } from "../../crypto/models/enc-string"; +import { MasterPasswordAuthenticationData } from "../../master-password/types/master-password.types"; import { DeviceTrustServiceAbstraction } from "../abstractions/device-trust.service.abstraction"; /** Uses disk storage so that the device key can persist after log out and tab removal. */ @@ -256,7 +257,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { async rotateDevicesTrust( userId: UserId, newUserKey: UserKey, - masterPasswordHash: string, + masterPasswordAuthenticationData: MasterPasswordAuthenticationData, ): Promise { this.logService.info("[Device trust rotation] Rotating device trust..."); if (!userId) { @@ -279,7 +280,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { const deviceIdentifier = await this.appIdService.getAppId(); const secretVerificationRequest = new SecretVerificationRequest(); - secretVerificationRequest.masterPasswordHash = masterPasswordHash; + secretVerificationRequest.masterPasswordHash = masterPasswordAuthenticationData.masterPasswordAuthenticationHash; // Get the keys that are used in rotating a devices keys from the server const currentDeviceKeys = await this.devicesApiService.getDeviceKeys(deviceIdentifier); @@ -310,7 +311,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { // then it can be added to trustRequest.otherDevices. const trustRequest = new UpdateDevicesTrustRequest(); - trustRequest.masterPasswordHash = masterPasswordHash; + trustRequest.masterPasswordHash = masterPasswordAuthenticationData.masterPasswordAuthenticationHash; trustRequest.currentDevice = currentDeviceUpdateRequest; trustRequest.otherDevices = []; diff --git a/libs/common/src/key-management/kdf/abstractions/change-kdf-service.ts b/libs/common/src/key-management/kdf/abstractions/change-kdf-service.ts new file mode 100644 index 00000000000..66cf9f1ae93 --- /dev/null +++ b/libs/common/src/key-management/kdf/abstractions/change-kdf-service.ts @@ -0,0 +1,8 @@ +// eslint-disable-next-line no-restricted-imports +import { KdfConfig } from "@bitwarden/key-management"; + +import { UserId } from "../../../types/guid"; + +export abstract class ChangeKdfServiceAbstraction { + abstract updateUserKdfParams(masterPassword: string, kdf: KdfConfig, userId: UserId): Promise; +} diff --git a/libs/common/src/key-management/kdf/services/change-kdf-service.ts b/libs/common/src/key-management/kdf/services/change-kdf-service.ts new file mode 100644 index 00000000000..28d71652b5b --- /dev/null +++ b/libs/common/src/key-management/kdf/services/change-kdf-service.ts @@ -0,0 +1,23 @@ +import { firstValueFrom } from "rxjs"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { KdfRequest } from "@bitwarden/common/models/request/kdf.request"; +import { UserId } from "@bitwarden/common/types/guid"; +// eslint-disable-next-line no-restricted-imports +import { KdfConfig, KeyService } from "@bitwarden/key-management"; + +import { MasterPasswordServiceAbstraction } from "../../master-password/abstractions/master-password.service.abstraction"; +import { ChangeKdfServiceAbstraction } from "../abstractions/change-kdf-service"; + +export class ChangeKdfService implements ChangeKdfServiceAbstraction { + constructor(private apiService: ApiService, private masterPasswordService: MasterPasswordServiceAbstraction, private keyService: KeyService) { } + + async updateUserKdfParams(masterPassword: string, kdf: KdfConfig, userId: UserId): Promise { + const userKey = await firstValueFrom(this.keyService.userKey$(userId)); + const salt = await firstValueFrom(this.masterPasswordService.saltForAccount$(userId)); + const authenticationData = await this.masterPasswordService.makeMasterPasswordAuthenticationData(masterPassword, kdf, salt); + const unlockData = await this.masterPasswordService.makeMasterPasswordUnlockData(masterPassword, kdf, salt, userKey); + const request = new KdfRequest(authenticationData, unlockData); + return this.apiService.send("POST", "/accounts/kdf", request, true, false); + } +} diff --git a/libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts b/libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts index 53ee650ed79..28ab5c3f5f3 100644 --- a/libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts +++ b/libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts @@ -20,6 +20,10 @@ export abstract class MasterPasswordServiceAbstraction { * @throws If the user ID is missing. */ abstract forceSetPasswordReason$: (userId: UserId) => Observable; + /** + * + */ + abstract saltForAccount$: (userId: UserId) => Observable; /** * An observable that emits the master key for the user. * @deprecated Interacting with the master-key directly is deprecated. Please use {@link makeMasterPasswordUnlockData}, {@link makeMasterPasswordAuthenticationData} or {@link unwrapUserKeyFromMasterPasswordUnlockData} instead. diff --git a/libs/common/src/key-management/master-password/services/fake-master-password.service.ts b/libs/common/src/key-management/master-password/services/fake-master-password.service.ts index 755a3c56509..281016d2bba 100644 --- a/libs/common/src/key-management/master-password/services/fake-master-password.service.ts +++ b/libs/common/src/key-management/master-password/services/fake-master-password.service.ts @@ -33,6 +33,10 @@ export class FakeMasterPasswordService implements InternalMasterPasswordServiceA this.masterKeyHashSubject.next(initialMasterKeyHash); } + saltForAccount$(userId: UserId): Observable { + return this.mock.saltForAccount$(userId); + } + masterKey$(userId: UserId): Observable { return this.masterKeySubject.asObservable(); } diff --git a/libs/common/src/key-management/master-password/services/master-password.service.ts b/libs/common/src/key-management/master-password/services/master-password.service.ts index 6f2d8689221..bbb2558d61e 100644 --- a/libs/common/src/key-management/master-password/services/master-password.service.ts +++ b/libs/common/src/key-management/master-password/services/master-password.service.ts @@ -2,6 +2,7 @@ // @ts-strict-ignore import { firstValueFrom, map, Observable } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; // eslint-disable-next-line no-restricted-imports @@ -74,7 +75,8 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr private encryptService: EncryptService, private logService: LogService, private cryptoFunctionService: CryptoFunctionService, - ) {} + private accountService: AccountService, + ) { } masterKey$(userId: UserId): Observable { if (userId == null) { @@ -83,6 +85,13 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr return this.stateProvider.getUser(userId, MASTER_KEY).state$; } + saltForAccount$(userId: UserId): Observable { + if (userId == null) { + throw new Error("User ID is required."); + } + return this.accountService.activeAccount$.pipe(map((a) => a?.email), map((email) => this.emailToSalt(email))); + } + masterKeyHash$(userId: UserId): Observable { if (userId == null) { throw new Error("User ID is required."); diff --git a/libs/common/src/models/request/kdf.request.ts b/libs/common/src/models/request/kdf.request.ts index 7ffdbcb4a4b..fd5e38ef36f 100644 --- a/libs/common/src/models/request/kdf.request.ts +++ b/libs/common/src/models/request/kdf.request.ts @@ -1,14 +1,33 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +import { MasterPasswordAuthenticationData, MasterPasswordUnlockData } from "@bitwarden/common/key-management/master-password/types/master-password.types"; // eslint-disable-next-line no-restricted-imports import { KdfType } from "@bitwarden/key-management"; import { PasswordRequest } from "../../auth/models/request/password.request"; export class KdfRequest extends PasswordRequest { + /** @deprecated */ kdf: KdfType; + /** @deprecated */ kdfIterations: number; + /** @deprecated */ kdfMemory?: number; - kdfParallelism?: number; + /** @deprecated */ + kdfParallelism?: number;; + + constructor(authenticationData: MasterPasswordAuthenticationData, unlockData: MasterPasswordUnlockData) { + super(authenticationData, unlockData); + + const kdf = authenticationData.kdf; + if (kdf.kdfType === KdfType.PBKDF2_SHA256) { + this.kdf = KdfType.PBKDF2_SHA256; + this.kdfIterations = kdf.iterations; + } else if (kdf.kdfType === KdfType.Argon2id) { + this.kdf = KdfType.Argon2id; + this.kdfIterations = kdf.iterations; + this.kdfMemory = kdf.memory; + this.kdfParallelism = kdf.parallelism; + } else { + throw new Error(`Unsupported KDF type: ${kdf}`); + } + } } diff --git a/libs/key-management/src/abstractions/key.service.ts b/libs/key-management/src/abstractions/key.service.ts index c843d8dc872..a4910f079ab 100644 --- a/libs/key-management/src/abstractions/key.service.ts +++ b/libs/key-management/src/abstractions/key.service.ts @@ -148,6 +148,12 @@ export abstract class KeyService { */ abstract hasUserKey(userId: UserId): Promise; + /** + * Makes a v1 user key, and does not wrap it. + * @deprecated Note: This is a place-holder until account registration is moved to a higher level + * / migrated to the SDK, and should not be used for new features. + */ + abstract makeUserKeyV1Raw(): Promise; /** * Generates a new user key * @throws Error when master key is null and there is no active user @@ -170,6 +176,7 @@ export abstract class KeyService { abstract getOrDeriveMasterKey(password: string, userId?: string): Promise; /** * Generates a master key from the provided password + * @deprecated * @param password The user's master password * @param email The user's email * @param KdfConfig The user's key derivation function configuration @@ -191,6 +198,7 @@ export abstract class KeyService { * Creates a master password hash from the user's master password. Can * be used for local authentication or for server authentication depending * on the hashPurpose provided. + * @deprecated * @throws Error when password is null or key is null and no active user or active user have no master key * @param password The user's master password * @param key The user's master key or active's user master key. diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index 7c1041a42f5..a186cf866e8 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -209,6 +209,11 @@ export class DefaultKeyService implements KeyServiceAbstraction { return (await firstValueFrom(this.stateProvider.getUserState$(USER_KEY, userId))) != null; } + async makeUserKeyV1Raw(): Promise { + const newUserKey = await this.keyGenerationService.createKey(512); + return newUserKey as UserKey; + } + /** * @deprecated Please use `makeMasterPasswordUnlockData` in {@link MasterPasswordService} instead. */ diff --git a/libs/key-management/src/models/kdf-config.ts b/libs/key-management/src/models/kdf-config.ts index a2ed8a22505..6a47a15f661 100644 --- a/libs/key-management/src/models/kdf-config.ts +++ b/libs/key-management/src/models/kdf-config.ts @@ -145,4 +145,19 @@ export class Argon2KdfConfig { } } +export function kdfConfigFromValues( + kdfType: KdfType, + iterations: number, + memory?: number, + parallelism?: number, +): KdfConfig { + if (kdfType === KdfType.PBKDF2_SHA256) { + return new PBKDF2KdfConfig(iterations); + } else if (kdfType === KdfType.Argon2id) { + return new Argon2KdfConfig(iterations, memory, parallelism); + } else { + throw new Error(`Unsupported KDF type: ${kdfType}`); + } +} + export const DEFAULT_KDF_CONFIG = new PBKDF2KdfConfig(PBKDF2KdfConfig.ITERATIONS.defaultValue);