diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts index dac5afa191a..4b4a22d54aa 100644 --- a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts +++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts @@ -11,7 +11,6 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SendWithIdRequest } from "@bitwarden/common/tools/send/models/request/send-with-id.request"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; @@ -30,6 +29,7 @@ import { EmergencyAccessTrustComponent, KeyRotationTrustInfoComponent, } from "@bitwarden/key-management-ui"; +import { PureCrypto } from "@bitwarden/sdk-internal"; import { OrganizationUserResetPasswordService } from "../../admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service"; import { WebauthnLoginAdminService } from "../../auth"; @@ -96,6 +96,13 @@ describe("KeyRotationService", () => { const mockTrustedPublicKeys = [Utils.fromUtf8ToArray("test-public-key")]; beforeAll(() => { + jest.spyOn(PureCrypto, "generate_user_key_aes256_cbc_hmac").mockReturnValue(new Uint8Array(64)); + jest + .spyOn(PureCrypto, "generate_user_key_xchacha20_poly1305") + .mockReturnValue(new Uint8Array(70)); + jest + .spyOn(PureCrypto, "encrypt_user_key_with_master_password") + .mockReturnValue("mockNewUserKey"); mockUserVerificationService = mock(); mockApiService = mock(); mockCipherService = mock(); @@ -158,6 +165,7 @@ describe("KeyRotationService", () => { mockToastService, mockI18nService, mockDialogService, + mockConfigService, ); }); @@ -181,7 +189,7 @@ describe("KeyRotationService", () => { } as any, ]); mockKeyService.hashMasterKey.mockResolvedValue("mockMasterPasswordHash"); - mockConfigService.getFeatureFlag.mockResolvedValue(true); + mockConfigService.getFeatureFlag.mockResolvedValue(false); mockEncryptService.wrapSymmetricKey.mockResolvedValue({ encryptedString: "mockEncryptedData", @@ -286,6 +294,59 @@ describe("KeyRotationService", () => { expect(arg.accountUnlockData.emergencyAccessUnlockData.length).toBe(1); expect(arg.accountUnlockData.organizationAccountRecoveryUnlockData.length).toBe(1); expect(arg.accountUnlockData.passkeyUnlockData.length).toBe(2); + expect(PureCrypto.generate_user_key_aes256_cbc_hmac).toHaveBeenCalled(); + expect(PureCrypto.encrypt_user_key_with_master_password).toHaveBeenCalledWith( + new Uint8Array(64), + "newMasterPassword", + mockUser.email, + DEFAULT_KDF_CONFIG.toSdkConfig(), + ); + expect(PureCrypto.generate_user_key_xchacha20_poly1305).not.toHaveBeenCalled(); + }); + + it("rotates the userkey to xchacha20poly1305 and encrypted data and changes master password when featureflag is active", async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(true); + + KeyRotationTrustInfoComponent.open = initialPromptedOpenTrue; + EmergencyAccessTrustComponent.open = emergencyAccessTrustOpenTrusted; + AccountRecoveryTrustComponent.open = accountRecoveryTrustOpenTrusted; + await keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData( + "mockMasterPassword", + "newMasterPassword", + mockUser, + ); + + expect(mockApiService.postUserKeyUpdateV2).toHaveBeenCalled(); + const arg = mockApiService.postUserKeyUpdateV2.mock.calls[0][0]; + expect(arg.accountUnlockData.masterPasswordUnlockData.masterKeyEncryptedUserKey).toBe( + "mockNewUserKey", + ); + expect(arg.oldMasterKeyAuthenticationHash).toBe("mockMasterPasswordHash"); + expect(arg.accountUnlockData.masterPasswordUnlockData.email).toBe("mockEmail"); + expect(arg.accountUnlockData.masterPasswordUnlockData.kdfType).toBe( + DEFAULT_KDF_CONFIG.kdfType, + ); + expect(arg.accountUnlockData.masterPasswordUnlockData.kdfIterations).toBe( + DEFAULT_KDF_CONFIG.iterations, + ); + + expect(arg.accountKeys.accountPublicKey).toBe(Utils.fromUtf8ToB64("mockPublicKey")); + expect(arg.accountKeys.userKeyEncryptedAccountPrivateKey).toBe("mockEncryptedData"); + + expect(arg.accountData.ciphers.length).toBe(2); + expect(arg.accountData.folders.length).toBe(2); + expect(arg.accountData.sends.length).toBe(2); + expect(arg.accountUnlockData.emergencyAccessUnlockData.length).toBe(1); + expect(arg.accountUnlockData.organizationAccountRecoveryUnlockData.length).toBe(1); + expect(arg.accountUnlockData.passkeyUnlockData.length).toBe(2); + expect(PureCrypto.generate_user_key_aes256_cbc_hmac).toHaveBeenCalled(); + expect(PureCrypto.encrypt_user_key_with_master_password).toHaveBeenCalledWith( + new Uint8Array(70), + "newMasterPassword", + mockUser.email, + DEFAULT_KDF_CONFIG.toSdkConfig(), + ); + expect(PureCrypto.generate_user_key_xchacha20_poly1305).toHaveBeenCalled(); }); it("returns early when first trust warning dialog is declined", async () => { @@ -344,21 +405,6 @@ describe("KeyRotationService", () => { ).rejects.toThrow(); }); - it("throws if user key creation fails", async () => { - mockKeyService.makeUserKey.mockResolvedValueOnce([ - null as unknown as UserKey, - null as unknown as EncString, - ]); - - await expect( - keyRotationService.rotateUserKeyMasterPasswordAndEncryptedData( - "mockMasterPassword", - "mockMasterPassword1", - mockUser, - ), - ).rejects.toThrow(); - }); - it("legacy throws if no private key is found", async () => { privateKey.next(null); diff --git a/package-lock.json b/package-lock.json index 4e08a6df9a8..0d800599075 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@angular/platform-browser": "18.2.13", "@angular/platform-browser-dynamic": "18.2.13", "@angular/router": "18.2.13", - "@bitwarden/sdk-internal": "0.2.0-main.137", + "@bitwarden/sdk-internal": "0.2.0-main.163", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "3.0.2", @@ -4700,9 +4700,9 @@ "link": true }, "node_modules/@bitwarden/sdk-internal": { - "version": "0.2.0-main.137", - "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.137.tgz", - "integrity": "sha512-Df0pB5tOEc4WiMjskunTrqHulPzenFv8C61sqsBhHfy80xcf5kU5JyPd4asbf3e4uNS6QGXptd8imp09AuiFEA==", + "version": "0.2.0-main.163", + "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.163.tgz", + "integrity": "sha512-k8Z4wUhgQi4+vc/Zy3GgRZqxqrCiEzC0M4dRkoieMSU65e/8b0HBy0kXYcrcip2HKIxFZd6hXsv/auIf7fgEFg==", "license": "GPL-3.0" }, "node_modules/@bitwarden/send-ui": { diff --git a/package.json b/package.json index 861559b2b66..4dee2dcb157 100644 --- a/package.json +++ b/package.json @@ -156,7 +156,7 @@ "@angular/platform-browser": "18.2.13", "@angular/platform-browser-dynamic": "18.2.13", "@angular/router": "18.2.13", - "@bitwarden/sdk-internal": "0.2.0-main.137", + "@bitwarden/sdk-internal": "0.2.0-main.163", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "3.0.2",