mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 08:13:42 +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:
@@ -1,9 +1,10 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { EncArrayBuffer } from "../../../platform/models/domain/enc-array-buffer";
|
||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||
import { SymmetricCryptoKey, UserKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||
import { SendData } from "../models/data/send.data";
|
||||
import { Send } from "../models/domain/send";
|
||||
import { SendWithIdRequest } from "../models/request/send-with-id.request";
|
||||
import { SendView } from "../models/view/send.view";
|
||||
|
||||
export abstract class SendService {
|
||||
@@ -17,6 +18,13 @@ export abstract class SendService {
|
||||
key?: SymmetricCryptoKey,
|
||||
) => Promise<[Send, EncArrayBuffer]>;
|
||||
get: (id: string) => Send;
|
||||
/**
|
||||
* Provides re-encrypted user sends for the key rotation process
|
||||
* @param newUserKey The new user key to use for re-encryption
|
||||
* @throws Error if the new user key is null or undefined
|
||||
* @returns A list of user sends that have been re-encrypted with the new user key
|
||||
*/
|
||||
getRotatedKeys: (newUserKey: UserKey) => Promise<SendWithIdRequest[]>;
|
||||
/**
|
||||
* @deprecated Do not call this, use the sends$ observable collection
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,7 @@ import { EncryptService } from "../../../platform/abstractions/encrypt.service";
|
||||
import { I18nService } from "../../../platform/abstractions/i18n.service";
|
||||
import { StateService } from "../../../platform/abstractions/state.service";
|
||||
import { EncString } from "../../../platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey, UserKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||
import { ContainerService } from "../../../platform/services/container.service";
|
||||
import { SendData } from "../models/data/send.data";
|
||||
import { Send } from "../models/domain/send";
|
||||
@@ -92,6 +93,37 @@ describe("SendService", () => {
|
||||
expect(stateService.getDecryptedSends).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
describe("getRotatedKeys", () => {
|
||||
let encryptedKey: EncString;
|
||||
beforeEach(() => {
|
||||
cryptoService.decryptToBytes.mockResolvedValue(new Uint8Array(32));
|
||||
encryptedKey = new EncString("Re-encrypted Send Key");
|
||||
cryptoService.encrypt.mockResolvedValue(encryptedKey);
|
||||
});
|
||||
|
||||
it("returns re-encrypted user sends", async () => {
|
||||
const newUserKey = new SymmetricCryptoKey(new Uint8Array(32)) as UserKey;
|
||||
const result = await sendService.getRotatedKeys(newUserKey);
|
||||
|
||||
expect(result).toMatchObject([{ id: "1", key: "Re-encrypted Send Key" }]);
|
||||
});
|
||||
|
||||
it("returns null if there are no sends", async () => {
|
||||
sendService.replace(null);
|
||||
|
||||
const newUserKey = new SymmetricCryptoKey(new Uint8Array(32)) as UserKey;
|
||||
const result = await sendService.getRotatedKeys(newUserKey);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("throws if the new user key is null", async () => {
|
||||
await expect(sendService.getRotatedKeys(null)).rejects.toThrowError(
|
||||
"New user key is required for rotation.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// InternalSendService
|
||||
|
||||
it("upsert", async () => {
|
||||
|
||||
@@ -7,12 +7,13 @@ import { StateService } from "../../../platform/abstractions/state.service";
|
||||
import { Utils } from "../../../platform/misc/utils";
|
||||
import { EncArrayBuffer } from "../../../platform/models/domain/enc-array-buffer";
|
||||
import { EncString } from "../../../platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||
import { SymmetricCryptoKey, UserKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||
import { SendType } from "../enums/send-type";
|
||||
import { SendData } from "../models/data/send.data";
|
||||
import { Send } from "../models/domain/send";
|
||||
import { SendFile } from "../models/domain/send-file";
|
||||
import { SendText } from "../models/domain/send-text";
|
||||
import { SendWithIdRequest } from "../models/request/send-with-id.request";
|
||||
import { SendView } from "../models/view/send.view";
|
||||
import { SEND_KDF_ITERATIONS } from "../send-kdf";
|
||||
|
||||
@@ -212,6 +213,22 @@ export class SendService implements InternalSendServiceAbstraction {
|
||||
await this.stateService.setEncryptedSends(sends);
|
||||
}
|
||||
|
||||
async getRotatedKeys(newUserKey: UserKey): Promise<SendWithIdRequest[]> {
|
||||
if (newUserKey == null) {
|
||||
throw new Error("New user key is required for rotation.");
|
||||
}
|
||||
|
||||
const requests = await Promise.all(
|
||||
this._sends.value.map(async (send) => {
|
||||
const sendKey = await this.cryptoService.decryptToBytes(send.key);
|
||||
send.key = await this.cryptoService.encrypt(sendKey, newUserKey);
|
||||
return new SendWithIdRequest(send);
|
||||
}),
|
||||
);
|
||||
// separate return for easier debugging
|
||||
return requests;
|
||||
}
|
||||
|
||||
private parseFile(send: Send, file: File, key: SymmetricCryptoKey): Promise<EncArrayBuffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
Reference in New Issue
Block a user