1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 00:03:56 +00:00

[PM-5963] Fix tde offboarding vault corruption (#9480)

* Fix tde offboarding

* Add tde offboarding password request

* Add event for tde offboarding

* Update libs/auth/src/common/models/domain/user-decryption-options.ts

Co-authored-by: Jake Fink <jfink@bitwarden.com>

* Update libs/common/src/services/api.service.ts

Co-authored-by: Jake Fink <jfink@bitwarden.com>

* Make tde offboarding take priority

* Update tde offboarding message

* Fix unit tests

* Fix unit tests

* Fix typo

* Fix unit tests

---------

Co-authored-by: Jake Fink <jfink@bitwarden.com>
This commit is contained in:
Bernd Schoolmann
2024-08-02 01:48:09 +02:00
committed by GitHub
parent d26ea1be5f
commit c6229abd12
19 changed files with 94 additions and 17 deletions

View File

@@ -154,12 +154,12 @@ describe("SsoComponent", () => {
}),
withMasterPasswordAndTrustedDevice: new UserDecryptionOptions({
hasMasterPassword: true,
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false, false),
keyConnectorOption: undefined,
}),
withMasterPasswordAndTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
hasMasterPassword: true,
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true, false),
keyConnectorOption: undefined,
}),
withMasterPasswordAndKeyConnector: new UserDecryptionOptions({
@@ -169,12 +169,12 @@ describe("SsoComponent", () => {
}),
noMasterPasswordWithTrustedDevice: new UserDecryptionOptions({
hasMasterPassword: false,
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false, false),
keyConnectorOption: undefined,
}),
noMasterPasswordWithTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
hasMasterPassword: false,
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true, false),
keyConnectorOption: undefined,
}),
noMasterPasswordWithKeyConnector: new UserDecryptionOptions({

View File

@@ -287,8 +287,18 @@ export class SsoComponent {
orgIdentifier: string,
userDecryptionOpts: UserDecryptionOptions,
): Promise<void> {
// If user doesn't have a MP, but has reset password permission, they must set a MP
// Tde offboarding takes precedence
if (
!userDecryptionOpts.hasMasterPassword &&
userDecryptionOpts.trustedDeviceOption.isTdeOffboarding
) {
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
await this.masterPasswordService.setForceSetPasswordReason(
ForceSetPasswordReason.TdeOffboarding,
userId,
);
} else if (
// If user doesn't have a MP, but has reset password permission, they must set a MP
!userDecryptionOpts.hasMasterPassword &&
userDecryptionOpts.trustedDeviceOption.hasManageResetPasswordPermission
) {

View File

@@ -127,12 +127,12 @@ describe("TwoFactorComponent", () => {
}),
withMasterPasswordAndTrustedDevice: new UserDecryptionOptions({
hasMasterPassword: true,
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false, false),
keyConnectorOption: undefined,
}),
withMasterPasswordAndTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
hasMasterPassword: true,
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true, false),
keyConnectorOption: undefined,
}),
withMasterPasswordAndKeyConnector: new UserDecryptionOptions({
@@ -142,12 +142,12 @@ describe("TwoFactorComponent", () => {
}),
noMasterPasswordWithTrustedDevice: new UserDecryptionOptions({
hasMasterPassword: false,
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false, false),
keyConnectorOption: undefined,
}),
noMasterPasswordWithTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
hasMasterPassword: false,
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true, false),
keyConnectorOption: undefined,
}),
noMasterPasswordWithKeyConnector: new UserDecryptionOptions({

View File

@@ -120,12 +120,12 @@ describe("TwoFactorComponent", () => {
}),
withMasterPasswordAndTrustedDevice: new UserDecryptionOptions({
hasMasterPassword: true,
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false, false),
keyConnectorOption: undefined,
}),
withMasterPasswordAndTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
hasMasterPassword: true,
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true, false),
keyConnectorOption: undefined,
}),
withMasterPasswordAndKeyConnector: new UserDecryptionOptions({
@@ -135,12 +135,12 @@ describe("TwoFactorComponent", () => {
}),
noMasterPasswordWithTrustedDevice: new UserDecryptionOptions({
hasMasterPassword: false,
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false),
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false, false),
keyConnectorOption: undefined,
}),
noMasterPasswordWithTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
hasMasterPassword: false,
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true),
trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true, false),
keyConnectorOption: undefined,
}),
noMasterPasswordWithKeyConnector: new UserDecryptionOptions({

View File

@@ -12,6 +12,7 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
import { UpdateTdeOffboardingPasswordRequest } from "@bitwarden/common/auth/models/request/update-tde-offboarding-password.request";
import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request";
import { MasterPasswordVerification } from "@bitwarden/common/auth/types/verification";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@@ -97,9 +98,13 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
}
get masterPasswordWarningText(): string {
return this.reason == ForceSetPasswordReason.WeakMasterPassword
? this.i18nService.t("updateWeakMasterPasswordWarning")
: this.i18nService.t("updateMasterPasswordWarning");
if (this.reason == ForceSetPasswordReason.WeakMasterPassword) {
return this.i18nService.t("weakMasterPasswordWarning");
} else if (this.reason == ForceSetPasswordReason.TdeOffboarding) {
return this.i18nService.t("tdeDisabledMasterPasswordRequired");
} else {
return this.i18nService.t("masterPasswordWarning");
}
}
togglePassword(confirmField: boolean) {
@@ -165,6 +170,9 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
case ForceSetPasswordReason.WeakMasterPassword:
this.formPromise = this.updatePassword(masterPasswordHash, userKey);
break;
case ForceSetPasswordReason.TdeOffboarding:
this.formPromise = this.updateTdeOffboardingPassword(masterPasswordHash, userKey);
break;
}
await this.formPromise;
@@ -211,4 +219,16 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
return this.apiService.postPassword(request);
}
private async updateTdeOffboardingPassword(
masterPasswordHash: string,
userKey: [UserKey, EncString],
) {
const request = new UpdateTdeOffboardingPasswordRequest();
request.key = userKey[1].encryptedString;
request.newMasterPasswordHash = masterPasswordHash;
request.masterPasswordHint = this.hint;
return this.apiService.putUpdateTdeOffboardingPassword(request);
}
}

