From b2d949dd1cf583942924b1e3b33ff1ba48ca6ce7 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 14 Mar 2025 16:22:36 +0100 Subject: [PATCH] Update opaque login with password --- .../settings/change-password.component.ts | 20 +++---- .../auth/models/request/password.request.ts | 1 + .../src/auth/opaque/default-opaque.service.ts | 19 +++++-- .../opaque/models/cipher-configuration.ts | 53 +++++++------------ libs/common/src/auth/opaque/opaque.service.ts | 16 ++++-- 5 files changed, 59 insertions(+), 50 deletions(-) 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 96f57e96bd8..082dc3de49b 100644 --- a/apps/web/src/app/auth/settings/change-password.component.ts +++ b/apps/web/src/app/auth/settings/change-password.component.ts @@ -215,7 +215,6 @@ export class ChangePasswordComponent try { if (this.rotateUserKey) { - throw new Error("Userkey rotation not supported"); this.formPromise = this.apiService.postPassword(request).then(async () => { // we need to save this for local masterkey verification during rotation await this.masterPasswordService.setMasterKeyHash(newLocalKeyHash, userId as UserId); @@ -223,18 +222,21 @@ export class ChangePasswordComponent return this.updateKey(); }); } else { + const sessionId = await this.opaqueService.register(this.masterPassword, newUserKey[0], { + memory: 256 * 1024, + iterations: 3, + parallelism: 4, + }); + request.opaqueSessionId = sessionId; this.formPromise = this.apiService.postPassword(request); } - await this.opaqueService.register(this.masterPassword, newUserKey[0], { - algorithm: "argon2id", - parameters: { memory: 256 * 1024, iterations: 3, parallelism: 4 }, - }); - await this.opaqueService.login(this.email, this.masterPassword, { - algorithm: "argon2id", - parameters: { memory: 256 * 1024, iterations: 3, parallelism: 4 }, - }); await this.formPromise; + await this.opaqueService.login(this.email, this.masterPassword, { + memory: 256 * 1024, + iterations: 3, + parallelism: 4, + }); this.toastService.showToast({ variant: "success", diff --git a/libs/common/src/auth/models/request/password.request.ts b/libs/common/src/auth/models/request/password.request.ts index 674754ff41a..072e3af1787 100644 --- a/libs/common/src/auth/models/request/password.request.ts +++ b/libs/common/src/auth/models/request/password.request.ts @@ -6,4 +6,5 @@ export class PasswordRequest extends SecretVerificationRequest { newMasterPasswordHash: string; masterPasswordHint: string; key: string; + opaqueSessionId: string; } diff --git a/libs/common/src/auth/opaque/default-opaque.service.ts b/libs/common/src/auth/opaque/default-opaque.service.ts index e0be57cebfc..ee1fdaf954b 100644 --- a/libs/common/src/auth/opaque/default-opaque.service.ts +++ b/libs/common/src/auth/opaque/default-opaque.service.ts @@ -4,10 +4,11 @@ import { RotateableKeySet } from "@bitwarden/auth/common"; import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { OpaqueSessionId } from "@bitwarden/common/types/guid"; import { UserKey } from "../../types/key"; -import { CipherConfiguration, KsfConfig } from "./models/cipher-configuration"; +import { Argon2IdParameters, 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"; @@ -21,8 +22,12 @@ export class DefaultOpaqueService implements OpaqueService { private sdkService: SdkService, ) {} - async register(masterPassword: string, userKey: UserKey, ksfConfig: KsfConfig): Promise { - const config = new CipherConfiguration(ksfConfig); + async register( + masterPassword: string, + userKey: UserKey, + ksfParameters: Argon2IdParameters, + ): Promise { + const config = new CipherConfiguration(ksfParameters); const cryptoClient = (await firstValueFrom(this.sdkService.client$)).crypto(); const registrationStart = cryptoClient.opaque_register_start( @@ -60,9 +65,15 @@ export class DefaultOpaqueService implements OpaqueService { keyset, ), ); + + return registrationStartResponse.sessionId; } - async login(email: string, masterPassword: string, ksfConfig: KsfConfig): Promise { + async login( + email: string, + masterPassword: string, + ksfConfig: Argon2IdParameters, + ): Promise { const config = new CipherConfiguration(ksfConfig); const cryptoClient = (await firstValueFrom(this.sdkService.client$)).crypto(); diff --git a/libs/common/src/auth/opaque/models/cipher-configuration.ts b/libs/common/src/auth/opaque/models/cipher-configuration.ts index dc362ce7d6f..db91ab2febb 100644 --- a/libs/common/src/auth/opaque/models/cipher-configuration.ts +++ b/libs/common/src/auth/opaque/models/cipher-configuration.ts @@ -1,36 +1,34 @@ import { CipherConfiguration as CipherConfigurationSdk } from "@bitwarden/sdk-internal"; -export type OpaqueKeVersion = 3; +export type CipherSuite = OPAQUEKE3_RISTRETTO255_3DH_ARGON2ID13_SUITE; +export type OPAQUEKE3_RISTRETTO255_3DH_ARGON2ID13_SUITE = + "OPAQUE_3_RISTRETTO255_OPRF_RISTRETTO255_KEGROUP_3DH_KEX_ARGON2ID13_KSF"; export class CipherConfiguration { - opaqueVersion: OpaqueKeVersion; + cipherSuite: CipherSuite; + argon2Parameters: Argon2IdParameters; - oprfCs: OprfCs; - keGroup: KeGroup; - keyExchange: KeyExchange; - ksf: KsfConfig; - - constructor(ksf: KsfConfig) { - this.opaqueVersion = 3; - this.oprfCs = "ristretto255"; - this.keGroup = "ristretto255"; - this.keyExchange = "triple-dh"; - this.ksf = ksf; + // only support one ciphersuite for now + constructor(ksf: Argon2IdParameters) { + this.cipherSuite = "OPAQUE_3_RISTRETTO255_OPRF_RISTRETTO255_KEGROUP_3DH_KEX_ARGON2ID13_KSF"; + this.argon2Parameters = ksf; } toSdkConfig(): CipherConfigurationSdk { - if (this.ksf.algorithm !== "argon2id") { - throw new Error("Unsupported KSF algorithm"); + if ( + this.cipherSuite !== "OPAQUE_3_RISTRETTO255_OPRF_RISTRETTO255_KEGROUP_3DH_KEX_ARGON2ID13_KSF" + ) { + throw new Error("Unsupported cipher suite"); } else { return { - oprf_cs: this.oprfCs, - ke_group: this.keGroup, - key_exchange: this.keyExchange, + oprf_cs: "ristretto255", + ke_group: "ristretto255", + key_exchange: "triple-dh", ksf: { argon2id: [ - this.ksf.parameters.memory, - this.ksf.parameters.iterations, - this.ksf.parameters.parallelism, + this.argon2Parameters.memory, + this.argon2Parameters.iterations, + this.argon2Parameters.parallelism, ], }, }; @@ -38,22 +36,9 @@ export class CipherConfiguration { } } -export type OprfCs = "ristretto255"; -export type KeGroup = "ristretto255"; -export type KeyExchange = "triple-dh"; - export type Argon2IdParameters = { // Memory in KiB memory: number; iterations: number; parallelism: number; }; - -export type KsfParameters = Argon2IdParameters; - -type KsfAlgorithm = "argon2id"; - -export type KsfConfig = { - algorithm: KsfAlgorithm; - parameters: KsfParameters; -}; diff --git a/libs/common/src/auth/opaque/opaque.service.ts b/libs/common/src/auth/opaque/opaque.service.ts index ae964e9650e..0498148ff00 100644 --- a/libs/common/src/auth/opaque/opaque.service.ts +++ b/libs/common/src/auth/opaque/opaque.service.ts @@ -1,17 +1,27 @@ +import { OpaqueSessionId } from "@bitwarden/common/types/guid"; + import { UserKey } from "../../types/key"; -import { KsfConfig } from "./models/cipher-configuration"; +import { Argon2IdParameters } from "./models/cipher-configuration"; export abstract class OpaqueService { /** * Register a user to use the Opaque login method. */ - abstract register(masterPassword: string, userKey: UserKey, ksfConfig: KsfConfig): Promise; + abstract register( + masterPassword: string, + userKey: UserKey, + ksfParameters: Argon2IdParameters, + ): Promise; /** * Authenticate using the Opaque login method. Returns the export key, which must be used * in combination with the rotateable keyset returned from the token endpoint. * @returns The ExportKey obtained during the Opaque login flow. */ - abstract login(email: string, masterPassword: string, ksfConfig: KsfConfig): Promise; + abstract login( + email: string, + masterPassword: string, + ksfParameters: Argon2IdParameters, + ): Promise; }