diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index b7f423e8ff7..81c98393434 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -36,6 +36,8 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { MasterPasswordApiService as MasterPasswordApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; +import { DefaultOpaqueKeyExchangeService } from "@bitwarden/common/auth/opaque/default-opaque-key-exchange.service"; +import { OpaqueKeyExchangeApiService } from "@bitwarden/common/auth/opaque/opaque-key-exchange-api.service"; import { AccountServiceImplementation, getUserId, @@ -44,6 +46,7 @@ import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { AvatarService } from "@bitwarden/common/auth/services/avatar.service"; import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; import { MasterPasswordApiService } from "@bitwarden/common/auth/services/master-password/master-password-api.service.implementation"; +import { PrePasswordLoginApiService } from "@bitwarden/common/auth/services/pre-password-login-api.service"; import { TokenService } from "@bitwarden/common/auth/services/token.service"; import { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.service"; import { UserVerificationApiService } from "@bitwarden/common/auth/services/user-verification/user-verification-api.service"; @@ -640,6 +643,19 @@ export class ServiceContainer { this.configService, ); + const opaqueKeyExchangeApiService = new OpaqueKeyExchangeApiService( + this.apiService, + this.environmentService, + ); + const opaqueKeyExchangeService = new DefaultOpaqueKeyExchangeService( + opaqueKeyExchangeApiService, + this.sdkService, + ); + const prePasswordLoginApiService = new PrePasswordLoginApiService( + this.apiService, + this.environmentService, + ); + this.loginStrategyService = new LoginStrategyService( this.accountService, this.masterPasswordService, @@ -666,6 +682,9 @@ export class ServiceContainer { this.vaultTimeoutSettingsService, this.kdfConfigService, this.taskSchedulerService, + prePasswordLoginApiService, + this.configService, + opaqueKeyExchangeService, ); // FIXME: CLI does not support autofill diff --git a/apps/web/src/app/auth/settings/change-password.component.ts b/apps/web/src/app/auth/settings/change-password.component.ts index c853dbdf95b..e07dac5971b 100644 --- a/apps/web/src/app/auth/settings/change-password.component.ts +++ b/apps/web/src/app/auth/settings/change-password.component.ts @@ -11,6 +11,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; +import { CipherConfiguration } from "@bitwarden/common/auth/opaque/models/cipher-configuration"; import { OpaqueKeyExchangeService } from "@bitwarden/common/auth/opaque/opaque-key-exchange.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; @@ -24,7 +25,12 @@ import { MasterKey, UserKey } from "@bitwarden/common/types/key"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { DialogService, ToastService } from "@bitwarden/components"; -import { Argon2KdfConfig, KdfConfigService, KdfType, KeyService } from "@bitwarden/key-management"; +import { + DEFAULT_OPAQUE_KDF_CONFIG, + KdfConfigService, + KdfType, + KeyService, +} from "@bitwarden/key-management"; import { UserKeyRotationService } from "../../key-management/key-rotation/user-key-rotation.service"; @@ -222,28 +228,22 @@ export class ChangePasswordComponent return this.updateKey(); }); } else { - // PBKDF2 is not recommended for opaque, so force use of Argon2 with default params if the user is using PBKDF2. const userConfiguredKdf = await this.kdfConfigService.getKdfConfig(); - const opaqueKdf = + const cipherConfig = CipherConfiguration.fromKdfConfig( userConfiguredKdf.kdfType === KdfType.Argon2id ? userConfiguredKdf - : new Argon2KdfConfig(); + : DEFAULT_OPAQUE_KDF_CONFIG, + ); const sessionId = await this.opaqueKeyExchangeService.register( this.masterPassword, newUserKey[0], - opaqueKdf, + cipherConfig, ); request.opaqueSessionId = sessionId; this.formPromise = this.masterPasswordApiService.postPassword(request); } - // TODO: remove this test code - const userConfiguredKdf = await this.kdfConfigService.getKdfConfig(); - const opaqueKdf = - userConfiguredKdf.kdfType === KdfType.Argon2id ? userConfiguredKdf : new Argon2KdfConfig(); - await this.opaqueKeyExchangeService.register(this.masterPassword, newUserKey[0], opaqueKdf); - this.toastService.showToast({ variant: "success", title: this.i18nService.t("masterPasswordChanged"), diff --git a/libs/auth/src/common/login-strategies/password-login.strategy.ts b/libs/auth/src/common/login-strategies/password-login.strategy.ts index a04f7f9a99b..47865228b93 100644 --- a/libs/auth/src/common/login-strategies/password-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/password-login.strategy.ts @@ -13,6 +13,7 @@ import { IdentityCaptchaResponse } from "@bitwarden/common/auth/models/response/ import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; +import { CipherConfiguration } from "@bitwarden/common/auth/opaque/models/cipher-configuration"; import { OpaqueKeyExchangeService } from "@bitwarden/common/auth/opaque/opaque-key-exchange.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -21,7 +22,7 @@ import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/sym import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { UserId } from "@bitwarden/common/types/guid"; import { MasterKey } from "@bitwarden/common/types/key"; -import { Argon2KdfConfig, KdfType } from "@bitwarden/key-management"; +import { DEFAULT_OPAQUE_KDF_CONFIG, KdfType } from "@bitwarden/key-management"; import { PasswordHashLoginCredentials } from "../models/domain/login-credentials"; import { CacheData } from "../services/login-strategies/login-strategy.state"; @@ -295,11 +296,14 @@ export class PasswordLoginStrategy extends BaseLoginStrategy { return; } - const opaqueKdf = - userConfiguredKdf.kdfType === KdfType.Argon2id ? userConfiguredKdf : new Argon2KdfConfig(); + const cipherConfig = CipherConfiguration.fromKdfConfig( + userConfiguredKdf.kdfType === KdfType.Argon2id + ? userConfiguredKdf + : DEFAULT_OPAQUE_KDF_CONFIG, + ); try { - await this.opaqueKeyExchangeService.register(masterPassword, userKey, opaqueKdf); + await this.opaqueKeyExchangeService.register(masterPassword, userKey, cipherConfig); } catch (error) { // If this process fails for any reason, we don't want to stop the login process // so just log the error and continue. diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts index d02338eafff..2e73404b893 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts @@ -11,6 +11,7 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; import { PrePasswordLoginResponse } from "@bitwarden/common/auth/models/response/pre-password-login.response"; +import { OpaqueKeyExchangeService } from "@bitwarden/common/auth/opaque/opaque-key-exchange.service"; import { PrePasswordLoginApiService } from "@bitwarden/common/auth/services/pre-password-login-api.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; @@ -22,6 +23,7 @@ import { VaultTimeoutSettingsService, } from "@bitwarden/common/key-management/vault-timeout"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -48,8 +50,6 @@ import { UserDecryptionOptionsService } from "../user-decryption-options/user-de import { LoginStrategyService } from "./login-strategy.service"; import { CACHE_EXPIRATION_KEY } from "./login-strategy.state"; -import { OpaqueKeyExchangeService } from "@bitwarden/common/auth/opaque/opaque-key-exchange.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; // TODO: update tests to pass // TODO: test makePrePasswordLoginMasterKey diff --git a/libs/common/src/auth/opaque/default-opaque-key-exchange.service.ts b/libs/common/src/auth/opaque/default-opaque-key-exchange.service.ts index 2badddadaf5..1769d85018e 100644 --- a/libs/common/src/auth/opaque/default-opaque-key-exchange.service.ts +++ b/libs/common/src/auth/opaque/default-opaque-key-exchange.service.ts @@ -8,7 +8,7 @@ import { OpaqueSessionId } from "@bitwarden/common/types/guid"; import { UserKey } from "../../types/key"; -import { Argon2IdParameters, CipherConfiguration } from "./models/cipher-configuration"; +import { CipherConfiguration } from "./models/cipher-configuration"; import { LoginFinishRequest } from "./models/login-finish.request"; import { LoginStartRequest } from "./models/login-start.request"; import { RegistrationFinishRequest } from "./models/registration-finish.request"; @@ -25,15 +25,14 @@ export class DefaultOpaqueKeyExchangeService implements OpaqueKeyExchangeService async register( masterPassword: string, userKey: UserKey, - keyStretchingFuncArgon2Params: Argon2IdParameters, // TODO: eval if we can use KdfConfig existing type + cipherConfig: CipherConfiguration, ): Promise { - if (!masterPassword || !userKey || !keyStretchingFuncArgon2Params) { + if (!masterPassword || !userKey || !cipherConfig) { throw new Error( - `Unable to register user with missing parameters. masterPassword exists: ${!!masterPassword}, userKey exists: ${!!userKey}, keyStretchingFuncArgon2Params exists: ${!!keyStretchingFuncArgon2Params}`, + `Unable to register user with missing parameters. masterPassword exists: ${!!masterPassword}, userKey exists: ${!!userKey}, cipherConfig exists: ${!!cipherConfig}`, ); } - const cipherConfig = new CipherConfiguration(keyStretchingFuncArgon2Params); const cryptoClient = (await firstValueFrom(this.sdkService.client$)).crypto(); const registrationStart = cryptoClient.opaque_register_start( @@ -81,15 +80,14 @@ export class DefaultOpaqueKeyExchangeService implements OpaqueKeyExchangeService async login( email: string, masterPassword: string, - keyStretchingFuncArgon2Params: Argon2IdParameters, + cipherConfig: CipherConfiguration, ): Promise { - if (!email || !masterPassword || !keyStretchingFuncArgon2Params) { + if (!email || !masterPassword || !cipherConfig) { throw new Error( - `Unable to log in user with missing parameters. email exists: ${!!email}; masterPassword exists: ${!!masterPassword}; keyStretchingFuncArgon2Params exists: ${!!keyStretchingFuncArgon2Params}`, + `Unable to log in user with missing parameters. email exists: ${!!email}; masterPassword exists: ${!!masterPassword}; cipherConfig exists: ${!!cipherConfig}`, ); } - const cipherConfig = new CipherConfiguration(keyStretchingFuncArgon2Params); const cryptoClient = (await firstValueFrom(this.sdkService.client$)).crypto(); const loginStart = cryptoClient.opaque_login_start(masterPassword, cipherConfig.toSdkConfig()); diff --git a/libs/common/src/auth/opaque/models/cipher-configuration.ts b/libs/common/src/auth/opaque/models/cipher-configuration.ts index d41298e2c72..89680d5de9f 100644 --- a/libs/common/src/auth/opaque/models/cipher-configuration.ts +++ b/libs/common/src/auth/opaque/models/cipher-configuration.ts @@ -1,3 +1,4 @@ +import { Argon2KdfConfig } from "@bitwarden/key-management"; import { CipherConfiguration as CipherConfigurationSdk } from "@bitwarden/sdk-internal"; // TODO: add js docs to all types / classes here. @@ -16,6 +17,21 @@ export class CipherConfiguration { this.argon2Parameters = ksf; } + /** + * Converts from Bitwarden KDF configs to OPAQUE KSF configs. + * + * @param kdfConfig - Bitwarden KDF config + * @returns OPAQUE KSF config + */ + static fromKdfConfig(kdfConfig: Argon2KdfConfig): CipherConfiguration { + return new CipherConfiguration({ + // convert MiB to KiB + memory: kdfConfig.memory * 1024, + iterations: kdfConfig.iterations, + parallelism: kdfConfig.parallelism, + }); + } + toSdkConfig(): CipherConfigurationSdk { if ( this.cipherSuite !== "OPAQUE_3_RISTRETTO255_OPRF_RISTRETTO255_KEGROUP_3DH_KEX_ARGON2ID13_KSF" diff --git a/libs/common/src/auth/opaque/opaque-key-exchange.service.ts b/libs/common/src/auth/opaque/opaque-key-exchange.service.ts index 78bc2272450..ad6390625d0 100644 --- a/libs/common/src/auth/opaque/opaque-key-exchange.service.ts +++ b/libs/common/src/auth/opaque/opaque-key-exchange.service.ts @@ -2,7 +2,7 @@ import { OpaqueSessionId } from "@bitwarden/common/types/guid"; import { UserKey } from "../../types/key"; -import { Argon2IdParameters } from "./models/cipher-configuration"; +import { CipherConfiguration } from "./models/cipher-configuration"; export abstract class OpaqueKeyExchangeService { /** @@ -11,7 +11,7 @@ export abstract class OpaqueKeyExchangeService { abstract register( masterPassword: string, userKey: UserKey, - ksfParameters: Argon2IdParameters, + cipherConfiguration: CipherConfiguration, ): Promise; /** @@ -22,6 +22,6 @@ export abstract class OpaqueKeyExchangeService { abstract login( email: string, masterPassword: string, - ksfParameters: Argon2IdParameters, + cipherConfiguration: CipherConfiguration, ): Promise; } diff --git a/libs/key-management/src/index.ts b/libs/key-management/src/index.ts index d7a2d9c8fd5..ba430f1945e 100644 --- a/libs/key-management/src/index.ts +++ b/libs/key-management/src/index.ts @@ -16,6 +16,7 @@ export { KdfConfig, createKdfConfig, DEFAULT_KDF_CONFIG, + DEFAULT_OPAQUE_KDF_CONFIG, } from "./models/kdf-config"; export { KdfConfigService } from "./abstractions/kdf-config.service"; export { DefaultKdfConfigService } from "./kdf-config.service"; diff --git a/libs/key-management/src/models/kdf-config.ts b/libs/key-management/src/models/kdf-config.ts index e52255c5e12..98e29e7a300 100644 --- a/libs/key-management/src/models/kdf-config.ts +++ b/libs/key-management/src/models/kdf-config.ts @@ -141,3 +141,4 @@ export class Argon2KdfConfig { } export const DEFAULT_KDF_CONFIG = new PBKDF2KdfConfig(PBKDF2KdfConfig.ITERATIONS.defaultValue); +export const DEFAULT_OPAQUE_KDF_CONFIG = new Argon2KdfConfig(1, 256, 1);