mirror of
https://github.com/bitwarden/browser
synced 2026-02-10 05:30:01 +00:00
Innovation/OPAQUE - Add and cleanup some TODOs (#13873)
* LoginStrategyServiceAbstraction - add TODO to refactor makePrePasswordLoginMasterKey in future * OpaqueLoginCredentials - add kdfConfig so we can derive master key for user verification scenarios. * LoginStrategyService.logIn - add TODO * OpaqueTokenRequest - add more docs * CipherConfiguration - add todo for more docs * DefaultOpaqueService - add todo * OpaqueLoginStrategy - (1) Add docs (2) clean up todos (3) add todos
This commit is contained in:
@@ -70,6 +70,9 @@ export abstract class LoginStrategyServiceAbstraction {
|
||||
// TODO: PM-15162 - deprecate captchaResponse
|
||||
captchaResponse: string,
|
||||
) => Promise<AuthResult>;
|
||||
|
||||
// TODO: PM-19273 - Refactor makePrePasswordLoginMasterKey to no longer be on the service
|
||||
// once PM-18176 removes the Recover2faComponent dependency.
|
||||
/**
|
||||
* Creates a master key from the provided master password and email.
|
||||
* If a KdfConfig is provided, it will be used to generate the key.
|
||||
|
||||
@@ -14,6 +14,7 @@ import { IdentityCaptchaResponse } from "@bitwarden/common/auth/models/response/
|
||||
import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response";
|
||||
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
|
||||
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
|
||||
import { CipherConfiguration } from "@bitwarden/common/auth/opaque/models/cipher-configuration";
|
||||
import { HashPurpose } from "@bitwarden/common/platform/enums";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||
@@ -38,6 +39,10 @@ export class OpaqueLoginStrategyData implements LoginStrategyData {
|
||||
/** The user's master key */
|
||||
masterKey: MasterKey;
|
||||
|
||||
/* The user's OPAQUE cipher configuration which controls
|
||||
the encryption schemes used during key derivation and key exchange */
|
||||
cipherConfiguration: CipherConfiguration;
|
||||
|
||||
/**
|
||||
* Tracks if the user needs to be forced to update their password
|
||||
*/
|
||||
@@ -52,10 +57,12 @@ export class OpaqueLoginStrategyData implements LoginStrategyData {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: link to RFC and give simple, brief explanation of the protocol
|
||||
/**
|
||||
*
|
||||
* A login strategy that uses the ...
|
||||
* A login strategy that uses the OPAQUE protocol for password authentication.
|
||||
* OPAQUE (Oblivious Pseudorandom Function (OPRF)-based Password Authentication and Key Exchange)
|
||||
* is a protocol that allows a client to authenticate to a server without revealing the password to the server.
|
||||
* RFC: https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-03.html
|
||||
*/
|
||||
export class OpaqueLoginStrategy extends BaseLoginStrategy {
|
||||
/** The email address of the user attempting to log in. */
|
||||
@@ -81,19 +88,20 @@ export class OpaqueLoginStrategy extends BaseLoginStrategy {
|
||||
this.localMasterKeyHash$ = this.cache.pipe(map((state) => state.localMasterKeyHash));
|
||||
}
|
||||
|
||||
// TODO: build OpaqueLoginCredentials
|
||||
override async logIn(credentials: OpaqueLoginCredentials) {
|
||||
const { email, masterPassword, twoFactor } = credentials;
|
||||
const { email, masterPassword, kdfConfig, cipherConfiguration, twoFactor } = credentials;
|
||||
|
||||
const data = new OpaqueLoginStrategyData();
|
||||
|
||||
// TODO: we will still generate a master key here but we need to extract the prelogin call out of the makePreloginKey
|
||||
// and simply rename it deriveMasterKey or something similar
|
||||
data.userEnteredEmail = email;
|
||||
|
||||
// Even though we are completing OPAQUE authN and not logging in with password hash,
|
||||
// we still need to hash the master password for logged in user verification scenarios.
|
||||
data.masterKey = await this.loginStrategyService.makePrePasswordLoginMasterKey(
|
||||
masterPassword,
|
||||
email,
|
||||
kdfConfig,
|
||||
);
|
||||
data.userEnteredEmail = email;
|
||||
|
||||
// Hash the password early (before authentication) so we don't persist it in memory in plaintext
|
||||
data.localMasterKeyHash = await this.keyService.hashMasterKey(
|
||||
@@ -102,12 +110,10 @@ export class OpaqueLoginStrategy extends BaseLoginStrategy {
|
||||
HashPurpose.LocalAuthorization,
|
||||
);
|
||||
|
||||
// const serverMasterKeyHash = await this.keyService.hashMasterKey(masterPassword, data.masterKey);
|
||||
data.cipherConfiguration = cipherConfiguration;
|
||||
|
||||
// TODO: we must figure out how we will handle 2FA at some point.
|
||||
data.tokenRequest = new OpaqueTokenRequest(
|
||||
email,
|
||||
undefined,
|
||||
await this.buildTwoFactor(twoFactor, email),
|
||||
await this.buildDeviceRequest(),
|
||||
);
|
||||
@@ -193,6 +199,7 @@ export class OpaqueLoginStrategy extends BaseLoginStrategy {
|
||||
// We still need this for local user verification scenarios
|
||||
await this.keyService.setMasterKeyEncryptedUserKey(response.key, userId);
|
||||
|
||||
// TODO: why not re-use master key from strategy data cache?
|
||||
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
||||
if (masterKey) {
|
||||
const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(
|
||||
|
||||
@@ -44,6 +44,7 @@ export class PasswordLoginCredentials {
|
||||
? new OpaqueLoginCredentials(
|
||||
this.email,
|
||||
this.masterPassword,
|
||||
preLoginResponse.toKdfConfig(),
|
||||
preLoginResponse.opaqueConfiguration,
|
||||
)
|
||||
: new PasswordHashLoginCredentials(
|
||||
@@ -158,6 +159,7 @@ export class OpaqueLoginCredentials {
|
||||
constructor(
|
||||
public email: string,
|
||||
public masterPassword: string,
|
||||
public kdfConfig: KdfConfig,
|
||||
public cipherConfiguration: CipherConfiguration,
|
||||
public twoFactor?: TokenTwoFactorRequest,
|
||||
) {}
|
||||
|
||||
@@ -226,6 +226,9 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction {
|
||||
// Password credentials may use the PasswordHashLoginStrategy or the OpaqueLoginStrategy
|
||||
if (credentials.type === AuthenticationType.Password) {
|
||||
const preLoginRequest = new PrePasswordLoginRequest(credentials.email);
|
||||
|
||||
// TODO: OPAQUE: we have to save off whether or not to enroll the user in OPAQUE based on the
|
||||
// response from the pre-password-login request and execute the enroll in the password login strategy
|
||||
const preLoginResponse =
|
||||
await this.prePasswordLoginApiService.postPrePasswordLogin(preLoginRequest);
|
||||
ownedCredentials = credentials.toSpecificLoginCredentials(preLoginResponse);
|
||||
|
||||
@@ -6,10 +6,10 @@ import { TokenTwoFactorRequest } from "./token-two-factor.request";
|
||||
import { TokenRequest } from "./token.request";
|
||||
|
||||
// TODO: we might have to support both login start and login finish requests within this?
|
||||
// or, we could have separate OpaqueStartTokenRequest and OpaqueFinishTokenRequest classes
|
||||
export class OpaqueTokenRequest extends TokenRequest {
|
||||
constructor(
|
||||
public email: string,
|
||||
public masterPasswordHash: string,
|
||||
protected twoFactor: TokenTwoFactorRequest,
|
||||
device?: DeviceRequest,
|
||||
public newDeviceOtp?: string,
|
||||
@@ -20,9 +20,9 @@ export class OpaqueTokenRequest extends TokenRequest {
|
||||
toIdentityToken(clientId: ClientType) {
|
||||
const obj = super.toIdentityToken(clientId);
|
||||
|
||||
// TODO: what grant type for OPAQUE?
|
||||
obj.grant_type = "password";
|
||||
obj.username = this.email;
|
||||
obj.password = this.masterPasswordHash;
|
||||
|
||||
if (this.newDeviceOtp) {
|
||||
obj.newDeviceOtp = this.newDeviceOtp;
|
||||
|
||||
@@ -69,6 +69,9 @@ export class DefaultOpaqueService implements OpaqueService {
|
||||
return registrationStartResponse.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.
|
||||
async login(
|
||||
email: string,
|
||||
masterPassword: string,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { CipherConfiguration as CipherConfigurationSdk } from "@bitwarden/sdk-internal";
|
||||
|
||||
// TODO: add js docs to all types / classes here.
|
||||
|
||||
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";
|
||||
|
||||
Reference in New Issue
Block a user