44 lines
1.7 KiB
Rust
44 lines
1.7 KiB
Rust
//! 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, String> {
|
|
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<Option<String>, 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)),
|
|
}
|
|
}
|