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 8d04f1d07bc..1f962af670d 100644 --- a/apps/web/src/app/auth/settings/change-password.component.ts +++ b/apps/web/src/app/auth/settings/change-password.component.ts @@ -226,7 +226,10 @@ export class ChangePasswordComponent this.formPromise = this.apiService.postPassword(request); } - await this.opaqueService.Register(this.masterPassword, newUserKey[0]); + await this.opaqueService.register(this.masterPassword, newUserKey[0], { + algorithm: "argon2id", + parameters: { memory: 256 * 1024, iterations: 3, parallelism: 4 }, + }); await this.formPromise; this.toastService.showToast({ diff --git a/libs/common/src/auth/models/response/prelogin.response.ts b/libs/common/src/auth/models/response/prelogin.response.ts index b5ca78c3b79..b54cda98073 100644 --- a/libs/common/src/auth/models/response/prelogin.response.ts +++ b/libs/common/src/auth/models/response/prelogin.response.ts @@ -1,6 +1,7 @@ import { KdfType } from "@bitwarden/key-management"; import { BaseResponse } from "../../../models/response/base.response"; +import { CipherConfiguration } from "../../opaque/models/cipher-configuration"; export class PreloginResponse extends BaseResponse { kdf: KdfType; @@ -8,11 +9,14 @@ export class PreloginResponse extends BaseResponse { kdfMemory?: number; kdfParallelism?: number; + opaqueConfiguration?: CipherConfiguration; + constructor(response: any) { super(response); this.kdf = this.getResponseProperty("Kdf"); this.kdfIterations = this.getResponseProperty("KdfIterations"); this.kdfMemory = this.getResponseProperty("KdfMemory"); this.kdfParallelism = this.getResponseProperty("KdfParallelism"); + this.opaqueConfiguration = this.getResponseProperty("OpaqueConfiguration"); } } diff --git a/libs/common/src/auth/opaque/default-opaque-api.service.ts b/libs/common/src/auth/opaque/default-opaque-api.service.ts index 6355beaa3e6..8d2a0401232 100644 --- a/libs/common/src/auth/opaque/default-opaque-api.service.ts +++ b/libs/common/src/auth/opaque/default-opaque-api.service.ts @@ -3,8 +3,6 @@ import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { OpaqueSessionId } from "../../types/guid"; - import { RegistrationFinishRequest } from "./models/registration-finish.request"; import { RegistrationFinishResponse } from "./models/registration-finish.response"; import { RegistrationStartRequest } from "./models/registration-start.request"; @@ -17,7 +15,7 @@ export class DefaultOpaqueApiService implements OpaqueApiService { private environmentService: EnvironmentService, ) {} - async RegistrationStart(request: RegistrationStartRequest): Promise { + async registrationStart(request: RegistrationStartRequest): Promise { const env = await firstValueFrom(this.environmentService.environment$); const response = await this.apiService.send( "POST", @@ -30,8 +28,7 @@ export class DefaultOpaqueApiService implements OpaqueApiService { return new RegistrationStartResponse(response); } - async RegistrationFinish( - credentialId: OpaqueSessionId, + async registrationFinish( request: RegistrationFinishRequest, ): Promise { const env = await firstValueFrom(this.environmentService.environment$); @@ -46,10 +43,10 @@ export class DefaultOpaqueApiService implements OpaqueApiService { return new RegistrationFinishResponse(response); } - LoginStart(): any { + loginStart(): any { throw new Error("Method not implemented"); } - LoginFinish(): any { + loginFinish(): any { throw new Error("Method not implemented"); } } diff --git a/libs/common/src/auth/opaque/default-opaque.service.ts b/libs/common/src/auth/opaque/default-opaque.service.ts index 8fdfdddbf31..dd537f0842f 100644 --- a/libs/common/src/auth/opaque/default-opaque.service.ts +++ b/libs/common/src/auth/opaque/default-opaque.service.ts @@ -4,72 +4,63 @@ 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 { Argon2KdfConfig } from "@bitwarden/key-management"; -import { Argon2Id, KeGroup, KeyExchange, OprfCS } from "@bitwarden/sdk-internal"; import { UserKey } from "../../types/key"; -import { CipherConfiguration } from "./models/cipher-configuration"; +import { CipherConfiguration, KsfConfig } from "./models/cipher-configuration"; import { RegistrationFinishRequest } from "./models/registration-finish.request"; import { RegistrationStartRequest } from "./models/registration-start.request"; import { OpaqueApiService } from "./opaque-api.service"; import { OpaqueService } from "./opaque.service"; -// static argon2 config for now -const cipherConfiguration = { - oprf: "ristretto255" as OprfCS, - ke_group: "ristretto255" as KeGroup, - key_exchange: "triple-dh" as KeyExchange, - ksf: { - t_cost: 3, - m_cost: 256 * 1024, - p_cost: 4, - } as Argon2Id, -}; -const kdfConfig = new Argon2KdfConfig(3, 256, 4); - export class DefaultOpaqueService implements OpaqueService { constructor( private opaqueApiService: OpaqueApiService, private sdkService: SdkService, ) {} - async Register(masterPassword: string, userKey: UserKey) { + async register(masterPassword: string, userKey: UserKey, ksfConfig: KsfConfig): Promise { + const config = new CipherConfiguration(ksfConfig); const cryptoClient = (await firstValueFrom(this.sdkService.client$)).crypto(); const registrationStart = cryptoClient.opaque_register_start( - Utils.fromUtf8ToArray(masterPassword), + masterPassword, + config.toSdkConfig(), ); - const registrationStartResponse = await this.opaqueApiService.RegistrationStart( + const registrationStartResponse = await this.opaqueApiService.registrationStart( new RegistrationStartRequest( - Utils.fromBufferToB64(new Uint8Array(registrationStart.registration_start_message)), - new CipherConfiguration(kdfConfig), + Utils.fromBufferToB64(registrationStart.registration_request), + config, ), ); const registrationFinish = cryptoClient.opaque_register_finish( - new Uint8Array(registrationStart.registration_start_state), - Utils.fromB64ToArray(registrationStartResponse.serverRegistrationStartResult), - Utils.fromUtf8ToArray(masterPassword), - cipherConfiguration, + masterPassword, + config.toSdkConfig(), + registrationStart.state, + Utils.fromB64ToArray(registrationStartResponse.registrationResponse), + ); + + const sdkKeyset = cryptoClient.create_rotateablekeyset_from_exportkey( + registrationFinish.export_key, userKey.key, ); const keyset = new RotateableKeySet( - new EncString(registrationFinish.keyset.encapsulated_key), - new EncString(registrationFinish.keyset.public_key), - new EncString(registrationFinish.keyset.private_key), + new EncString(sdkKeyset.encapsulated_key), + new EncString(sdkKeyset.public_key), + new EncString(sdkKeyset.private_key), ); - await this.opaqueApiService.RegistrationFinish( - registrationStartResponse.sessionId, + await this.opaqueApiService.registrationFinish( new RegistrationFinishRequest( - Utils.fromBufferToB64(new Uint8Array(registrationFinish.registration_finish_message)), + registrationStartResponse.sessionId, + Utils.fromBufferToB64(registrationFinish.registration_upload), keyset, ), ); } - async Login(masterPassword: string): Promise { + async login(masterPassword: string, ksfConfig: KsfConfig): Promise { throw new Error("Method not implemented."); } } diff --git a/libs/common/src/auth/opaque/models/cipher-configuration.ts b/libs/common/src/auth/opaque/models/cipher-configuration.ts index 7c6d07981d7..dc362ce7d6f 100644 --- a/libs/common/src/auth/opaque/models/cipher-configuration.ts +++ b/libs/common/src/auth/opaque/models/cipher-configuration.ts @@ -1,13 +1,59 @@ -import { KdfConfig } from "../../../../../key-management/src"; +import { CipherConfiguration as CipherConfigurationSdk } from "@bitwarden/sdk-internal"; + +export type OpaqueKeVersion = 3; export class CipherConfiguration { - opaqueVersion = 1; // TODO: what's the current version? - kdf: KdfConfig; - oprf = "ristretto-255"; - ke = "ristretto-255"; - keyExchange = "triple-diffie-helmen"; + opaqueVersion: OpaqueKeVersion; - constructor(kdf: KdfConfig) { - this.kdf = kdf; + 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; + } + + toSdkConfig(): CipherConfigurationSdk { + if (this.ksf.algorithm !== "argon2id") { + throw new Error("Unsupported KSF algorithm"); + } else { + return { + oprf_cs: this.oprfCs, + ke_group: this.keGroup, + key_exchange: this.keyExchange, + ksf: { + argon2id: [ + this.ksf.parameters.memory, + this.ksf.parameters.iterations, + this.ksf.parameters.parallelism, + ], + }, + }; + } } } + +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/models/login-finish.request.ts b/libs/common/src/auth/opaque/models/login-finish.request.ts index f8654ed50fe..784382d8f29 100644 --- a/libs/common/src/auth/opaque/models/login-finish.request.ts +++ b/libs/common/src/auth/opaque/models/login-finish.request.ts @@ -2,7 +2,7 @@ import { OpaqueSessionId } from "@bitwarden/common/types/guid"; export class LoginFinishRequest { constructor( - readonly loginSessionId: OpaqueSessionId, - readonly clientLoginFinishResult: string, + readonly sessionId: OpaqueSessionId, + readonly credentialFinalization: string, ) {} } diff --git a/libs/common/src/auth/opaque/models/login-start.request.ts b/libs/common/src/auth/opaque/models/login-start.request.ts index 8247b20a066..c83b96ee4a7 100644 --- a/libs/common/src/auth/opaque/models/login-start.request.ts +++ b/libs/common/src/auth/opaque/models/login-start.request.ts @@ -1,6 +1,6 @@ export class LoginStartRequest { constructor( readonly email: string, - readonly clientLoginStartRequest: string, + readonly credentialRequest: string, ) {} } diff --git a/libs/common/src/auth/opaque/models/login-start.response.ts b/libs/common/src/auth/opaque/models/login-start.response.ts index dfc88ed6ee8..10ef2a80513 100644 --- a/libs/common/src/auth/opaque/models/login-start.response.ts +++ b/libs/common/src/auth/opaque/models/login-start.response.ts @@ -1,12 +1,12 @@ import { BaseResponse } from "../../../models/response/base.response"; export class LoginStartResponse extends BaseResponse { - loginSessionId: string; - serverLoginStartResult: string; + sessionId: string; + credentialResponse: string; constructor(response: any) { super(response); - this.loginSessionId = this.getResponseProperty("LoginSessionId"); - this.serverLoginStartResult = this.getResponseProperty("ServerRegistrationStartResult"); + this.sessionId = this.getResponseProperty("SessionId"); + this.credentialResponse = this.getResponseProperty("CredentialResponse"); } } diff --git a/libs/common/src/auth/opaque/models/registration-finish.request.ts b/libs/common/src/auth/opaque/models/registration-finish.request.ts index 8e4c12dfee9..2c417f8dd8a 100644 --- a/libs/common/src/auth/opaque/models/registration-finish.request.ts +++ b/libs/common/src/auth/opaque/models/registration-finish.request.ts @@ -1,8 +1,10 @@ import { RotateableKeySet } from "@bitwarden/auth/common"; +import { OpaqueSessionId } from "@bitwarden/common/types/guid"; export class RegistrationFinishRequest { constructor( - readonly clientRegistrationFinishResult: string, + readonly sessionId: OpaqueSessionId, + readonly registrationUpload: string, readonly keySet: RotateableKeySet, ) {} } diff --git a/libs/common/src/auth/opaque/models/registration-start.request.ts b/libs/common/src/auth/opaque/models/registration-start.request.ts index 538855c9445..c9cf06881c9 100644 --- a/libs/common/src/auth/opaque/models/registration-start.request.ts +++ b/libs/common/src/auth/opaque/models/registration-start.request.ts @@ -2,7 +2,7 @@ import { CipherConfiguration } from "./cipher-configuration"; export class RegistrationStartRequest { constructor( - readonly clientRegistrationStartResult: string, + readonly registrationRequest: string, readonly cipherConfiguration: CipherConfiguration, ) {} } diff --git a/libs/common/src/auth/opaque/models/registration-start.response.ts b/libs/common/src/auth/opaque/models/registration-start.response.ts index fbf5bec4f0a..2087888c9ec 100644 --- a/libs/common/src/auth/opaque/models/registration-start.response.ts +++ b/libs/common/src/auth/opaque/models/registration-start.response.ts @@ -3,12 +3,12 @@ import { OpaqueSessionId } from "../../../types/guid"; export class RegistrationStartResponse extends BaseResponse { sessionId: OpaqueSessionId; - serverRegistrationStartResult: string; + registrationResponse: string; constructor(response: any) { super(response); this.sessionId = this.getResponseProperty("SessionId"); - this.serverRegistrationStartResult = this.getResponseProperty("ServerRegistrationStartResult"); + this.registrationResponse = this.getResponseProperty("RegistrationResponse"); } } diff --git a/libs/common/src/auth/opaque/opaque-api.service.ts b/libs/common/src/auth/opaque/opaque-api.service.ts index c6c2aaf0ca5..ee153db3353 100644 --- a/libs/common/src/auth/opaque/opaque-api.service.ts +++ b/libs/common/src/auth/opaque/opaque-api.service.ts @@ -1,16 +1,13 @@ -import { OpaqueSessionId } from "../../types/guid"; - import { RegistrationFinishRequest } from "./models/registration-finish.request"; import { RegistrationFinishResponse } from "./models/registration-finish.response"; import { RegistrationStartRequest } from "./models/registration-start.request"; import { RegistrationStartResponse } from "./models/registration-start.response"; export abstract class OpaqueApiService { - abstract RegistrationStart(request: RegistrationStartRequest): Promise; - abstract RegistrationFinish( - sessionId: OpaqueSessionId, + abstract registrationStart(request: RegistrationStartRequest): Promise; + abstract registrationFinish( request: RegistrationFinishRequest, ): Promise; - abstract LoginStart(): any; - abstract LoginFinish(): any; + abstract loginStart(): any; + abstract loginFinish(): any; } diff --git a/libs/common/src/auth/opaque/opaque.service.ts b/libs/common/src/auth/opaque/opaque.service.ts index c7ee949c571..13fc88ff3f8 100644 --- a/libs/common/src/auth/opaque/opaque.service.ts +++ b/libs/common/src/auth/opaque/opaque.service.ts @@ -1,14 +1,17 @@ import { UserKey } from "../../types/key"; +import { KsfConfig } from "./models/cipher-configuration"; + export abstract class OpaqueService { /** * Register a user to use the Opaque login method. */ - abstract Register(masterPassword: string, userKey: UserKey): Promise; + abstract register(masterPassword: string, userKey: UserKey, ksfConfig: KsfConfig): Promise; /** - * Authenticate using the Opaque login method. - * @returns The UserKey obtained during the Opaque login flow. + * 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(masterPassword: string): Promise; + abstract login(masterPassword: string, ksfConfig: KsfConfig): Promise; }