1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-19 17:53:39 +00:00

[PM-28813] Implement encryption diagnostics & recovery tool (#17673)

* Implement data recovery tool

* Fix tests

* Move Sdkloadservice call and use bit action
This commit is contained in:
Bernd Schoolmann
2025-12-10 04:03:31 +01:00
committed by GitHub
parent 42c09b325c
commit 3af19ad934
17 changed files with 1180 additions and 2 deletions

View File

@@ -43,6 +43,7 @@ export enum FeatureFlag {
LinuxBiometricsV2 = "pm-26340-linux-biometrics-v2",
UnlockWithMasterPasswordUnlockData = "pm-23246-unlock-with-master-password-unlock-data",
NoLogoutOnKdfChange = "pm-23995-no-logout-on-kdf-change",
DataRecoveryTool = "pm-28813-data-recovery-tool",
ConsolidatedSessionTimeoutComponent = "pm-26056-consolidated-session-timeout-component",
/* Tools */
@@ -149,6 +150,7 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.LinuxBiometricsV2]: FALSE,
[FeatureFlag.UnlockWithMasterPasswordUnlockData]: FALSE,
[FeatureFlag.NoLogoutOnKdfChange]: FALSE,
[FeatureFlag.DataRecoveryTool]: FALSE,
[FeatureFlag.ConsolidatedSessionTimeoutComponent]: FALSE,
/* Platform */

View File

@@ -7,4 +7,11 @@ export abstract class UserAsymmetricKeysRegenerationService {
* @param userId The user id.
*/
abstract regenerateIfNeeded(userId: UserId): Promise<void>;
/**
* Performs the regeneration of the user's public/private key pair without checking any preconditions.
* This should only be used for V1 encryption accounts
* @param userId The user id.
*/
abstract regenerateUserPublicKeyEncryptionKeyPair(userId: UserId): Promise<void>;
}

View File

@@ -370,3 +370,52 @@ describe("regenerateIfNeeded", () => {
);
});
});
describe("regenerateUserPublicKeyEncryptionKeyPair", () => {
let sut: DefaultUserAsymmetricKeysRegenerationService;
const userId = "userId" as UserId;
let keyService: MockProxy<KeyService>;
let cipherService: MockProxy<CipherService>;
let userAsymmetricKeysRegenerationApiService: MockProxy<UserAsymmetricKeysRegenerationApiService>;
let logService: MockProxy<LogService>;
let sdkService: MockSdkService;
let apiService: MockProxy<ApiService>;
let configService: MockProxy<ConfigService>;
beforeEach(() => {
keyService = mock<KeyService>();
cipherService = mock<CipherService>();
userAsymmetricKeysRegenerationApiService = mock<UserAsymmetricKeysRegenerationApiService>();
logService = mock<LogService>();
sdkService = new MockSdkService();
apiService = mock<ApiService>();
configService = mock<ConfigService>();
sut = new DefaultUserAsymmetricKeysRegenerationService(
keyService,
cipherService,
userAsymmetricKeysRegenerationApiService,
logService,
sdkService,
apiService,
configService,
);
});
afterEach(() => {
jest.resetAllMocks();
});
it("should throw error when user key is not V1 encryption type", async () => {
const mockUserKey = {
keyB64: "mockKeyB64",
inner: () => ({ type: 7 }),
} as unknown as UserKey;
keyService.userKey$.mockReturnValue(of(mockUserKey));
await expect(sut.regenerateUserPublicKeyEncryptionKeyPair(userId)).rejects.toThrow(
"User key is not V1 encryption type",
);
});
});

View File

@@ -37,7 +37,7 @@ export class DefaultUserAsymmetricKeysRegenerationService
if (privateKeyRegenerationFlag) {
const shouldRegenerate = await this.shouldRegenerate(userId);
if (shouldRegenerate) {
await this.regenerateUserAsymmetricKeys(userId);
await this.regenerateUserPublicKeyEncryptionKeyPair(userId);
}
}
} catch (error) {
@@ -125,11 +125,14 @@ export class DefaultUserAsymmetricKeysRegenerationService
return false;
}
private async regenerateUserAsymmetricKeys(userId: UserId): Promise<void> {
async regenerateUserPublicKeyEncryptionKeyPair(userId: UserId): Promise<void> {
const userKey = await firstValueFrom(this.keyService.userKey$(userId));
if (userKey == null) {
throw new Error("User key not found");
}
if (userKey.inner().type !== EncryptionType.AesCbc256_HmacSha256_B64) {
throw new Error("User key is not V1 encryption type");
}
const makeKeyPairResponse = await firstValueFrom(
this.sdkService.client$.pipe(
map((sdk) => {