Browse Source

[PM-27086] create setInitialPasswordV2()

auth/pm-27086/input-password-use-new-km-data-types
rr-bw 2 days ago
parent
commit
b35d38eec8
No known key found for this signature in database
GPG Key ID: 3FA13C3ADEE51D5D
  1. 168
      libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts
  2. 59
      libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts
  3. 31
      libs/angular/src/auth/password-management/set-initial-password/set-initial-password.service.abstraction.ts

168
libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts

@ -18,12 +18,19 @@ import { UpdateTdeOffboardingPasswordRequest } from "@bitwarden/common/auth/mode @@ -18,12 +18,19 @@ import { UpdateTdeOffboardingPasswordRequest } from "@bitwarden/common/auth/mode
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 {
MasterPasswordAuthenticationData,
MasterPasswordAuthenticationHash,
MasterPasswordUnlockData,
} 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";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { UserId } from "@bitwarden/common/types/guid";
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { KdfConfigService, KeyService, KdfConfig } from "@bitwarden/key-management";
import { PureCrypto } from "@bitwarden/sdk-internal";
import {
SetInitialPasswordService,
@ -46,6 +53,9 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi @@ -46,6 +53,9 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction,
) {}
/**
* @deprecated To be removed in PM-28143
*/
async setInitialPassword(
credentials: SetInitialPasswordCredentials,
userType: SetInitialPasswordUserType,
@ -171,6 +181,145 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi @@ -171,6 +181,145 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
}
}
async setInitialPasswordV2(
credentials: SetInitialPasswordCredentials,
userType: SetInitialPasswordUserType,
userId: UserId,
): Promise<void> {
const {
newPassword,
newPasswordHint,
kdfConfig,
salt,
orgSsoIdentifier,
orgId,
resetPasswordAutoEnroll,
} = credentials;
for (const [key, value] of Object.entries(credentials)) {
if (value == null) {
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.");
}
let userKey = await firstValueFrom(this.keyService.userKey$(userId));
if (userKey == null) {
userKey = new SymmetricCryptoKey(PureCrypto.make_user_key_aes256_cbc_hmac()) as UserKey;
}
const authenticationData: MasterPasswordAuthenticationData =
await this.masterPasswordService.makeMasterPasswordAuthenticationData(
newPassword,
kdfConfig,
salt,
);
const unlockData: MasterPasswordUnlockData =
await this.masterPasswordService.makeMasterPasswordUnlockData(
newPassword,
kdfConfig,
salt,
userKey,
);
if (unlockData.masterKeyWrappedUserKey == null) {
throw new Error("masterKeyEncryptedUserKey not found. Could not set password.");
}
let keyPair: [string, EncString] | null = null;
let keysRequest: KeysRequest | null = null;
if (userType === SetInitialPasswordUserType.JIT_PROVISIONED_MP_ORG_USER) {
/**
* A user being JIT provisioned into a MP encryption org does not yet have a user
* asymmetric key pair, so we create it for them here.
*
* Sidenote:
* In the case of a TDE user whose permissions require that they have a MP - that user
* will already have a user asymmetric key pair by this point, so we skip this if-block
* so that we don't create a new key pair for them.
*/
// Extra safety check (see description on https://github.com/bitwarden/clients/pull/10180):
// In case we have have a local private key and are not sure whether it has been posted to the server,
// we post the local private key instead of generating a new one
const existingUserPrivateKey = (await firstValueFrom(
this.keyService.userPrivateKey$(userId),
)) as Uint8Array;
const existingUserPublicKey = await firstValueFrom(this.keyService.userPublicKey$(userId));
if (existingUserPrivateKey != null && existingUserPublicKey != null) {
const existingUserPublicKeyB64 = Utils.fromBufferToB64(existingUserPublicKey);
// Existing key pair
keyPair = [
existingUserPublicKeyB64,
await this.encryptService.wrapDecapsulationKey(existingUserPrivateKey, userKey),
];
} else {
// New key pair
keyPair = await this.keyService.makeKeyPair(userKey);
}
if (keyPair == null) {
throw new Error("keyPair not found. Could not set password.");
}
if (!keyPair[1].encryptedString) {
throw new Error("encrypted private key not found. Could not set password.");
}
keysRequest = new KeysRequest(keyPair[0], keyPair[1].encryptedString);
}
const request = SetPasswordRequest.newConstructor(
authenticationData,
unlockData,
newPasswordHint,
orgSsoIdentifier,
keysRequest,
);
await this.masterPasswordApiService.setPassword(request);
// Clear force set password reason to allow navigation back to vault.
await this.masterPasswordService.setForceSetPasswordReason(ForceSetPasswordReason.None, userId);
// User now has a password so update account decryption options in state
await this.updateAccountDecryptionPropertiesV2(unlockData, 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.)
*/
if (keyPair != null && userType === SetInitialPasswordUserType.JIT_PROVISIONED_MP_ORG_USER) {
if (!keyPair[1].encryptedString) {
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.masterPasswordService.setMasterKeyHash(newLocalMasterKeyHash, userId); // TODO-rr-bw: how to handle local key hash?
if (resetPasswordAutoEnroll) {
await this.handleResetPasswordAutoEnroll(
authenticationData.masterPasswordAuthenticationHash,
orgId,
userId,
);
}
}
/**
* @deprecated To be removed in PM-28143
*/
private async makeMasterKeyEncryptedUserKey(
masterKey: MasterKey,
userId: UserId,
@ -210,8 +359,25 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi @@ -210,8 +359,25 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
await this.keyService.setUserKey(masterKeyEncryptedUserKey[0], userId);
}
private async updateAccountDecryptionPropertiesV2(
unlockData: MasterPasswordUnlockData,
userId: UserId,
) {
const userDecryptionOpts = await firstValueFrom(
this.userDecryptionOptionsService.userDecryptionOptionsById$(userId),
);
userDecryptionOpts.hasMasterPassword = true;
await this.userDecryptionOptionsService.setUserDecryptionOptionsById(
userId,
userDecryptionOpts,
);
await this.masterPasswordService.setMasterPasswordUnlockData(unlockData, userId);
// await this.masterPasswordService.setMasterKey(masterKey, userId); // TODO-rr-bw: how to handle this? remove this?
}
private async handleResetPasswordAutoEnroll(
masterKeyHash: string,
masterKeyHash: MasterPasswordAuthenticationHash | string, // In PM-28143, remove `| string`; should only accept MasterPasswordAuthenticationHash.
orgId: string,
userId: UserId,
) {

59
libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts

@ -22,7 +22,9 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv @@ -22,7 +22,9 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { assertTruthy, assertNonNullish } from "@bitwarden/common/auth/utils";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
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 { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
@ -71,6 +73,7 @@ export class SetInitialPasswordComponent implements OnInit { @@ -71,6 +73,7 @@ export class SetInitialPasswordComponent implements OnInit {
private accountService: AccountService,
private activatedRoute: ActivatedRoute,
private anonLayoutWrapperDataService: AnonLayoutWrapperDataService,
private configService: ConfigService,
private dialogService: DialogService,
private i18nService: I18nService,
private logoutService: LogoutService,
@ -194,9 +197,19 @@ export class SetInitialPasswordComponent implements OnInit { @@ -194,9 +197,19 @@ export class SetInitialPasswordComponent implements OnInit {
switch (this.userType) {
case SetInitialPasswordUserType.JIT_PROVISIONED_MP_ORG_USER:
case SetInitialPasswordUserType.TDE_ORG_USER_RESET_PASSWORD_PERMISSION_REQUIRES_MP:
case SetInitialPasswordUserType.TDE_ORG_USER_RESET_PASSWORD_PERMISSION_REQUIRES_MP: {
const newApisFlagEnabled = await this.configService.getFeatureFlag(
FeatureFlag.PM27086_UpdateAuthenticationApisForInputPassword,
);
if (newApisFlagEnabled) {
await this.setInitialPasswordV2(passwordInputResult);
return;
}
await this.setInitialPassword(passwordInputResult);
break;
}
case SetInitialPasswordUserType.OFFBOARDED_TDE_ORG_USER:
await this.setInitialPasswordTdeOffboarding(passwordInputResult);
break;
@ -208,6 +221,9 @@ export class SetInitialPasswordComponent implements OnInit { @@ -208,6 +221,9 @@ export class SetInitialPasswordComponent implements OnInit {
}
}
/**
* @deprecated To be removed in PM-28143
*/
private async setInitialPassword(passwordInputResult: PasswordInputResult) {
const ctx = "Could not set initial password.";
assertTruthy(passwordInputResult.newMasterKey, "newMasterKey", ctx);
@ -250,6 +266,47 @@ export class SetInitialPasswordComponent implements OnInit { @@ -250,6 +266,47 @@ export class SetInitialPasswordComponent implements OnInit {
}
}
private async setInitialPasswordV2(passwordInputResult: PasswordInputResult) {
const ctx = "Could not set initial password.";
assertTruthy(passwordInputResult.newPassword, "newPassword", ctx);
assertTruthy(passwordInputResult.kdfConfig, "kdfConfig", ctx);
assertTruthy(passwordInputResult.salt, "salt", ctx);
assertTruthy(this.orgSsoIdentifier, "orgSsoIdentifier", ctx);
assertTruthy(this.orgId, "orgId", ctx);
assertTruthy(this.userType, "userType", ctx);
assertTruthy(this.userId, "userId", ctx);
assertNonNullish(passwordInputResult.newPasswordHint, "newPasswordHint", ctx); // can have an empty string as a valid value, so check non-nullish
assertNonNullish(this.resetPasswordAutoEnroll, "resetPasswordAutoEnroll", ctx); // can have `false` as a valid value, so check non-nullish
try {
const credentials: SetInitialPasswordCredentials = {
newPassword: passwordInputResult.newPassword,
newPasswordHint: passwordInputResult.newPasswordHint,
kdfConfig: passwordInputResult.kdfConfig,
salt: passwordInputResult.salt,
orgSsoIdentifier: this.orgSsoIdentifier,
orgId: this.orgId,
resetPasswordAutoEnroll: this.resetPasswordAutoEnroll,
};
await this.setInitialPasswordService.setInitialPasswordV2(
credentials,
this.userType,
this.userId,
);
this.showSuccessToastByUserType();
this.submitting = false;
await this.router.navigate(["vault"]);
} catch (e) {
this.logService.error("Error setting initial password", e);
this.validationService.showError(e);
this.submitting = false;
}
}
private async setInitialPasswordTdeOffboarding(passwordInputResult: PasswordInputResult) {
const ctx = "Could not set initial password.";
assertTruthy(passwordInputResult.newMasterKey, "newMasterKey", ctx);

31
libs/angular/src/auth/password-management/set-initial-password/set-initial-password.service.abstraction.ts

@ -1,3 +1,4 @@ @@ -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,14 +43,21 @@ export const SetInitialPasswordUserType: Readonly<{ @@ -42,14 +43,21 @@ export const SetInitialPasswordUserType: Readonly<{
}> = Object.freeze(_SetInitialPasswordUserType);
export interface SetInitialPasswordCredentials {
newMasterKey: MasterKey;
newServerMasterKeyHash: string;
newLocalMasterKeyHash: string;
newPassword?: string; // Make required in PM-28143 (remove `?`)
newPasswordHint: string;
kdfConfig: KdfConfig;
salt?: MasterPasswordSalt; // Make required in PM-28143 (remove `?`)
orgSsoIdentifier: string;
orgId: string;
resetPasswordAutoEnroll: boolean;
// The deprecated properties below will be removed in PM-28143
/** @deprecated */
newMasterKey?: MasterKey;
/** @deprecated */
newServerMasterKeyHash?: string;
/** @deprecated */
newLocalMasterKeyHash?: string;
}
export interface SetInitialPasswordTdeOffboardingCredentials {
@ -66,6 +74,8 @@ export interface SetInitialPasswordTdeOffboardingCredentials { @@ -66,6 +74,8 @@ export interface SetInitialPasswordTdeOffboardingCredentials {
*/
export abstract class SetInitialPasswordService {
/**
* @deprecated To be removed in PM-28143
*
* Sets an initial password for an existing authed user who is either:
* - {@link SetInitialPasswordUserType.JIT_PROVISIONED_MP_ORG_USER}
* - {@link SetInitialPasswordUserType.TDE_ORG_USER_RESET_PASSWORD_PERMISSION_REQUIRES_MP}
@ -92,4 +102,19 @@ export abstract class SetInitialPasswordService { @@ -92,4 +102,19 @@ export abstract class SetInitialPasswordService {
credentials: SetInitialPasswordTdeOffboardingCredentials,
userId: UserId,
) => Promise<void>;
/**
* Sets an initial password for an existing authed user who is either:
* - {@link SetInitialPasswordUserType.JIT_PROVISIONED_MP_ORG_USER}
* - {@link SetInitialPasswordUserType.TDE_ORG_USER_RESET_PASSWORD_PERMISSION_REQUIRES_MP}
*
* @param credentials An object of the credentials needed to set the initial password
* @throws If any property on the `credentials` object is null or undefined, or if a
* masterKeyEncryptedUserKey or newKeyPair could not be created.
*/
abstract setInitialPasswordV2: (
credentials: SetInitialPasswordCredentials,
userType: SetInitialPasswordUserType,
userId: UserId,
) => Promise<void>;
}

Loading…
Cancel
Save