From 88ab2f0999c2ba5e5d1c320cb608d86b035916f9 Mon Sep 17 00:00:00 2001 From: fufesou Date: Mon, 11 May 2026 18:10:37 +0800 Subject: [PATCH 1/6] refact(password): encrypt Signed-off-by: fufesou --- libs/hbb_common | 2 +- src/flutter_ffi.rs | 12 +----------- src/ipc.rs | 12 ++---------- src/server/connection.rs | 42 ++++++++++++++++++++++++++-------------- src/ui_interface.rs | 3 +-- 5 files changed, 33 insertions(+), 38 deletions(-) diff --git a/libs/hbb_common b/libs/hbb_common index 9043c15ac..3f30a9291 160000 --- a/libs/hbb_common +++ b/libs/hbb_common @@ -1 +1 @@ -Subproject commit 9043c15acc6d5b42b6c12ad284c16c1ec172f1f0 +Subproject commit 3f30a92913a20b316a06aedf52702cc714ca6b03 diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 4b62b4fca..13a97cb43 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -2471,23 +2471,13 @@ pub fn is_disable_installation() -> SyncReturn { } pub fn is_preset_password() -> bool { - let hard = config::HARD_SETTINGS - .read() - .unwrap() - .get("password") - .cloned() - .unwrap_or_default(); - if hard.is_empty() { - return false; - } - // On desktop, service owns the authoritative config; query it via IPC and return only a boolean. #[cfg(not(any(target_os = "android", target_os = "ios")))] return crate::ipc::is_permanent_password_preset(); // On mobile, we have no service IPC; verify against local storage. #[cfg(any(target_os = "android", target_os = "ios"))] - return config::Config::matches_permanent_password_plain(&hard); + return config::Config::is_using_preset_password(); } // Don't call this function for desktop version. diff --git a/src/ipc.rs b/src/ipc.rs index ffe1b08a5..bb5e33696 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -839,15 +839,7 @@ async fn handle(data: Data, stream: &mut Connection) { "N".to_owned() }); } else if name == "permanent-password-is-preset" { - let hard = config::HARD_SETTINGS - .read() - .unwrap() - .get("password") - .cloned() - .unwrap_or_default(); - let is_preset = - !hard.is_empty() && Config::matches_permanent_password_plain(&hard); - value = Some(if is_preset { + value = Some(if Config::is_using_preset_password() { "Y".to_owned() } else { "N".to_owned() @@ -898,7 +890,7 @@ async fn handle(data: Data, stream: &mut Connection) { log::warn!("Changing permanent password is disabled"); updated = false; } else { - Config::set_permanent_password(&value); + updated = Config::set_permanent_password(&value); } // Explicitly ACK/NACK permanent-password writes. This allows UIs/FFI to // distinguish "accepted by daemon" vs "IPC send succeeded" without diff --git a/src/server/connection.rs b/src/server/connection.rs index 538503d9c..3fa4ba361 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -30,8 +30,12 @@ use cidr_utils::cidr::IpCidr; #[cfg(target_os = "android")] use hbb_common::protobuf::EnumOrUnknown; use hbb_common::{ - config::decode_permanent_password_h1_from_storage, config::{self, keys, Config, TrustedDevice}, + config::{ + decode_permanent_password_h1_from_storage, + local_permanent_password_storage_is_usable_for_auth, + preset_permanent_password_storage_is_usable_for_auth, + }, fs::{self, can_enable_overwrite_detection, JobType}, futures::{SinkExt, StreamExt}, get_time, get_version_number, @@ -412,8 +416,9 @@ impl Connection { let _raii_id = raii::ConnectionID::new(id); let _raii_control_permissions_id = raii::ControlPermissionsID::new(id, &control_permissions); + let salt = Config::get_effective_permanent_password_salt(); let hash = Hash { - salt: Config::get_salt(), + salt, challenge: Config::get_auto_password(6), ..Default::default() }; @@ -2109,6 +2114,16 @@ impl Connection { self.validate_password_plain(storage) } + fn validate_preset_password_storage(&self, storage: &str, salt: &str) -> bool { + if salt.is_empty() { + return self.validate_password_plain(storage); + } + let Some(h1) = decode_permanent_password_h1_from_storage(storage) else { + return false; + }; + self.verify_h1(&h1[..]) + } + // This is coarse brute-force protection for the current temporary password value. // We only care whether the active temporary password itself was presented correctly, // not whether later authorization steps succeed. A successful temporary-password @@ -2180,23 +2195,22 @@ impl Connection { log::info!("Permanent password accepted via logon-screen fallback"); } }; - // Since hashed storage uses a prefix-based encoding, a hard plaintext that - // happens to look like hashed storage could be mis-detected. Validate local storage - // and hard/preset plaintext via separate paths to avoid that ambiguity. - let (local_storage, _) = Config::get_local_permanent_password_storage_and_salt(); + // Strictly check storage usability before auth so malformed encrypted/hash storage + // cannot fall back to being accepted as legacy plaintext. + let (local_storage, local_salt) = + Config::get_local_permanent_password_storage_and_salt(); if !local_storage.is_empty() { - if self.validate_password_storage(&local_storage) { + if local_permanent_password_storage_is_usable_for_auth(&local_storage, &local_salt) + && self.validate_password_storage(&local_storage) + { print_fallback(); return true; } } else { - let hard = config::HARD_SETTINGS - .read() - .unwrap() - .get("password") - .cloned() - .unwrap_or_default(); - if !hard.is_empty() && self.validate_password_plain(&hard) { + let (hard, salt) = Config::get_preset_password_storage_and_salt(); + if preset_permanent_password_storage_is_usable_for_auth(&hard, &salt) + && self.validate_preset_password_storage(&hard, &salt) + { print_fallback(); return true; } diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 1645b242d..f70021e5b 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -647,8 +647,7 @@ pub fn set_permanent_password_with_result(password: String) -> bool { } #[cfg(any(target_os = "android", target_os = "ios"))] { - config::Config::set_permanent_password(&password); - return true; + return config::Config::set_permanent_password(&password); } #[cfg(not(any(target_os = "android", target_os = "ios")))] { From 36ae07c675782b919f2a131728532065040be80d Mon Sep 17 00:00:00 2001 From: fufesou Date: Wed, 20 May 2026 16:58:00 +0800 Subject: [PATCH 2/6] refact(password): simplify preset password Signed-off-by: fufesou --- libs/hbb_common | 2 +- src/server/connection.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/hbb_common b/libs/hbb_common index 3f30a9291..f94992f6c 160000 --- a/libs/hbb_common +++ b/libs/hbb_common @@ -1 +1 @@ -Subproject commit 3f30a92913a20b316a06aedf52702cc714ca6b03 +Subproject commit f94992f6cad2f0b3ed6840a3400ef46e1fdc745b diff --git a/src/server/connection.rs b/src/server/connection.rs index 3fa4ba361..98b6067eb 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -32,7 +32,7 @@ use hbb_common::protobuf::EnumOrUnknown; use hbb_common::{ config::{self, keys, Config, TrustedDevice}, config::{ - decode_permanent_password_h1_from_storage, + decode_permanent_password_h1_from_storage, decode_preset_password_h1_from_storage, local_permanent_password_storage_is_usable_for_auth, preset_permanent_password_storage_is_usable_for_auth, }, @@ -2118,7 +2118,7 @@ impl Connection { if salt.is_empty() { return self.validate_password_plain(storage); } - let Some(h1) = decode_permanent_password_h1_from_storage(storage) else { + let Some(h1) = decode_preset_password_h1_from_storage(storage) else { return false; }; self.verify_h1(&h1[..]) From eea2fd17f8b409a2e01f5b5f58bf80c2743e086e Mon Sep 17 00:00:00 2001 From: fufesou Date: Thu, 21 May 2026 21:26:41 +0800 Subject: [PATCH 3/6] update hbb_common Signed-off-by: fufesou --- libs/hbb_common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/hbb_common b/libs/hbb_common index f94992f6c..8e0c8ab93 160000 --- a/libs/hbb_common +++ b/libs/hbb_common @@ -1 +1 @@ -Subproject commit f94992f6cad2f0b3ed6840a3400ef46e1fdc745b +Subproject commit 8e0c8ab939a45e9d10992a4675ed144f2f821208 From 80cb51ce4dc8905962791555577480dddb1f0828 Mon Sep 17 00:00:00 2001 From: fufesou Date: Sat, 23 May 2026 14:44:14 +0800 Subject: [PATCH 4/6] refact(password): clear password, do not clear salt --- src/ipc.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/ipc.rs b/src/ipc.rs index bb5e33696..68c987f4e 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -1542,11 +1542,6 @@ fn apply_permanent_password_storage_and_salt_payload(payload: Option<&str>) -> R bail!("Invalid permanent-password-storage-and-salt payload"); }; - if storage.is_empty() { - Config::set_permanent_password_storage_for_sync("", "")?; - return Ok(()); - } - Config::set_permanent_password_storage_for_sync(storage, salt)?; Ok(()) } From 9a629b969234edb3bfe441cf1ac3ac50e027daba Mon Sep 17 00:00:00 2001 From: fufesou Date: Sat, 23 May 2026 17:46:40 +0800 Subject: [PATCH 5/6] refact(password): update hbb_common Signed-off-by: fufesou --- libs/hbb_common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/hbb_common b/libs/hbb_common index 8e0c8ab93..822701e41 160000 --- a/libs/hbb_common +++ b/libs/hbb_common @@ -1 +1 @@ -Subproject commit 8e0c8ab939a45e9d10992a4675ed144f2f821208 +Subproject commit 822701e416a60c97e6f3b23bb0bfa84a2177c57f From ae200050b638b6e1d396fd98f36b97fc69afa028 Mon Sep 17 00:00:00 2001 From: fufesou Date: Sat, 23 May 2026 17:57:03 +0800 Subject: [PATCH 6/6] refact(password): merge import Signed-off-by: fufesou --- src/server/connection.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/server/connection.rs b/src/server/connection.rs index 98b6067eb..1ae995577 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -30,11 +30,10 @@ use cidr_utils::cidr::IpCidr; #[cfg(target_os = "android")] use hbb_common::protobuf::EnumOrUnknown; use hbb_common::{ - config::{self, keys, Config, TrustedDevice}, config::{ - decode_permanent_password_h1_from_storage, decode_preset_password_h1_from_storage, - local_permanent_password_storage_is_usable_for_auth, - preset_permanent_password_storage_is_usable_for_auth, + self, decode_permanent_password_h1_from_storage, decode_preset_password_h1_from_storage, + keys, local_permanent_password_storage_is_usable_for_auth, + preset_permanent_password_storage_is_usable_for_auth, Config, TrustedDevice, }, fs::{self, can_enable_overwrite_detection, JobType}, futures::{SinkExt, StreamExt},