1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-06 19:53:59 +00:00

Migrate set initial password component to new API

This commit is contained in:
Bernd Schoolmann
2025-07-23 19:00:15 +02:00
parent 9651196d79
commit adfa3a946e
13 changed files with 100 additions and 100 deletions

View File

@@ -12,8 +12,8 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import {
EncryptedString,
EncString,
UnsignedSharedKey,
} from "@bitwarden/common/key-management/crypto/models/enc-string";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
@@ -60,7 +60,7 @@ export class OrganizationUserResetPasswordService
orgId: string,
userKey: UserKey,
trustedPublicKeys: Uint8Array[],
): Promise<EncryptedString> {
): Promise<UnsignedSharedKey> {
if (userKey == null) {
throw new Error("User key is required for recovery.");
}
@@ -84,7 +84,7 @@ export class OrganizationUserResetPasswordService
// RSA Encrypt user key with organization's public key
const encryptedKey = await this.encryptService.encapsulateKeyUnsigned(userKey, publicKey);
return encryptedKey.encryptedString;
return encryptedKey.toUnsignedSharedKey();
}
/**
@@ -208,9 +208,8 @@ export class OrganizationUserResetPasswordService
const encryptedKey = await this.buildRecoveryKey(org.id, newUserKey, trustedPublicKeys);
// Create/Execute request
const request = new OrganizationUserResetPasswordWithIdRequest();
const request = new OrganizationUserResetPasswordWithIdRequest(encryptedKey);
request.organizationId = org.id;
request.resetPasswordKey = encryptedKey;
request.masterPasswordHash = "ignored";
requests.push(request);

View File

@@ -16,6 +16,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { UnsignedSharedKey } from "@bitwarden/common/key-management/crypto/models/enc-string";
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";
@@ -214,9 +215,9 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
);
} else {
// Remove reset password
const request = new OrganizationUserResetPasswordEnrollmentRequest();
// Note: This seems wrong, and should be replaced by null or undefined, since "" is not a valid UnsignedSharedKey.
const request = new OrganizationUserResetPasswordEnrollmentRequest("" as UnsignedSharedKey);
request.masterPasswordHash = "ignored";
request.resetPasswordKey = "";
this.actionPromise =
this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment(
this.organization.id,

View File

@@ -1,9 +1,15 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
import { UnsignedSharedKey } from "@bitwarden/common/key-management/crypto/models/enc-string";
export class OrganizationUserResetPasswordEnrollmentRequest extends SecretVerificationRequest {
resetPasswordKey: string;
resetPasswordKey: UnsignedSharedKey;
constructor(unsignedSharedKey: UnsignedSharedKey) {
super();
this.resetPasswordKey = unsignedSharedKey;
}
}
export class OrganizationUserResetPasswordWithIdRequest extends OrganizationUserResetPasswordEnrollmentRequest {

View File

@@ -218,9 +218,10 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements
publicKey,
);
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest(
encryptedUserKey.toUnsignedSharedKey(),
);
resetRequest.masterPasswordHash = masterPasswordHash;
resetRequest.resetPasswordKey = encryptedUserKey.encryptedString;
return this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment(
this.orgId,

View File

@@ -15,14 +15,20 @@ 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 { assertNonNullish } from "@bitwarden/common/auth/utils";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import {
EncString,
UnsignedSharedKey,
} from "@bitwarden/common/key-management/crypto/models/enc-string";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { MasterPasswordAuthenticationData } from "@bitwarden/common/key-management/master-password/types/master-password.types";
import { firstValueFromOrThrow } from "@bitwarden/common/key-management/utils";
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";
import { UserId } from "@bitwarden/common/types/guid";
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { UserKey } from "@bitwarden/common/types/key";
import { KdfConfigService, KeyService, KdfConfig } from "@bitwarden/key-management";
import {
@@ -52,11 +58,10 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
userId: UserId,
): Promise<void> {
const {
newMasterKey,
newServerMasterKeyHash,
newLocalMasterKeyHash,
newPassword,
newPasswordHint,
kdfConfig,
salt,
orgSsoIdentifier,
orgId,
resetPasswordAutoEnroll,
@@ -67,20 +72,21 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
throw new Error(`${key} not found. Could not set password.`);
}
}
if (userId == null) {
throw new Error("userId not found. Could not set password.");
}
if (userType == null) {
throw new Error("userType not found. Could not set password.");
}
const masterKeyEncryptedUserKey = await this.makeMasterKeyEncryptedUserKey(
newMasterKey,
userId,
assertNonNullish(userId, "userId", "setInitialPassword");
assertNonNullish(userType, "userType", "setInitialPassword");
const userKey = await this.keyService.makeUserKeyV1();
const authenticationData =
await this.masterPasswordService.makeMasterPasswordAuthenticationData(
newPassword,
kdfConfig,
salt,
);
const unlockData = await this.masterPasswordService.makeMasterPasswordUnlockData(
newPassword,
kdfConfig,
salt,
userKey,
);
if (masterKeyEncryptedUserKey == null || !masterKeyEncryptedUserKey[1].encryptedString) {
throw new Error("masterKeyEncryptedUserKey not found. Could not set password.");
}
let keyPair: [string, EncString] | null = null;
let keysRequest: KeysRequest | null = null;
@@ -111,14 +117,11 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
// Existing key pair
keyPair = [
existingUserPublicKeyB64,
await this.encryptService.wrapDecapsulationKey(
existingUserPrivateKey,
masterKeyEncryptedUserKey[0],
),
await this.encryptService.wrapDecapsulationKey(existingUserPrivateKey, userKey),
];
} else {
// New key pair
keyPair = await this.keyService.makeKeyPair(masterKeyEncryptedUserKey[0]);
keyPair = await this.keyService.makeKeyPair(userKey);
}
if (keyPair == null) {
@@ -131,14 +134,12 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
keysRequest = new KeysRequest(keyPair[0], keyPair[1].encryptedString);
}
const request = new SetPasswordRequest(
newServerMasterKeyHash,
masterKeyEncryptedUserKey[1].encryptedString,
const request = SetPasswordRequest.newConstructor(
authenticationData,
unlockData,
newPasswordHint,
orgSsoIdentifier,
keysRequest,
kdfConfig.kdfType,
kdfConfig.iterations,
);
await this.masterPasswordApiService.setPassword(request);
@@ -147,12 +148,7 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
await this.masterPasswordService.setForceSetPasswordReason(ForceSetPasswordReason.None, userId);
// User now has a password so update account decryption options in state
await this.updateAccountDecryptionProperties(
newMasterKey,
kdfConfig,
masterKeyEncryptedUserKey,
userId,
);
await this.updateAccountDecryptionProperties(kdfConfig, userKey, userId);
/**
* Set the private key only for new JIT provisioned users in MP encryption orgs.
@@ -165,34 +161,14 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
await this.keyService.setPrivateKey(keyPair[1].encryptedString, userId);
}
await this.masterPasswordService.setMasterKeyHash(newLocalMasterKeyHash, userId);
if (resetPasswordAutoEnroll) {
await this.handleResetPasswordAutoEnroll(newServerMasterKeyHash, orgId, userId);
await this.handleResetPasswordAutoEnroll(authenticationData, orgId as OrganizationId, userId);
}
}
private async makeMasterKeyEncryptedUserKey(
masterKey: MasterKey,
userId: UserId,
): Promise<[UserKey, EncString]> {
let masterKeyEncryptedUserKey: [UserKey, EncString] | null = null;
const userKey = await firstValueFrom(this.keyService.userKey$(userId));
if (userKey == null) {
masterKeyEncryptedUserKey = await this.keyService.makeUserKey(masterKey);
} else {
masterKeyEncryptedUserKey = await this.keyService.encryptUserKeyWithMasterKey(masterKey);
}
return masterKeyEncryptedUserKey;
}
private async updateAccountDecryptionProperties(
masterKey: MasterKey,
kdfConfig: KdfConfig,
masterKeyEncryptedUserKey: [UserKey, EncString],
userKey: UserKey,
userId: UserId,
) {
const userDecryptionOpts = await firstValueFrom(
@@ -201,13 +177,12 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
userDecryptionOpts.hasMasterPassword = true;
await this.userDecryptionOptionsService.setUserDecryptionOptions(userDecryptionOpts);
await this.kdfConfigService.setKdfConfig(userId, kdfConfig);
await this.masterPasswordService.setMasterKey(masterKey, userId);
await this.keyService.setUserKey(masterKeyEncryptedUserKey[0], userId);
await this.keyService.setUserKey(userKey, userId);
}
private async handleResetPasswordAutoEnroll(
masterKeyHash: string,
orgId: string,
authenticationData: MasterPasswordAuthenticationData,
orgId: OrganizationId,
userId: UserId,
) {
const organizationKeys = await this.organizationApiService.getKeys(orgId);
@@ -219,27 +194,23 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
}
const orgPublicKey = Utils.fromB64ToArray(organizationKeys.publicKey);
const userKey = await firstValueFrom(this.keyService.userKey$(userId));
if (userKey == null) {
throw new Error("userKey not found. Could not handle reset password auto enroll.");
}
const userKey = await firstValueFromOrThrow(this.keyService.userKey$(userId), "userKey");
// RSA encrypt user key with organization public key
const orgPublicKeyEncryptedUserKey = await this.encryptService.encapsulateKeyUnsigned(
userKey,
orgPublicKey,
);
const orgPublicKeyEncryptedUserKey: UnsignedSharedKey = (
await this.encryptService.encapsulateKeyUnsigned(userKey, orgPublicKey)
).toUnsignedSharedKey();
if (orgPublicKeyEncryptedUserKey == null || !orgPublicKeyEncryptedUserKey.encryptedString) {
if (orgPublicKeyEncryptedUserKey == null || !orgPublicKeyEncryptedUserKey) {
throw new Error(
"orgPublicKeyEncryptedUserKey not found. Could not handle reset password auto enroll.",
);
}
const enrollmentRequest = new OrganizationUserResetPasswordEnrollmentRequest();
enrollmentRequest.masterPasswordHash = masterKeyHash;
enrollmentRequest.resetPasswordKey = orgPublicKeyEncryptedUserKey.encryptedString;
const enrollmentRequest = new OrganizationUserResetPasswordEnrollmentRequest(
orgPublicKeyEncryptedUserKey,
);
enrollmentRequest.authenticateWith(authenticationData);
await this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment(
orgId,

View File

@@ -222,11 +222,10 @@ export class SetInitialPasswordComponent implements OnInit {
try {
const credentials: SetInitialPasswordCredentials = {
newMasterKey: passwordInputResult.newMasterKey,
newServerMasterKeyHash: passwordInputResult.newServerMasterKeyHash,
newLocalMasterKeyHash: passwordInputResult.newLocalMasterKeyHash,
newPassword: passwordInputResult.newPassword,
newPasswordHint: passwordInputResult.newPasswordHint,
kdfConfig: passwordInputResult.kdfConfig,
salt: passwordInputResult.salt,
orgSsoIdentifier: this.orgSsoIdentifier,
orgId: this.orgId,
resetPasswordAutoEnroll: this.resetPasswordAutoEnroll,

View File

@@ -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";
@@ -42,12 +43,12 @@ export const SetInitialPasswordUserType: Readonly<{
}> = Object.freeze(_SetInitialPasswordUserType);
export interface SetInitialPasswordCredentials {
newMasterKey: MasterKey;
newServerMasterKeyHash: string;
newLocalMasterKeyHash: string;
newPassword: string;
newPasswordHint: string;
kdfConfig: KdfConfig;
salt: MasterPasswordSalt;
orgSsoIdentifier: string;
// Note: This should be refactored to be a OrganizationId
orgId: string;
resetPasswordAutoEnroll: boolean;
}

View File

@@ -163,9 +163,10 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService {
const encryptedUserKey = await this.encryptService.encapsulateKeyUnsigned(userKey, publicKey);
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest(
encryptedUserKey.toUnsignedSharedKey(),
);
resetRequest.masterPasswordHash = masterKeyHash;
resetRequest.resetPasswordKey = encryptedUserKey.encryptedString;
await this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment(
orgId,

View File

@@ -11,10 +11,7 @@ export class SecretVerificationRequest {
/**
* Mutates this request to include the master password authentication data, to authenticate the request.
*/
authenticateWith(
masterPasswordAuthenticationData: MasterPasswordAuthenticationData,
): SecretVerificationRequest {
authenticateWith(masterPasswordAuthenticationData: MasterPasswordAuthenticationData) {
this.masterPasswordHash = masterPasswordAuthenticationData.masterPasswordAuthenticationHash;
return this;
}
}

