From 90b2cf08a04064d4ea34c4668f53fa77e42f3d91 Mon Sep 17 00:00:00 2001 From: tom27052006 <83423411+tom27052006@users.noreply.github.com> Date: Wed, 16 Sep 2026 17:59:41 +0000 Subject: [PATCH] Support new emergency access password payload --- src/api/core/accounts.rs | 9 +++++ src/api/core/emergency_access.rs | 61 ++++++++++++++++++++++++++++---- 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 69be1334..2b14a269 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -91,6 +91,15 @@ pub struct KDFData { kdf_parallelism: Option, } +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)] #[serde(rename_all = "camelCase")] pub struct RegisterData { diff --git a/src/api/core/emergency_access.rs b/src/api/core/emergency_access.rs index 2eb95502..beb340ba 100644 --- a/src/api/core/emergency_access.rs +++ b/src/api/core/emergency_access.rs @@ -6,7 +6,7 @@ use crate::{ CONFIG, api::{ EmptyResult, JsonResult, - core::{CipherSyncData, CipherSyncType}, + core::{CipherSyncData, CipherSyncType, accounts::KDFData}, }, auth::{Headers, decode_emergency_access_invite}, db::{ @@ -615,17 +615,39 @@ async fn takeover_emergency_access(emer_id: EmergencyAccessId, headers: Headers, "kdfMemory": grantor_user.client_kdf_memory, "kdfParallelism": grantor_user.client_kdf_parallelism, "keyEncrypted": &emergency_access.key_encrypted, + "salt": master_password_salt(&grantor_user), "object": "emergencyAccessTakeover", }); Ok(Json(result)) } +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct EmergencyAccessAuthenticationData { + salt: String, + kdf: KDFData, + master_password_authentication_hash: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct EmergencyAccessUnlockData { + salt: String, + kdf: KDFData, + master_key_wrapped_user_key: String, +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct EmergencyAccessPasswordData { - new_master_password_hash: String, - key: String, + // Legacy payload + new_master_password_hash: Option, + key: Option, + + // Current payload + authentication_data: Option, + unlock_data: Option, } #[post("/emergency-access//password", data = "")] @@ -638,8 +660,6 @@ async fn password_emergency_access( check_emergency_access_enabled()?; let data: EmergencyAccessPasswordData = data.into_inner(); - let new_master_password_hash = &data.new_master_password_hash; - //let key = &data.Key; let requesting_user = headers.user; let Some(emergency_access) = @@ -656,8 +676,33 @@ async fn password_emergency_access( err!("Grantor user not found.") }; + let (new_master_password_hash, new_key) = + if let (Some(authentication_data), Some(unlock_data)) = (data.authentication_data, data.unlock_data) { + if authentication_data.kdf != unlock_data.kdf { + err!("KDF settings must be equal for authentication and unlock") + } + + if authentication_data.salt != unlock_data.salt { + err!("Invalid master password salt") + } + + if !authentication_data.kdf.matches_user(&grantor_user) { + err!("KDF settings do not match the grantor account") + } + + if authentication_data.salt != master_password_salt(&grantor_user) { + err!("Invalid master password salt") + } + + (authentication_data.master_password_authentication_hash, unlock_data.master_key_wrapped_user_key) + } else if let (Some(new_master_password_hash), Some(new_key)) = (data.new_master_password_hash, data.key) { + (new_master_password_hash, new_key) + } else { + err!("Invalid request!") + }; + // change grantor_user password - grantor_user.set_password(new_master_password_hash, Some(data.key), true, None, &conn).await?; + grantor_user.set_password(&new_master_password_hash, Some(new_key), true, None, &conn).await?; grantor_user.save(&conn).await?; // Disable TwoFactor providers since they will otherwise block logins @@ -701,6 +746,10 @@ async fn policies_emergency_access(emer_id: EmergencyAccessId, headers: Headers, }))) } +fn master_password_salt(user: &User) -> String { + user.email.trim().to_lowercase() +} + fn is_valid_request( emergency_access: &EmergencyAccess, requesting_user_id: &UserId,