mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 00:03:56 +00:00
[PM-17900] Add cose / xchacha20poly1305 migration on userkey rotation (#14539)
* Add new encrypt service functions * Undo changes * Cleanup * Fix build * Fix comments * Switch encrypt service to use SDK functions * Add cose migration on userkey rotation * Update sdk * Set featureflag to default disabled * Add tests * Update sdk to build 168 * Make changes according to feedback
This commit is contained in:
@@ -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,11 @@ describe("KeyRotationService", () => {
|
||||
const mockTrustedPublicKeys = [Utils.fromUtf8ToArray("test-public-key")];
|
||||
|
||||
beforeAll(() => {
|
||||
jest.spyOn(PureCrypto, "make_user_key_aes256_cbc_hmac").mockReturnValue(new Uint8Array(64));
|
||||
jest.spyOn(PureCrypto, "make_user_key_xchacha20_poly1305").mockReturnValue(new Uint8Array(70));
|
||||
jest
|
||||
.spyOn(PureCrypto, "encrypt_user_key_with_master_password")
|
||||
.mockReturnValue("mockNewUserKey");
|
||||
mockUserVerificationService = mock<UserVerificationService>();
|
||||
mockApiService = mock<UserKeyRotationApiService>();
|
||||
mockCipherService = mock<CipherService>();
|
||||
@@ -158,6 +163,7 @@ describe("KeyRotationService", () => {
|
||||
mockToastService,
|
||||
mockI18nService,
|
||||
mockDialogService,
|
||||
mockConfigService,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -181,7 +187,7 @@ describe("KeyRotationService", () => {
|
||||
} as any,
|
||||
]);
|
||||
mockKeyService.hashMasterKey.mockResolvedValue("mockMasterPasswordHash");
|
||||
mockConfigService.getFeatureFlag.mockResolvedValue(true);
|
||||
mockConfigService.getFeatureFlag.mockResolvedValue(false);
|
||||
|
||||
mockEncryptService.wrapSymmetricKey.mockResolvedValue({
|
||||
encryptedString: "mockEncryptedData",
|
||||
@@ -286,6 +292,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.make_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.make_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.make_user_key_aes256_cbc_hmac).not.toHaveBeenCalled();
|
||||
expect(PureCrypto.encrypt_user_key_with_master_password).toHaveBeenCalledWith(
|
||||
new Uint8Array(70),
|
||||
"newMasterPassword",
|
||||
mockUser.email,
|
||||
DEFAULT_KDF_CONFIG.toSdkConfig(),
|
||||
);
|
||||
expect(PureCrypto.make_user_key_xchacha20_poly1305).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns early when first trust warning dialog is declined", async () => {
|
||||
@@ -344,21 +403,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);
|
||||
|
||||
|
||||
@@ -5,14 +5,17 @@ import { Account } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
||||
import { MasterPasswordVerification } from "@bitwarden/common/auth/types/verification";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout";
|
||||
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";
|
||||
import { HashPurpose } from "@bitwarden/common/platform/enums";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { UserKey } from "@bitwarden/common/types/key";
|
||||
@@ -26,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/core";
|
||||
@@ -59,6 +63,7 @@ export class UserKeyRotationService {
|
||||
private toastService: ToastService,
|
||||
private i18nService: I18nService,
|
||||
private dialogService: DialogService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -116,8 +121,22 @@ export class UserKeyRotationService {
|
||||
|
||||
const newMasterKey = await this.keyService.makeMasterKey(newMasterPassword, email, kdfConfig);
|
||||
|
||||
const [newUnencryptedUserKey, newMasterKeyEncryptedUserKey] =
|
||||
await this.keyService.makeUserKey(newMasterKey);
|
||||
let userKeyBytes: Uint8Array;
|
||||
if (await this.configService.getFeatureFlag(FeatureFlag.EnrollAeadOnKeyRotation)) {
|
||||
userKeyBytes = PureCrypto.make_user_key_xchacha20_poly1305();
|
||||
} else {
|
||||
userKeyBytes = PureCrypto.make_user_key_aes256_cbc_hmac();
|
||||
}
|
||||
|
||||
const newMasterKeyEncryptedUserKey = new EncString(
|
||||
PureCrypto.encrypt_user_key_with_master_password(
|
||||
userKeyBytes,
|
||||
newMasterPassword,
|
||||
email,
|
||||
kdfConfig.toSdkConfig(),
|
||||
),
|
||||
);
|
||||
const newUnencryptedUserKey = new SymmetricCryptoKey(userKeyBytes) as UserKey;
|
||||
|
||||
if (!newUnencryptedUserKey || !newMasterKeyEncryptedUserKey) {
|
||||
this.logService.info("[Userkey rotation] User key could not be created. Aborting!");
|
||||
|
||||
Reference in New Issue
Block a user