View File

@@ -57,13 +57,10 @@ export class PasswordResetEnrollmentServiceImplementation
// RSA Encrypt user's userKey.key with organization public key
const encryptedKey = await this.encryptService.encapsulateKeyUnsigned(userKey, orgPublicKey);
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
resetRequest.resetPasswordKey = encryptedKey.encryptedString;
await this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment(
organizationId,
userId,
resetRequest,
new OrganizationUserResetPasswordEnrollmentRequest(encryptedKey.toUnsignedSharedKey()),
);
}
}

View File

@@ -2,6 +2,8 @@
// @ts-strict-ignore
import { Jsonify, Opaque } from "type-fest";
import { UnsignedSharedKey as SdkUnsignedSharedkey } from "@bitwarden/sdk-internal";
import { EncryptionType, EXPECTED_NUM_PARTS_BY_ENCRYPTION_TYPE } from "../../../platform/enums";
import { Encrypted } from "../../../platform/interfaces/encrypted";
import { Utils } from "../../../platform/misc/utils";
@@ -47,6 +49,11 @@ export class EncString implements Encrypted {
return this.encryptedString;
}
// Note: Unsigned shared key should be split out of EncString completely. This currently lacks type-safety.
toUnsignedSharedKey(): UnsignedSharedKey {
return this.encryptedString as unknown as UnsignedSharedKey;
}
toJSON() {
return this.encryptedString as string;
}
@@ -215,4 +222,14 @@ export class EncString implements Encrypted {
}
}
/**
* EncryptedString is symmetrically encrypted
* Note: This will eventually be replaced by the SDK version of EncryptedString.
*/
export type EncryptedString = Opaque<string, "EncString">;
/**
* UnsignedSharedKey is a key, encrypted with a public key, but not signed. The sender cannot be authenticated.
* Note: This will eventually be replaced by the SDK version of UnsignedSharedKey.
*/
export type UnsignedSharedKey = Opaque<SdkUnsignedSharedkey, "UnsignedSharedKey">;

View File

@@ -155,6 +155,10 @@ export abstract class KeyService {
* @returns A new user key and the master key protected version of it
*/
abstract makeUserKey(masterKey: MasterKey | null): Promise<[UserKey, EncString]>;
/**
* Generates a new v1 user key
*/
abstract makeUserKeyV1(): Promise<UserKey>;
/**
* Clears the user's stored version of the user key
* @param keySuffix The desired version of the key to clear
@@ -208,6 +212,7 @@ export abstract class KeyService {
): Promise<string>;
/**
* Compares the provided master password to the stored password hash.
* @deprecated
* @param masterPassword The user's master password
* @param key The user's master key
* @param userId The id of the user to do the operation for.

View File

@@ -235,6 +235,11 @@ export class DefaultKeyService implements KeyServiceAbstraction {
return this.buildProtectedSymmetricKey(masterKey, newUserKey);
}
async makeUserKeyV1(): Promise<UserKey> {
const key = await this.keyGenerationService.createKey(512);
return key as UserKey;
}
/**
* Clears the user key. Clears all stored versions of the user keys as well, such as the biometrics key
* @param userId The desired user