import { Injectable } from "@angular/core"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { OrganizationUserResetPasswordRequest, OrganizationUserResetPasswordWithIdRequest, } from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; import { Argon2KdfConfig, KdfConfig, PBKDF2KdfConfig, } from "@bitwarden/common/auth/models/domain/kdf-config"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { KdfType } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { UserKey } from "@bitwarden/common/types/key"; @Injectable({ providedIn: "root", }) export class OrganizationUserResetPasswordService { constructor( private cryptoService: CryptoService, private encryptService: EncryptService, private organizationService: OrganizationService, private organizationUserService: OrganizationUserService, private organizationApiService: OrganizationApiServiceAbstraction, private i18nService: I18nService, ) {} /** * Returns the user key encrypted by the organization's public key. * Intended for use in enrollment * @param orgId desired organization */ async buildRecoveryKey(orgId: string, userKey?: UserKey): Promise { // Retrieve Public Key const orgKeys = await this.organizationApiService.getKeys(orgId); if (orgKeys == null) { throw new Error(this.i18nService.t("resetPasswordOrgKeysError")); } const publicKey = Utils.fromB64ToArray(orgKeys.publicKey); // RSA Encrypt user key with organization's public key userKey ??= await this.cryptoService.getUserKey(); if (userKey == null) { throw new Error("No user key found"); } const encryptedKey = await this.cryptoService.rsaEncrypt(userKey.key, publicKey); return encryptedKey.encryptedString; } /** * Sets a user's master password through account recovery. * Intended for organization admins * @param newMasterPassword user's new master password * @param email user's email * @param orgUserId organization user's id * @param orgId organization id */ async resetMasterPassword( newMasterPassword: string, email: string, orgUserId: string, orgId: string, ): Promise { const response = await this.organizationUserService.getOrganizationUserResetPasswordDetails( orgId, orgUserId, ); if (response == null) { throw new Error(this.i18nService.t("resetPasswordDetailsError")); } // Decrypt Organization's encrypted Private Key with org key const orgSymKey = await this.cryptoService.getOrgKey(orgId); if (orgSymKey == null) { throw new Error("No org key found"); } const decPrivateKey = await this.encryptService.decryptToBytes( new EncString(response.encryptedPrivateKey), orgSymKey, ); // Decrypt User's Reset Password Key to get UserKey const decValue = await this.cryptoService.rsaDecrypt(response.resetPasswordKey, decPrivateKey); const existingUserKey = new SymmetricCryptoKey(decValue) 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.cryptoService.makeMasterKey( newMasterPassword, email.trim().toLowerCase(), kdfConfig, ); const newMasterKeyHash = await this.cryptoService.hashMasterKey( newMasterPassword, newMasterKey, ); // Create new encrypted user key for the User const newUserKey = await this.cryptoService.encryptUserKeyWithMasterKey( newMasterKey, existingUserKey, ); // Create request const request = new OrganizationUserResetPasswordRequest(); request.key = newUserKey[1].encryptedString; request.newMasterPasswordHash = newMasterKeyHash; // Change user's password await this.organizationUserService.putOrganizationUserResetPassword(orgId, orgUserId, request); } /** * Returns existing account recovery keys re-encrypted with the new user key. * @param newUserKey the new user key * @throws Error if new user key is null */ async getRotatedKeys( newUserKey: UserKey, ): Promise { if (newUserKey == null) { throw new Error("New user key is required for rotation."); } const allOrgs = await this.organizationService.getAll(); if (!allOrgs) { return; } const requests: OrganizationUserResetPasswordWithIdRequest[] = []; for (const org of allOrgs) { // If not already enrolled, skip if (!org.resetPasswordEnrolled) { continue; } // Re-enroll - encrypt user key with organization public key const encryptedKey = await this.buildRecoveryKey(org.id, newUserKey); // Create/Execute request const request = new OrganizationUserResetPasswordWithIdRequest(); request.organizationId = org.id; request.resetPasswordKey = encryptedKey; request.masterPasswordHash = "ignored"; requests.push(request); } return requests; } /** * @deprecated Nov 6, 2023: Use new Key Rotation Service for posting rotated data. */ async postLegacyRotation( userId: string, requests: OrganizationUserResetPasswordWithIdRequest[], ): Promise { if (requests == null) { return; } for (const request of requests) { await this.organizationUserService.putOrganizationUserResetPasswordEnrollment( request.organizationId, userId, request, ); } } }