Browse Source

Merge 76392649d0 into d2660324e6

pull/7747/merge
Tom 2 days ago
committed by GitHub
parent
commit
19cbb18bab
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 41
      src/api/core/accounts.rs
  2. 26
      src/api/core/organizations.rs
  3. 4
      src/db/models/user.rs

41
src/api/core/accounts.rs

@ -93,6 +93,15 @@ pub struct KDFData {
kdf_parallelism: Option<i32>, kdf_parallelism: Option<i32>,
} }
impl KDFData {
pub(super) fn matches_user(&self, user: &User) -> bool {
self.kdf == user.client_kdf_type
&& self.kdf_iterations == user.client_kdf_iter
&& self.kdf_memory == user.client_kdf_memory
&& self.kdf_parallelism == user.client_kdf_parallelism
}
}
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RegisterData { pub struct RegisterData {
@ -701,18 +710,32 @@ fn set_kdf_data(user: &mut User, data: &KDFData) -> EmptyResult {
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct AuthenticationData { pub(super) struct AuthenticationData {
salt: String, salt: String,
kdf: KDFData, pub(super) kdf: KDFData,
master_password_authentication_hash: String, pub(super) master_password_authentication_hash: String,
}
impl AuthenticationData {
pub(super) fn check(&self, user: &User, unlock: &UnlockData) -> EmptyResult {
if self.kdf != unlock.kdf {
err!("KDF settings must be equal for authentication and unlock")
}
if self.salt != user.master_password_salt() || self.salt != unlock.salt {
err!("Invalid master password salt")
}
Ok(())
}
} }
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct UnlockData { pub(super) struct UnlockData {
salt: String, salt: String,
kdf: KDFData, kdf: KDFData,
master_key_wrapped_user_key: String, pub(super) master_key_wrapped_user_key: String,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -731,13 +754,7 @@ async fn post_kdf(data: Json<ChangeKdfData>, headers: Headers, conn: DbConn, nt:
err!("Invalid password") err!("Invalid password")
} }
if data.authentication_data.kdf != data.unlock_data.kdf { data.authentication_data.check(&headers.user, &data.unlock_data)?;
err!("KDF settings must be equal for authentication and unlock")
}
if headers.user.email != data.authentication_data.salt || headers.user.email != data.unlock_data.salt {
err!("Invalid master password salt")
}
let mut user = headers.user; let mut user = headers.user;

26
src/api/core/organizations.rs

@ -26,6 +26,8 @@ use crate::{
util::{NumberOrString, convert_json_key_lcase_first}, util::{NumberOrString, convert_json_key_lcase_first},
}; };
use super::accounts::{AuthenticationData, UnlockData};
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {
routes![ routes![
get_organization, get_organization,
@ -2939,9 +2941,14 @@ struct OrganizationUserResetPasswordEnrollmentRequest {
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct OrganizationUserRecoverAccountRequest { struct OrganizationUserRecoverAccountRequest {
// Legacy payload
new_master_password_hash: Option<String>, new_master_password_hash: Option<String>,
key: Option<String>, key: Option<String>,
// Current payload
authentication_data: Option<AuthenticationData>,
unlock_data: Option<UnlockData>,
#[serde(default)] #[serde(default)]
reset_master_password: bool, reset_master_password: bool,
#[serde(default)] #[serde(default)]
@ -3054,13 +3061,23 @@ async fn recover_account(
} }
if req.reset_master_password { if req.reset_master_password {
if let Some(key) = req.key let (new_master_password_hash, new_key) = if let (Some(authentication_data), Some(unlock_data)) =
&& let Some(hash) = req.new_master_password_hash (req.authentication_data, req.unlock_data)
{ {
user.set_password(hash.as_str(), Some(key), true, None, &conn).await?; authentication_data.check(&user, &unlock_data)?;
if !authentication_data.kdf.matches_user(&user) {
err!("KDF settings do not match the user account")
}
(authentication_data.master_password_authentication_hash, unlock_data.master_key_wrapped_user_key)
} else if let (Some(new_master_password_hash), Some(new_key)) = (req.new_master_password_hash, req.key) {
(new_master_password_hash, new_key)
} else { } else {
err_code!("Unprocessable request", "Missing fields to reset password", Status::UnprocessableEntity.code); err_code!("Unprocessable request", "Missing fields to reset password", Status::UnprocessableEntity.code);
} };
user.set_password(&new_master_password_hash, Some(new_key), true, None, &conn).await?;
} }
if req.reset_two_factor { if req.reset_two_factor {
@ -3118,6 +3135,7 @@ async fn get_reset_password_details(
"kdfIterations": user.client_kdf_iter, "kdfIterations": user.client_kdf_iter,
"kdfMemory": user.client_kdf_memory, "kdfMemory": user.client_kdf_memory,
"kdfParallelism": user.client_kdf_parallelism, "kdfParallelism": user.client_kdf_parallelism,
"masterPasswordSalt": user.master_password_salt(),
"resetPasswordKey": member.reset_password_key, "resetPasswordKey": member.reset_password_key,
"encryptedPrivateKey": org.private_key, "encryptedPrivateKey": org.private_key,
}))) })))

4
src/db/models/user.rs

@ -170,6 +170,10 @@ impl User {
) )
} }
pub fn master_password_salt(&self) -> String {
self.email.trim().to_lowercase()
}
pub fn check_valid_recovery_code(&self, recovery_code: &str) -> bool { pub fn check_valid_recovery_code(&self, recovery_code: &str) -> bool {
if let Some(ref totp_recover) = self.totp_recover { if let Some(ref totp_recover) = self.totp_recover {
crypto::ct_eq(recovery_code, totp_recover.to_lowercase()) crypto::ct_eq(recovery_code, totp_recover.to_lowercase())

Loading…
Cancel
Save