diff --git a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts index a1727a8cc59..14dd55ba347 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts @@ -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 { + ): Promise { 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); diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts index e95ea669725..9ef226b534c 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts @@ -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, diff --git a/libs/admin-console/src/common/organization-user/models/requests/organization-user-reset-password-enrollment.request.ts b/libs/admin-console/src/common/organization-user/models/requests/organization-user-reset-password-enrollment.request.ts index f106438d440..0fe9b5830fd 100644 --- a/libs/admin-console/src/common/organization-user/models/requests/organization-user-reset-password-enrollment.request.ts +++ b/libs/admin-console/src/common/organization-user/models/requests/organization-user-reset-password-enrollment.request.ts @@ -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 { diff --git a/libs/angular/src/auth/components/set-password.component.ts b/libs/angular/src/auth/components/set-password.component.ts index 03cbdbc625a..2cfbfb5be28 100644 --- a/libs/angular/src/auth/components/set-password.component.ts +++ b/libs/angular/src/auth/components/set-password.component.ts @@ -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, diff --git a/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts b/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts index fbab228d6e0..12090204baa 100644 --- a/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts +++ b/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts @@ -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 { 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, diff --git a/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts b/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts index 27d4c11f692..5f90e609ed6 100644 --- a/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts +++ b/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts @@ -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, diff --git a/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.service.abstraction.ts b/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.service.abstraction.ts index c1f6ba1a5ec..dcb37d12715 100644 --- a/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.service.abstraction.ts +++ b/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.service.abstraction.ts @@ -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; } diff --git a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts index 5fc3272b650..b94c897dd93 100644 --- a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts +++ b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts @@ -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, diff --git a/libs/common/src/auth/models/request/secret-verification.request.ts b/libs/common/src/auth/models/request/secret-verification.request.ts index 0572b11bac5..7e0b88e98a1 100644 --- a/libs/common/src/auth/models/request/secret-verification.request.ts +++ b/libs/common/src/auth/models/request/secret-verification.request.ts @@ -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; } } diff --git a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts index f491d7d5eb0..b290137f74e 100644 --- a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts +++ b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts @@ -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()), ); } } diff --git a/libs/common/src/key-management/crypto/models/enc-string.ts b/libs/common/src/key-management/crypto/models/enc-string.ts index 164c4b6b603..c657235a676 100644 --- a/libs/common/src/key-management/crypto/models/enc-string.ts +++ b/libs/common/src/key-management/crypto/models/enc-string.ts @@ -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; + +/** + * 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; diff --git a/libs/key-management/src/abstractions/key.service.ts b/libs/key-management/src/abstractions/key.service.ts index 991fb92b544..9573e093279 100644 --- a/libs/key-management/src/abstractions/key.service.ts +++ b/libs/key-management/src/abstractions/key.service.ts @@ -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; /** * 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; /** * 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. diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index eed5a529204..dc7b90ce177 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -235,6 +235,11 @@ export class DefaultKeyService implements KeyServiceAbstraction { return this.buildProtectedSymmetricKey(masterKey, newUserKey); } + async makeUserKeyV1(): Promise { + 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