diff --git a/src/main.rs b/src/main.rs index fc2c1f6..910092a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,9 +6,6 @@ mod ipc; mod log; mod storage; -use std::fs; -use std::path::PathBuf; - use clap::Parser; use sha2::{Digest, Sha256}; use zeroize::Zeroize; @@ -42,27 +39,6 @@ struct Args { remove: bool, } -fn config_dir() -> PathBuf { - let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".into()); - PathBuf::from(home).join(".cache").join("com.bitwarden.desktop") -} - -fn config_path() -> PathBuf { - config_dir().join("agent.conf") -} - -fn save_email(email: &str) { - fs::create_dir_all(config_dir()).ok(); - fs::write(config_path(), email).ok(); -} - -fn load_email() -> Option { - fs::read_to_string(config_path()) - .ok() - .map(|s| s.trim().to_string()) - .filter(|s| !s.is_empty()) -} - fn user_hash(email: &str) -> String { let mut hasher = Sha256::new(); hasher.update(email.to_lowercase().trim().as_bytes()); @@ -72,29 +48,17 @@ fn user_hash(email: &str) -> String { fn main() { let args = Args::parse(); - - let email = args - .email - .clone() - .or_else(load_email) - .unwrap_or_else(|| log::fatal("no email provided (use --email on first run)")); - - let uid = user_hash(&email); let store = get_backend(args.backend.as_deref()); let prompt = get_prompter(args.askpass.as_deref()); log::info(&format!("backend: {}", store.name())); - if args.remove { - if store.has_key(&uid) { - store.remove(&uid); - log::info(&format!("key removed for {email}")); - } else { - log::info(&format!("no key found for {email}")); - } - return; - } + if args.enroll { + let email = args + .email + .as_deref() + .unwrap_or_else(|| log::fatal("--email required for enrollment")); + let uid = user_hash(email); - if !store.has_key(&uid) || args.enroll { log::info(if !store.has_key(&uid) { "enrolling" } else { @@ -108,7 +72,7 @@ fn main() { .unwrap_or_else(|| log::fatal("no password provided")); log::info(&format!("logging in as {email}")); - let (mut key_bytes, server_uid) = auth::login(&email, &pw, &args.server, &prompt); + let (mut key_bytes, server_uid) = auth::login(email, &pw, &args.server, &prompt); log::info(&format!("authenticated, uid={server_uid}")); let auth = prompt(&format!("choose {} password:", store.name())) @@ -123,13 +87,34 @@ fn main() { .store(&uid, &key_bytes, &auth) .unwrap_or_else(|e| log::fatal(&format!("store failed: {e}"))); key_bytes.zeroize(); - save_email(&email); log::info(&format!("key sealed via {}", store.name())); log::info("wiped key from memory"); - } else { - log::info(&format!("key ready for {email}")); + return; } + if args.remove { + let email = args + .email + .as_deref() + .unwrap_or_else(|| log::fatal("--email required for --remove")); + let uid = user_hash(email); + if store.has_key(&uid) { + store.remove(&uid); + log::info(&format!("key removed for {email}")); + } else { + log::info(&format!("no key found for {email}")); + } + return; + } + + let uid = match store.find_key() { + Some(uid) => { + log::info(&format!("found key: {uid}")); + uid + } + None => log::fatal("no enrolled key found (run with --email --enroll first)"), + }; + let mut bridge = BiometricBridge::new(store, uid, prompt); let sock = ipc::socket_path(); log::info(&format!("listening on {}", sock.display())); diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 659392d..48674af 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -8,6 +8,7 @@ pub trait KeyStore { fn store(&self, uid: &str, data: &[u8], auth: &str) -> Result<(), String>; fn load(&self, uid: &str, auth: &str) -> Result, String>; fn remove(&self, uid: &str); + fn find_key(&self) -> Option; } pub fn get_backend(preferred: Option<&str>) -> Box { diff --git a/src/storage/pin.rs b/src/storage/pin.rs index fab4909..672f8a3 100644 --- a/src/storage/pin.rs +++ b/src/storage/pin.rs @@ -114,6 +114,17 @@ impl KeyStore for PinKeyStore { fs::remove_file(p).ok(); } } + + fn find_key(&self) -> Option { + let entries = fs::read_dir(&self.dir).ok()?; + for entry in entries.flatten() { + let path = entry.path(); + if path.extension().and_then(|e| e.to_str()) == Some("enc") { + return path.file_stem().and_then(|s| s.to_str()).map(|s| s.to_string()); + } + } + None + } } fn derive(password: &str, salt: &[u8]) -> Result>, String> {