mirror of
https://github.com/bitwarden/browser
synced 2026-02-09 05:00:10 +00:00
Merge branch 'main' of https://github.com/bitwarden/clients into vault/pm-27632/sdk-cipher-ops
This commit is contained in:
@@ -15,9 +15,11 @@ import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/ma
|
||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||
import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request";
|
||||
import { UpdateTdeOffboardingPasswordRequest } from "@bitwarden/common/auth/models/request/update-tde-offboarding-password.request";
|
||||
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { MasterPasswordSalt } from "@bitwarden/common/key-management/master-password/types/master-password.types";
|
||||
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
@@ -44,6 +46,7 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
|
||||
protected organizationApiService: OrganizationApiServiceAbstraction,
|
||||
protected organizationUserApiService: OrganizationUserApiService,
|
||||
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
|
||||
protected accountCryptographicStateService: AccountCryptographicStateService,
|
||||
) {}
|
||||
|
||||
async setInitialPassword(
|
||||
@@ -60,6 +63,8 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
|
||||
orgSsoIdentifier,
|
||||
orgId,
|
||||
resetPasswordAutoEnroll,
|
||||
newPassword,
|
||||
salt,
|
||||
} = credentials;
|
||||
|
||||
for (const [key, value] of Object.entries(credentials)) {
|
||||
@@ -153,6 +158,20 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
|
||||
userId,
|
||||
);
|
||||
|
||||
// Set master password unlock data for unlock path pointed to with
|
||||
// MasterPasswordUnlockData feature development
|
||||
// (requires: password, salt, kdf, userKey).
|
||||
// As migration to this strategy continues, both unlock paths need supported.
|
||||
// Several invocations in this file become redundant and can be removed once
|
||||
// the feature is enshrined/unwound. These are marked with [PM-23246] below.
|
||||
await this.setMasterPasswordUnlockData(
|
||||
newPassword,
|
||||
salt,
|
||||
kdfConfig,
|
||||
masterKeyEncryptedUserKey[0],
|
||||
userId,
|
||||
);
|
||||
|
||||
/**
|
||||
* Set the private key only for new JIT provisioned users in MP encryption orgs.
|
||||
* (Existing TDE users will have their private key set on sync or on login.)
|
||||
@@ -162,8 +181,17 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
|
||||
throw new Error("encrypted private key not found. Could not set private key in state.");
|
||||
}
|
||||
await this.keyService.setPrivateKey(keyPair[1].encryptedString, userId);
|
||||
await this.accountCryptographicStateService.setAccountCryptographicState(
|
||||
{
|
||||
V1: {
|
||||
private_key: keyPair[1].encryptedString,
|
||||
},
|
||||
},
|
||||
userId,
|
||||
);
|
||||
}
|
||||
|
||||
// [PM-23246] "Legacy" master key setting path - to be removed once unlock path migration is complete
|
||||
await this.masterPasswordService.setMasterKeyHash(newLocalMasterKeyHash, userId);
|
||||
|
||||
if (resetPasswordAutoEnroll) {
|
||||
@@ -206,10 +234,40 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
|
||||
userDecryptionOpts,
|
||||
);
|
||||
await this.kdfConfigService.setKdfConfig(userId, kdfConfig);
|
||||
// [PM-23246] "Legacy" master key setting path - to be removed once unlock path migration is complete
|
||||
await this.masterPasswordService.setMasterKey(masterKey, userId);
|
||||
// [PM-23246] "Legacy" master key setting path - to be removed once unlock path migration is complete
|
||||
await this.masterPasswordService.setMasterKeyEncryptedUserKey(
|
||||
masterKeyEncryptedUserKey[1],
|
||||
userId,
|
||||
);
|
||||
await this.keyService.setUserKey(masterKeyEncryptedUserKey[0], userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* As part of [PM-28494], adding this setting path to accommodate the changes that are
|
||||
* emerging with pm-23246-unlock-with-master-password-unlock-data.
|
||||
* Without this, immediately locking/unlocking the vault with the new password _may_ still fail
|
||||
* if sync has not completed. Sync will eventually set this data, but we want to ensure it's
|
||||
* set right away here to prevent a race condition UX issue that prevents immediate unlock.
|
||||
*/
|
||||
private async setMasterPasswordUnlockData(
|
||||
password: string,
|
||||
salt: MasterPasswordSalt,
|
||||
kdfConfig: KdfConfig,
|
||||
userKey: UserKey,
|
||||
userId: UserId,
|
||||
): Promise<void> {
|
||||
const masterPasswordUnlockData = await this.masterPasswordService.makeMasterPasswordUnlockData(
|
||||
password,
|
||||
kdfConfig,
|
||||
salt,
|
||||
userKey,
|
||||
);
|
||||
|
||||
await this.masterPasswordService.setMasterPasswordUnlockData(masterPasswordUnlockData, userId);
|
||||
}
|
||||
|
||||
private async handleResetPasswordAutoEnroll(
|
||||
masterKeyHash: string,
|
||||
orgId: string,
|
||||
|
||||
@@ -20,6 +20,7 @@ import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/ma
|
||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||
import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request";
|
||||
import { UpdateTdeOffboardingPasswordRequest } from "@bitwarden/common/auth/models/request/update-tde-offboarding-password.request";
|
||||
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import {
|
||||
EncryptedString,
|
||||
@@ -56,6 +57,7 @@ describe("DefaultSetInitialPasswordService", () => {
|
||||
let organizationApiService: MockProxy<OrganizationApiServiceAbstraction>;
|
||||
let organizationUserApiService: MockProxy<OrganizationUserApiService>;
|
||||
let userDecryptionOptionsService: MockProxy<InternalUserDecryptionOptionsServiceAbstraction>;
|
||||
let accountCryptographicStateService: MockProxy<AccountCryptographicStateService>;
|
||||
|
||||
let userId: UserId;
|
||||
let userKey: UserKey;
|
||||
@@ -73,6 +75,7 @@ describe("DefaultSetInitialPasswordService", () => {
|
||||
organizationApiService = mock<OrganizationApiServiceAbstraction>();
|
||||
organizationUserApiService = mock<OrganizationUserApiService>();
|
||||
userDecryptionOptionsService = mock<InternalUserDecryptionOptionsServiceAbstraction>();
|
||||
accountCryptographicStateService = mock<AccountCryptographicStateService>();
|
||||
|
||||
userId = "userId" as UserId;
|
||||
userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey;
|
||||
@@ -90,6 +93,7 @@ describe("DefaultSetInitialPasswordService", () => {
|
||||
organizationApiService,
|
||||
organizationUserApiService,
|
||||
userDecryptionOptionsService,
|
||||
accountCryptographicStateService,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -130,6 +134,8 @@ describe("DefaultSetInitialPasswordService", () => {
|
||||
orgSsoIdentifier: "orgSsoIdentifier",
|
||||
orgId: "orgId",
|
||||
resetPasswordAutoEnroll: false,
|
||||
newPassword: "Test@Password123!",
|
||||
salt: "user@example.com" as any,
|
||||
};
|
||||
userType = SetInitialPasswordUserType.JIT_PROVISIONED_MP_ORG_USER;
|
||||
|
||||
@@ -222,6 +228,8 @@ describe("DefaultSetInitialPasswordService", () => {
|
||||
"orgSsoIdentifier",
|
||||
"orgId",
|
||||
"resetPasswordAutoEnroll",
|
||||
"newPassword",
|
||||
"salt",
|
||||
].forEach((key) => {
|
||||
it(`should throw if ${key} is not provided on the SetInitialPasswordCredentials object`, async () => {
|
||||
// Arrange
|
||||
@@ -353,6 +361,10 @@ describe("DefaultSetInitialPasswordService", () => {
|
||||
ForceSetPasswordReason.None,
|
||||
userId,
|
||||
);
|
||||
expect(masterPasswordService.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(
|
||||
masterKeyEncryptedUserKey[1],
|
||||
userId,
|
||||
);
|
||||
});
|
||||
|
||||
it("should update account decryption properties", async () => {
|
||||
@@ -386,6 +398,16 @@ describe("DefaultSetInitialPasswordService", () => {
|
||||
// Assert
|
||||
expect(masterPasswordApiService.setPassword).toHaveBeenCalledWith(setPasswordRequest);
|
||||
expect(keyService.setPrivateKey).toHaveBeenCalledWith(keyPair[1].encryptedString, userId);
|
||||
expect(
|
||||
accountCryptographicStateService.setAccountCryptographicState,
|
||||
).toHaveBeenCalledWith(
|
||||
{
|
||||
V1: {
|
||||
private_key: keyPair[1].encryptedString as EncryptedString,
|
||||
},
|
||||
},
|
||||
userId,
|
||||
);
|
||||
});
|
||||
|
||||
it("should set the local master key hash to state", async () => {
|
||||
@@ -403,6 +425,36 @@ describe("DefaultSetInitialPasswordService", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("should create and set master password unlock data to prevent race condition with sync", async () => {
|
||||
// Arrange
|
||||
setupMocks();
|
||||
|
||||
const mockUnlockData = {
|
||||
salt: credentials.salt,
|
||||
kdf: credentials.kdfConfig,
|
||||
masterKeyWrappedUserKey: "wrapped_key_string",
|
||||
};
|
||||
|
||||
masterPasswordService.makeMasterPasswordUnlockData.mockResolvedValue(
|
||||
mockUnlockData as any,
|
||||
);
|
||||
|
||||
// Act
|
||||
await sut.setInitialPassword(credentials, userType, userId);
|
||||
|
||||
// Assert
|
||||
expect(masterPasswordService.makeMasterPasswordUnlockData).toHaveBeenCalledWith(
|
||||
credentials.newPassword,
|
||||
credentials.kdfConfig,
|
||||
credentials.salt,
|
||||
masterKeyEncryptedUserKey[0],
|
||||
);
|
||||
expect(masterPasswordService.setMasterPasswordUnlockData).toHaveBeenCalledWith(
|
||||
mockUnlockData,
|
||||
userId,
|
||||
);
|
||||
});
|
||||
|
||||
describe("given resetPasswordAutoEnroll is true", () => {
|
||||
it(`should handle reset password (account recovery) auto enroll`, async () => {
|
||||
// Arrange
|
||||
@@ -572,6 +624,10 @@ describe("DefaultSetInitialPasswordService", () => {
|
||||
credentials.newMasterKey,
|
||||
userId,
|
||||
);
|
||||
expect(masterPasswordService.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(
|
||||
masterKeyEncryptedUserKey[1],
|
||||
userId,
|
||||
);
|
||||
expect(keyService.setUserKey).toHaveBeenCalledWith(masterKeyEncryptedUserKey[0], userId);
|
||||
});
|
||||
|
||||
@@ -602,6 +658,36 @@ describe("DefaultSetInitialPasswordService", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("should create and set master password unlock data to prevent race condition with sync", async () => {
|
||||
// Arrange
|
||||
setupMocks({ ...defaultMockConfig, userType });
|
||||
|
||||
const mockUnlockData = {
|
||||
salt: credentials.salt,
|
||||
kdf: credentials.kdfConfig,
|
||||
masterKeyWrappedUserKey: "wrapped_key_string",
|
||||
};
|
||||
|
||||
masterPasswordService.makeMasterPasswordUnlockData.mockResolvedValue(
|
||||
mockUnlockData as any,
|
||||
);
|
||||
|
||||
// Act
|
||||
await sut.setInitialPassword(credentials, userType, userId);
|
||||
|
||||
// Assert
|
||||
expect(masterPasswordService.makeMasterPasswordUnlockData).toHaveBeenCalledWith(
|
||||
credentials.newPassword,
|
||||
credentials.kdfConfig,
|
||||
credentials.salt,
|
||||
masterKeyEncryptedUserKey[0],
|
||||
);
|
||||
expect(masterPasswordService.setMasterPasswordUnlockData).toHaveBeenCalledWith(
|
||||
mockUnlockData,
|
||||
userId,
|
||||
);
|
||||
});
|
||||
|
||||
describe("given resetPasswordAutoEnroll is true", () => {
|
||||
it(`should handle reset password (account recovery) auto enroll`, async () => {
|
||||
// Arrange
|
||||
|
||||
@@ -214,6 +214,8 @@ export class SetInitialPasswordComponent implements OnInit {
|
||||
assertTruthy(passwordInputResult.newServerMasterKeyHash, "newServerMasterKeyHash", ctx);
|
||||
assertTruthy(passwordInputResult.newLocalMasterKeyHash, "newLocalMasterKeyHash", ctx);
|
||||
assertTruthy(passwordInputResult.kdfConfig, "kdfConfig", ctx);
|
||||
assertTruthy(passwordInputResult.newPassword, "newPassword", ctx);
|
||||
assertTruthy(passwordInputResult.salt, "salt", ctx);
|
||||
assertTruthy(this.orgSsoIdentifier, "orgSsoIdentifier", ctx);
|
||||
assertTruthy(this.orgId, "orgId", ctx);
|
||||
assertTruthy(this.userType, "userType", ctx);
|
||||
@@ -231,6 +233,8 @@ export class SetInitialPasswordComponent implements OnInit {
|
||||
orgSsoIdentifier: this.orgSsoIdentifier,
|
||||
orgId: this.orgId,
|
||||
resetPasswordAutoEnroll: this.resetPasswordAutoEnroll,
|
||||
newPassword: passwordInputResult.newPassword,
|
||||
salt: passwordInputResult.salt,
|
||||
};
|
||||
|
||||
await this.setInitialPasswordService.setInitialPassword(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { MasterPasswordSalt } from "@bitwarden/common/key-management/master-password/types/master-password.types";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { MasterKey } from "@bitwarden/common/types/key";
|
||||
import { KdfConfig } from "@bitwarden/key-management";
|
||||
@@ -50,6 +51,8 @@ export interface SetInitialPasswordCredentials {
|
||||
orgSsoIdentifier: string;
|
||||
orgId: string;
|
||||
resetPasswordAutoEnroll: boolean;
|
||||
newPassword: string;
|
||||
salt: MasterPasswordSalt;
|
||||
}
|
||||
|
||||
export interface SetInitialPasswordTdeOffboardingCredentials {
|
||||
|
||||
@@ -168,6 +168,8 @@ import { OrganizationBillingService } from "@bitwarden/common/billing/services/o
|
||||
import { DefaultSubscriptionPricingService } from "@bitwarden/common/billing/services/subscription-pricing.service";
|
||||
import { HibpApiService } from "@bitwarden/common/dirt/services/hibp-api.service";
|
||||
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
|
||||
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
|
||||
import { DefaultAccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/default-account-cryptographic-state.service";
|
||||
import {
|
||||
DefaultKeyGenerationService,
|
||||
KeyGenerationService,
|
||||
@@ -528,7 +530,7 @@ const safeProviders: SafeProvider[] = [
|
||||
safeProvider({
|
||||
provide: ChangeKdfService,
|
||||
useClass: DefaultChangeKdfService,
|
||||
deps: [ChangeKdfApiService, SdkService],
|
||||
deps: [ChangeKdfApiService, SdkService, KeyService, InternalMasterPasswordServiceAbstraction],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: EncryptedMigrator,
|
||||
@@ -572,6 +574,7 @@ const safeProviders: SafeProvider[] = [
|
||||
KdfConfigService,
|
||||
TaskSchedulerService,
|
||||
ConfigService,
|
||||
AccountCryptographicStateService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
@@ -897,8 +900,14 @@ const safeProviders: SafeProvider[] = [
|
||||
StateProvider,
|
||||
SecurityStateService,
|
||||
KdfConfigService,
|
||||
AccountCryptographicStateService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: AccountCryptographicStateService,
|
||||
useClass: DefaultAccountCryptographicStateService,
|
||||
deps: [StateProvider],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: BroadcasterService,
|
||||
useClass: DefaultBroadcasterService,
|
||||
@@ -1336,7 +1345,7 @@ const safeProviders: SafeProvider[] = [
|
||||
safeProvider({
|
||||
provide: ChangeKdfService,
|
||||
useClass: DefaultChangeKdfService,
|
||||
deps: [ChangeKdfApiService, SdkService],
|
||||
deps: [ChangeKdfApiService, SdkService, KeyService, InternalMasterPasswordServiceAbstraction],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: AuthRequestServiceAbstraction,
|
||||
@@ -1568,6 +1577,7 @@ const safeProviders: SafeProvider[] = [
|
||||
OrganizationApiServiceAbstraction,
|
||||
OrganizationUserApiService,
|
||||
InternalUserDecryptionOptionsServiceAbstraction,
|
||||
AccountCryptographicStateService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
|
||||
Reference in New Issue
Block a user