View File

@@ -211,6 +211,7 @@ describe("SsoLoginStrategy", () => {
HasAdminApproval: true,
HasLoginApprovingDevice: true,
HasManageResetPasswordPermission: false,
IsTdeOffboarding: false,
EncryptedPrivateKey: mockEncDevicePrivateKey,
EncryptedUserKey: mockEncUserKey,
},
@@ -343,6 +344,7 @@ describe("SsoLoginStrategy", () => {
HasAdminApproval: true,
HasLoginApprovingDevice: false,
HasManageResetPasswordPermission: false,
IsTdeOffboarding: false,
EncryptedPrivateKey: mockEncDevicePrivateKey,
EncryptedUserKey: mockEncUserKey,
},

View File

@@ -54,6 +54,8 @@ export class TrustedDeviceUserDecryptionOption {
hasLoginApprovingDevice: boolean;
/** True if the user has manage reset password permission, as these users must be forced to have a master password. */
hasManageResetPasswordPermission: boolean;
/** True if tde is disabled but user has not set a master password yet. */
isTdeOffboarding: boolean;
/**
* Initializes a new instance of the TrustedDeviceUserDecryptionOption from a response object.
@@ -70,6 +72,7 @@ export class TrustedDeviceUserDecryptionOption {
options.hasAdminApproval = response?.hasAdminApproval ?? false;
options.hasLoginApprovingDevice = response?.hasLoginApprovingDevice ?? false;
options.hasManageResetPasswordPermission = response?.hasManageResetPasswordPermission ?? false;
options.isTdeOffboarding = response?.isTdeOffboarding ?? false;
return options;
}

View File

@@ -29,10 +29,12 @@ export class FakeTrustedDeviceUserDecryptionOption extends TrustedDeviceUserDecr
hasAdminApproval: boolean,
hasLoginApprovingDevice: boolean,
hasManageResetPasswordPermission: boolean,
isTdeOffboarding: boolean,
) {
super();
this.hasAdminApproval = hasAdminApproval;
this.hasLoginApprovingDevice = hasLoginApprovingDevice;
this.hasManageResetPasswordPermission = hasManageResetPasswordPermission;
this.isTdeOffboarding = isTdeOffboarding;
}
}

View File

@@ -8,6 +8,8 @@ import {
} from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { UserDecryptionOptions } from "../../models/domain/user-decryption-options";
import {
USER_DECRYPTION_OPTIONS,
UserDecryptionOptionsService,
@@ -27,12 +29,13 @@ describe("UserDecryptionOptionsService", () => {
sut = new UserDecryptionOptionsService(fakeStateProvider);
});
const userDecryptionOptions = {
const userDecryptionOptions: UserDecryptionOptions = {
hasMasterPassword: true,
trustedDeviceOption: {
hasAdminApproval: false,
hasLoginApprovingDevice: false,
hasManageResetPasswordPermission: true,
isTdeOffboarding: false,
},
keyConnectorOption: {
keyConnectorUrl: "https://keyconnector.bitwarden.com",

View File

@@ -48,6 +48,7 @@ import { TwoFactorEmailRequest } from "../auth/models/request/two-factor-email.r
import { TwoFactorProviderRequest } from "../auth/models/request/two-factor-provider.request";
import { TwoFactorRecoveryRequest } from "../auth/models/request/two-factor-recovery.request";
import { UpdateProfileRequest } from "../auth/models/request/update-profile.request";
import { UpdateTdeOffboardingPasswordRequest } from "../auth/models/request/update-tde-offboarding-password.request";
import { UpdateTempPasswordRequest } from "../auth/models/request/update-temp-password.request";
import { UpdateTwoFactorAuthenticatorRequest } from "../auth/models/request/update-two-factor-authenticator.request";
import { UpdateTwoFactorDuoRequest } from "../auth/models/request/update-two-factor-duo.request";
@@ -181,6 +182,7 @@ export abstract class ApiService {
postUserApiKey: (id: string, request: SecretVerificationRequest) => Promise<ApiKeyResponse>;
postUserRotateApiKey: (id: string, request: SecretVerificationRequest) => Promise<ApiKeyResponse>;
putUpdateTempPassword: (request: UpdateTempPasswordRequest) => Promise<any>;
putUpdateTdeOffboardingPassword: (request: UpdateTdeOffboardingPasswordRequest) => Promise<any>;
postConvertToKeyConnector: () => Promise<void>;
//passwordless
postAuthRequest: (request: CreateAuthRequest) => Promise<AuthRequestResponse>;

View File

@@ -26,4 +26,9 @@ export enum ForceSetPasswordReason {
* Set post login & decryption client side and by server in sync (to catch logged in users).
*/
TdeUserWithoutPasswordHasPasswordResetPermission,
/**
* Occurs when TDE is disabled and master password has to be set.
*/
TdeOffboarding,
}

View File

@@ -0,0 +1,5 @@
import { OrganizationUserResetPasswordRequest } from "../../../admin-console/abstractions/organization-user/requests";
export class UpdateTdeOffboardingPasswordRequest extends OrganizationUserResetPasswordRequest {
masterPasswordHint: string;
}

View File

@@ -5,6 +5,7 @@ export interface ITrustedDeviceUserDecryptionOptionServerResponse {
HasAdminApproval: boolean;
HasLoginApprovingDevice: boolean;
HasManageResetPasswordPermission: boolean;
IsTdeOffboarding: boolean;
EncryptedPrivateKey?: string;
EncryptedUserKey?: string;
}
@@ -13,6 +14,7 @@ export class TrustedDeviceUserDecryptionOptionResponse extends BaseResponse {
hasAdminApproval: boolean;
hasLoginApprovingDevice: boolean;
hasManageResetPasswordPermission: boolean;
isTdeOffboarding: boolean;
encryptedPrivateKey: EncString;
encryptedUserKey: EncString;
@@ -25,6 +27,8 @@ export class TrustedDeviceUserDecryptionOptionResponse extends BaseResponse {
"HasManageResetPasswordPermission",
);
this.isTdeOffboarding = this.getResponseProperty("IsTdeOffboarding");
if (response.EncryptedPrivateKey) {
this.encryptedPrivateKey = new EncString(this.getResponseProperty("EncryptedPrivateKey"));
}

View File

@@ -11,6 +11,7 @@ export enum EventType {
User_UpdatedTempPassword = 1008,
User_MigratedKeyToKeyConnector = 1009,
User_RequestedDeviceApproval = 1010,
User_TdeOffboardingPasswordSet = 1011,
Cipher_Created = 1100,
Cipher_Updated = 1101,

View File

@@ -57,6 +57,7 @@ import { TwoFactorEmailRequest } from "../auth/models/request/two-factor-email.r
import { TwoFactorProviderRequest } from "../auth/models/request/two-factor-provider.request";
import { TwoFactorRecoveryRequest } from "../auth/models/request/two-factor-recovery.request";
import { UpdateProfileRequest } from "../auth/models/request/update-profile.request";
import { UpdateTdeOffboardingPasswordRequest } from "../auth/models/request/update-tde-offboarding-password.request";
import { UpdateTempPasswordRequest } from "../auth/models/request/update-temp-password.request";
import { UpdateTwoFactorAuthenticatorRequest } from "../auth/models/request/update-two-factor-authenticator.request";
import { UpdateTwoFactorDuoRequest } from "../auth/models/request/update-two-factor-duo.request";
@@ -461,6 +462,10 @@ export class ApiService implements ApiServiceAbstraction {
return this.send("PUT", "/accounts/update-temp-password", request, true, false);
}
putUpdateTdeOffboardingPassword(request: UpdateTdeOffboardingPasswordRequest): Promise<void> {
return this.send("PUT", "/accounts/update-tde-offboarding-password", request, true, false);
}
postConvertToKeyConnector(): Promise<void> {
return this.send("POST", "/accounts/convert-to-key-connector", null, true, false);
}