fix(password): update salt in set_permanent_password()

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou
2026-03-19 11:54:20 +08:00
parent b662b38d30
commit 37802208aa

View File

@@ -1233,6 +1233,23 @@ impl Config {
} }
let mut config = CONFIG.write().unwrap(); let mut config = CONFIG.write().unwrap();
// If the permanent password is already stored as a hashed verifier, avoid rotating salt
// when the plaintext stays the same. Rotating salt on "no-op" updates would unnecessarily
// clear trusted devices and trigger config sync churn.
if !password.is_empty()
&& !config.password.is_empty()
&& is_permanent_password_hashed_storage(&config.password)
&& !config.salt.is_empty()
{
if let Some(stored_h1) = decode_permanent_password_h1_from_storage(&config.password) {
let candidate_h1 = compute_permanent_password_h1(password, &config.salt);
if constant_time_eq_32(&candidate_h1, &stored_h1) {
return;
}
}
}
let stored = if password.is_empty() { let stored = if password.is_empty() {
String::new() String::new()
} else { } else {
@@ -1250,12 +1267,9 @@ impl Config {
config: &mut Config, config: &mut Config,
password: &str, password: &str,
) -> String { ) -> String {
if config.salt.is_empty() { // Rotate salt on permanent password updates so the verifier changes even if the user
// If salt is missing, we cannot keep an existing hashed verifier valid anyway. // reuses a previous password. (No-op updates are handled in `set_permanent_password()`.)
// When updating the password, generate a new salt and store it with the new verifier. config.salt = Config::get_auto_password(DEFAULT_SALT_LEN);
log::warn!("Salt is empty; generating new salt for permanent password update");
config.salt = Config::get_auto_password(DEFAULT_SALT_LEN);
}
let h1 = compute_permanent_password_h1(password, &config.salt); let h1 = compute_permanent_password_h1(password, &config.salt);
encode_permanent_password_storage_from_h1(&h1) encode_permanent_password_storage_from_h1(&h1)
} }
@@ -1598,11 +1612,13 @@ impl Config {
return; return;
} }
// Once the permanent password is stored as a hashed verifier, treat salt as immutable. // Once the permanent password is stored as a hashed verifier, keep salt and verifier
// Allow hashed verifier updates when salt is unchanged, so service->user config sync can // consistent as a pair.
// propagate permanent password changes without plaintext. // - Refuse salt-only changes (would break verification).
if incoming.salt != current.salt { // - Allow hashed->hashed updates with salt rotation only when the verifier also changes,
log::error!("Refusing to change salt for hashed permanent password via Config::set"); // so service->user config sync can propagate password updates without plaintext.
if incoming.salt != current.salt && incoming.password == current.password {
log::error!("Refusing to change salt without updating hashed permanent password");
incoming.password = current.password.clone(); incoming.password = current.password.clone();
incoming.salt = current.salt.clone(); incoming.salt = current.salt.clone();
return; return;
@@ -1616,8 +1632,10 @@ impl Config {
return; return;
} }
// Allow hashed->hashed verifier updates when salt matches. // Allow hashed->hashed verifier updates (with optional salt rotation).
if !incoming.password.is_empty() && is_permanent_password_hashed_storage(&incoming.password) if !incoming.password.is_empty()
&& is_permanent_password_hashed_storage(&incoming.password)
&& !incoming.salt.is_empty()
{ {
return; return;
} }