//! OS keychain storage for the derived index key. //! //! Stores the *derived* SQLCipher key (hex), never the password: a //! keychain unlock skips the ~0.5 s Argon2 derivation, and the password //! itself never persists anywhere. Entries are keyed by database path so //! portable installs and multiple profiles don't clobber each other. //! //! Every failure here is non-fatal by design — a missing Secret Service //! daemon, a locked keyring, a denied prompt — and callers fall back to //! asking for the password. use keyring::Entry; const SERVICE: &str = "quicksearch"; fn entry(db_path: &str) -> Result { Entry::new(SERVICE, db_path).map_err(|e| format!("keychain unavailable: {}", e)) } /// Remember the derived key for this database on this machine. pub fn store_key(db_path: &str, key_hex: &str) -> Result<(), String> { entry(db_path)? .set_password(key_hex) .map_err(|e| format!("keychain store failed: {}", e)) } /// The remembered key, `Ok(None)` when nothing is stored. pub fn load_key(db_path: &str) -> Result, String> { match entry(db_path)?.get_password() { Ok(hex) => Ok(Some(hex)), Err(keyring::Error::NoEntry) => Ok(None), Err(e) => Err(format!("keychain read failed: {}", e)), } } /// Forget the remembered key; an entry that never existed reports success. /// A real failure means the key is still on the keychain, and callers must /// surface that rather than record "not remembered". pub fn delete_key(db_path: &str) -> Result<(), String> { match entry(db_path)?.delete_credential() { Ok(()) | Err(keyring::Error::NoEntry) => Ok(()), Err(e) => Err(format!("keychain delete failed: {}", e)), } }