1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-10 05:30:01 +00:00

Update apis

This commit is contained in:
Bernd Schoolmann
2025-03-13 13:32:48 +01:00
parent 28079dfbac
commit 71546055d3
13 changed files with 113 additions and 70 deletions

View File

@@ -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({

View File

@@ -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");
}
}

View File

@@ -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<RegistrationStartResponse> {
async registrationStart(request: RegistrationStartRequest): Promise<RegistrationStartResponse> {
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<RegistrationFinishResponse> {
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");
}
}

View File

@@ -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<void> {
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<UserKey> {
async login(masterPassword: string, ksfConfig: KsfConfig): Promise<Uint8Array> {
throw new Error("Method not implemented.");
}
}

View File

@@ -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;
};

View File

@@ -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,
) {}
}

View File

@@ -1,6 +1,6 @@
export class LoginStartRequest {
constructor(
readonly email: string,
readonly clientLoginStartRequest: string,
readonly credentialRequest: string,
) {}
}

View File

@@ -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");
}
}

View File

@@ -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,
) {}
}

View File

@@ -2,7 +2,7 @@ import { CipherConfiguration } from "./cipher-configuration";
export class RegistrationStartRequest {
constructor(
readonly clientRegistrationStartResult: string,
readonly registrationRequest: string,
readonly cipherConfiguration: CipherConfiguration,
) {}
}

View File

@@ -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");
}
}

View File

@@ -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<RegistrationStartResponse>;
abstract RegistrationFinish(
sessionId: OpaqueSessionId,
abstract registrationStart(request: RegistrationStartRequest): Promise<RegistrationStartResponse>;
abstract registrationFinish(
request: RegistrationFinishRequest,
): Promise<RegistrationFinishResponse>;
abstract LoginStart(): any;
abstract LoginFinish(): any;
abstract loginStart(): any;
abstract loginFinish(): any;
}

View File

@@ -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<void>;
abstract register(masterPassword: string, userKey: UserKey, ksfConfig: KsfConfig): Promise<void>;
/**
* 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<UserKey>;
abstract login(masterPassword: string, ksfConfig: KsfConfig): Promise<Uint8Array>;
}