1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-26 17:43:22 +00:00

Innovation/opaque grant validator (#13918)

* Add grant validator

* Fix 2fa

* Set active endpoint
This commit is contained in:
Bernd Schoolmann
2025-03-20 15:13:02 +01:00
committed by GitHub
parent b6c2eb7d82
commit c84be3eb22
10 changed files with 72 additions and 12 deletions

View File

@@ -11,6 +11,7 @@ export class OpaqueTokenRequest extends TokenRequest {
constructor(
public email: string,
protected twoFactor: TokenTwoFactorRequest,
public sessionId: string,
device?: DeviceRequest,
public newDeviceOtp?: string,
) {
@@ -21,8 +22,9 @@ export class OpaqueTokenRequest extends TokenRequest {
const obj = super.toIdentityToken(clientId);
// TODO: what grant type for OPAQUE?
obj.grant_type = "password";
obj.grant_type = "opaque-ke";
obj.username = this.email;
obj.sessionId = this.sessionId;
if (this.newDeviceOtp) {
obj.newDeviceOtp = this.newDeviceOtp;

View File

@@ -13,6 +13,7 @@ import { LoginStartRequest } from "./models/login-start.request";
import { OpaqueCipherConfiguration } from "./models/opaque-cipher-configuration";
import { RegistrationFinishRequest } from "./models/registration-finish.request";
import { RegistrationStartRequest } from "./models/registration-start.request";
import { SetRegistrationActiveRequest } from "./models/set-registration-active.request";
import { OpaqueKeyExchangeApiService } from "./opaque-key-exchange-api.service";
import { OpaqueKeyExchangeService } from "./opaque-key-exchange.service";
@@ -74,6 +75,12 @@ export class DefaultOpaqueKeyExchangeService implements OpaqueKeyExchangeService
return registrationStartResponse.sessionId;
}
async setRegistrationActive(sessionId: OpaqueSessionId): Promise<void> {
await this.opaqueKeyExchangeApiService.setRegistrationActive(
new SetRegistrationActiveRequest(sessionId),
);
}
// TODO: we will likely have to break this apart to return the start / finish requests
// so that the opaque login strategy can send both to the identity token endpoint
// in separate calls.
@@ -81,7 +88,7 @@ export class DefaultOpaqueKeyExchangeService implements OpaqueKeyExchangeService
email: string,
masterPassword: string,
cipherConfig: OpaqueCipherConfiguration,
): Promise<Uint8Array> {
): Promise<{ sessionId: string; exportKey: Uint8Array }> {
if (!email || !masterPassword || !cipherConfig) {
throw new Error(
`Unable to log in user with missing parameters. email exists: ${!!email}; masterPassword exists: ${!!masterPassword}; cipherConfig exists: ${!!cipherConfig}`,
@@ -112,6 +119,6 @@ export class DefaultOpaqueKeyExchangeService implements OpaqueKeyExchangeService
throw new Error("Login failed");
}
return loginFinish.export_key;
return { sessionId: loginStartResponse.sessionId, exportKey: loginFinish.export_key };
}
}

View File

@@ -17,6 +17,16 @@ export class OpaqueCipherConfiguration {
this.argon2Parameters = ksf;
}
static fromAny(config: any): OpaqueCipherConfiguration {
if (
config.cipherSuite !==
"OPAQUE_3_RISTRETTO255_OPRF_RISTRETTO255_KEGROUP_3DH_KEX_ARGON2ID13_KSF"
) {
throw new Error("Unsupported cipher suite");
}
return new OpaqueCipherConfiguration(config.argon2Parameters);
}
/**
* Converts from Bitwarden KDF configs to OPAQUE KSF configs.
*

View File

@@ -0,0 +1,5 @@
import { OpaqueSessionId } from "@bitwarden/common/types/guid";
export class SetRegistrationActiveRequest {
constructor(readonly sessionId: OpaqueSessionId) {}
}

View File

@@ -10,6 +10,7 @@ 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";
import { SetRegistrationActiveRequest } from "./models/set-registration-active.request";
export class OpaqueKeyExchangeApiService {
constructor(
@@ -45,13 +46,25 @@ export class OpaqueKeyExchangeApiService {
return new RegistrationFinishResponse(response);
}
async setRegistrationActive(request: SetRegistrationActiveRequest): Promise<void> {
const env = await firstValueFrom(this.environmentService.environment$);
await this.apiService.send(
"POST",
`/opaque/set-registration-active`,
request,
true,
true,
env.getApiUrl(),
);
}
async loginStart(request: LoginStartRequest): Promise<LoginStartResponse> {
const env = await firstValueFrom(this.environmentService.environment$);
const response = await this.apiService.send(
"POST",
`/opaque/start-login`,
request,
true,
false,
true,
env.getApiUrl(),
);
@@ -64,10 +77,10 @@ export class OpaqueKeyExchangeApiService {
"POST",
`/opaque/finish-login`,
request,
true,
false,
true,
env.getApiUrl(),
);
return response.success;
return response == true;
}
}

View File

@@ -14,6 +14,11 @@ export abstract class OpaqueKeyExchangeService {
cipherConfiguration: OpaqueCipherConfiguration,
): Promise<OpaqueSessionId>;
/**
* Set the registration as the active authentication method for the user.
*/
abstract setRegistrationActive(sessionId: OpaqueSessionId): Promise<void>;
/**
* 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.
@@ -23,5 +28,8 @@ export abstract class OpaqueKeyExchangeService {
email: string,
masterPassword: string,
cipherConfiguration: OpaqueCipherConfiguration,
): Promise<Uint8Array>;
): Promise<{
sessionId: string;
exportKey: Uint8Array;
}>;
}