mirror of
https://github.com/morgan9e/bitwarden-desktop-agent
synced 2026-04-14 00:04:06 +09:00
auto-detect enrolled key, --email only needed for enroll/remove
This commit is contained in:
77
src/main.rs
77
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<String> {
|
||||
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 <email> --enroll first)"),
|
||||
};
|
||||
|
||||
let mut bridge = BiometricBridge::new(store, uid, prompt);
|
||||
let sock = ipc::socket_path();
|
||||
log::info(&format!("listening on {}", sock.display()));
|
||||
|
||||
@@ -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<Vec<u8>, String>;
|
||||
fn remove(&self, uid: &str);
|
||||
fn find_key(&self) -> Option<String>;
|
||||
}
|
||||
|
||||
pub fn get_backend(preferred: Option<&str>) -> Box<dyn KeyStore> {
|
||||
|
||||
@@ -114,6 +114,17 @@ impl KeyStore for PinKeyStore {
|
||||
fs::remove_file(p).ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn find_key(&self) -> Option<String> {
|
||||
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<Zeroizing<Vec<u8>>, String> {
|
||||
|
||||
Reference in New Issue
Block a user