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

Update opaque login with password

This commit is contained in:
Bernd Schoolmann
2025-03-14 16:22:36 +01:00
parent 3af12a2e15
commit b2d949dd1c
5 changed files with 59 additions and 50 deletions

View File

@@ -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",

View File

@@ -6,4 +6,5 @@ export class PasswordRequest extends SecretVerificationRequest {
newMasterPasswordHash: string;
masterPasswordHint: string;
key: string;
opaqueSessionId: string;
}

View File

@@ -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<void> {
const config = new CipherConfiguration(ksfConfig);
async register(
masterPassword: string,
userKey: UserKey,
ksfParameters: Argon2IdParameters,
): Promise<OpaqueSessionId> {
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<Uint8Array> {
async login(
email: string,
masterPassword: string,
ksfConfig: Argon2IdParameters,
): Promise<Uint8Array> {
const config = new CipherConfiguration(ksfConfig);
const cryptoClient = (await firstValueFrom(this.sdkService.client$)).crypto();

View File

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

View File

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