1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-19 09:43:23 +00:00

[PM-3797] Client changes to use new key rotation process (#6881)

## Type of change

<!-- (mark with an `X`) -->

```
- [ ] Bug fix
- [ ] New feature development
- [x] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
- [ ] Build/deploy pipeline (DevOps)
- [ ] Other
```

## Objective

<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
Final Client changes for Key Rotation Improvements. 

- Introduces a new `KeyRotationService` that is responsible for owning rotation process.
- Moves `Send` re-encryption to the `SendService` (`KeyRotationService` shouldn't have knowledge about how domains are encrypted).
- Moves `EmergencyAccess` re-encryption to the `EmergencyAccessService`.
- Renames `AccountRecoveryService` to `OrganizationUserResetPasswordService` after feedback from Admin Console


## Code changes

<!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes-->
<!--Also refer to any related changes or PRs in other repositories-->

Auth
- **emergency-access-update.request.ts:** New request model for domain updates that includes Id
- **emergency-access.service.ts:** Moved `EmergencyAccess` re-encryption to the `EmergencyAccessService`. Add deprecated method for legacy key rotations if feature flag is off
- **key-rotation.service/api/spec/module:** New key rotation service for owning the rotation process. Added api service, module, and spec file.
- **update-key.request.ts:** Moved to Auth ownership. Also added new properties for including other domains.
- **migrate-legacy-encryption.component.ts:** Use new key rotation service instead of old component specific service. Delete old service.
- **change-password.component.ts:** Use new key rotation service.
- **settings.module.ts:** Import key rotation module.

Admin Console
- **organization-user-reset-password.service.ts/spec:** Responsible for re-encryption of reset password keys during key rotation. Added tests.
- **organization-user-reset-password-enrollment.request.ts:** New request model for key rotations
- **reset-password.component.ts:** Update `AccountRecoveryService` to `OrganizationUserResetPasswordService`
- **enroll-master-password-reset.component.ts:** Update `AccountRecoveryService` to `OrganizationUserResetPasswordService`

Tools
- **send.service/spec.ts:** Responsible only for re-encryption of sends during key rotation. Added tests.

Other
- **api.service.ts:** Move `postAccountKey` to `KeyRotationApiService`
- **feature-flag.enum.ts:** add new feature flag

## Screenshots

<!--Required for any UI changes. Delete if not applicable-->

## Before you submit

- Please add **unit tests** where it makes sense to do so (encouraged but not required)
- If this change requires a **documentation update** - notify the documentation team
- If this change has particular **deployment requirements** - notify the DevOps team
- Ensure that all UI additions follow [WCAG AA requirements](https://contributing.bitwarden.com/contributing/accessibility/)
This commit is contained in:
Jake Fink
2023-12-22 10:31:24 -05:00
committed by GitHub
parent e079fb4ab6
commit a62f8cd652
25 changed files with 569 additions and 608 deletions

View File

@@ -1,40 +1,28 @@
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { firstValueFrom, Observable } from "rxjs";
import { Observable } from "rxjs";
import { ChangePasswordComponent as BaseChangePasswordComponent } from "@bitwarden/angular/auth/components/change-password.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
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 { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.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 { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { UpdateKeyRequest } from "@bitwarden/common/models/request/update-key.request";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.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 { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { MasterKey, UserKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { SendWithIdRequest } from "@bitwarden/common/tools/send/models/request/send-with-id.request";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { CipherWithIdRequest } from "@bitwarden/common/vault/models/request/cipher-with-id.request";
import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request";
import { DialogService } from "@bitwarden/components";
import { EmergencyAccessService } from "../emergency-access";
import { UserKeyRotationService } from "../key-rotation/user-key-rotation.service";
@Component({
selector: "app-change-password",
@@ -58,20 +46,14 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
platformUtilsService: PlatformUtilsService,
policyService: PolicyService,
private auditService: AuditService,
private folderService: FolderService,
private cipherService: CipherService,
private syncService: SyncService,
private emergencyAccessService: EmergencyAccessService,
private apiService: ApiService,
private sendService: SendService,
private organizationService: OrganizationService,
private router: Router,
private organizationApiService: OrganizationApiServiceAbstraction,
private organizationUserService: OrganizationUserService,
dialogService: DialogService,
private userVerificationService: UserVerificationService,
private deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
private configService: ConfigServiceAbstraction,
private keyRotationService: UserKeyRotationService,
) {
super(
i18nService,
@@ -201,7 +183,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
try {
if (this.rotateUserKey) {
this.formPromise = this.apiService.postPassword(request).then(() => {
return this.updateKey(newMasterKey, request.newMasterPasswordHash);
return this.updateKey();
});
} else {
this.formPromise = this.apiService.postPassword(request);
@@ -220,81 +202,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
}
}
private async updateKey(masterKey: MasterKey, masterPasswordHash: string) {
const [newUserKey, masterKeyEncUserKey] = await this.cryptoService.makeUserKey(masterKey);
const userPrivateKey = await this.cryptoService.getPrivateKey();
let encPrivateKey: EncString = null;
if (userPrivateKey != null) {
encPrivateKey = await this.cryptoService.encrypt(userPrivateKey, newUserKey);
}
const request = new UpdateKeyRequest();
request.privateKey = encPrivateKey != null ? encPrivateKey.encryptedString : null;
request.key = masterKeyEncUserKey.encryptedString;
request.masterPasswordHash = masterPasswordHash;
const folders = await firstValueFrom(this.folderService.folderViews$);
for (let i = 0; i < folders.length; i++) {
if (folders[i].id == null) {
continue;
}
const folder = await this.folderService.encrypt(folders[i], newUserKey);
request.folders.push(new FolderWithIdRequest(folder));
}
const ciphers = await this.cipherService.getAllDecrypted();
for (let i = 0; i < ciphers.length; i++) {
if (ciphers[i].organizationId != null) {
continue;
}
const cipher = await this.cipherService.encrypt(ciphers[i], newUserKey);
request.ciphers.push(new CipherWithIdRequest(cipher));
}
const sends = await firstValueFrom(this.sendService.sends$);
await Promise.all(
sends.map(async (send) => {
const sendKey = await this.cryptoService.decryptToBytes(send.key, null);
send.key = (await this.cryptoService.encrypt(sendKey, newUserKey)) ?? send.key;
request.sends.push(new SendWithIdRequest(send));
}),
);
await this.deviceTrustCryptoService.rotateDevicesTrust(newUserKey, masterPasswordHash);
await this.apiService.postAccountKey(request);
await this.emergencyAccessService.rotate(newUserKey);
await this.updateAllResetPasswordKeys(newUserKey, masterPasswordHash);
}
private async updateAllResetPasswordKeys(userKey: UserKey, masterPasswordHash: string) {
const orgs = await this.organizationService.getAll();
for (const org of orgs) {
// If not already enrolled, skip
if (!org.resetPasswordEnrolled) {
continue;
}
// Retrieve public key
const response = await this.organizationApiService.getKeys(org.id);
const publicKey = Utils.fromB64ToArray(response?.publicKey);
// Re-enroll - encrypt user's encKey.key with organization public key
const encryptedKey = await this.cryptoService.rsaEncrypt(userKey.key, publicKey);
// Create/Execute request
const request = new OrganizationUserResetPasswordEnrollmentRequest();
request.masterPasswordHash = masterPasswordHash;
request.resetPasswordKey = encryptedKey.encryptedString;
await this.organizationUserService.putOrganizationUserResetPasswordEnrollment(
org.id,
org.userId,
request,
);
}
private async updateKey() {
await this.keyRotationService.rotateUserKeyAndEncryptedData(this.masterPassword);
}
}