#![warn(clippy::all)] use crypto::hmac::Hmac; use crypto::mac::{Mac, MacResult}; use crypto::sha2::Sha256; use dialoguer::{theme::ColorfulTheme, Input, PasswordInput, Select}; use docopt::Docopt; use log::debug; use rand::rngs::OsRng; use rand::seq::IteratorRandom; use rand::seq::SliceRandom; use serde::Deserialize; use std::error::Error; pub mod crypt; pub mod db; pub mod generate; static USAGE: &str = " foil Usage: foil set [--namespace=] [--db=] foil get [--namespace=] [--db=] foil list [--namespace=] [--db=] foil shell [--namespace=] [--db=] foil dump [--db=] foil generate foil (-h | --help) Options: --db= The path to the sqlite database [default: db.sqlite3]. --src= The path to the old sqlite database [default: db.sqlite3]. -n DB, --namespace= An identifier for a group of secrets [default: main] -h, --help Show this screen. --version Show version. "; #[derive(Debug, Deserialize)] struct Args { cmd_set: bool, cmd_get: bool, cmd_list: bool, cmd_generate: bool, cmd_dump: bool, cmd_shell: bool, flag_db: Option, flag_src: Option, flag_namespace: String, arg_spec: Option, } fn get_master_key(db_conn: &mut db::DbHandle) -> [u8; 32] { let known_string = db_conn .get_db_property("known_string") .expect("There was a problem reading from the db") .unwrap_or_else(|| { println!("No master password set yet, create new one:"); let mut random = OsRng::new().unwrap(); let new_known: String = { let mut new_chars: Vec = "abcdefghijklmnopqrstuvwxyz" .chars() .choose_multiple(&mut random, 64); new_chars.shuffle(&mut random); new_chars.into_iter().collect() }; db_conn.set_db_property("known_string", &new_known); new_known }); let master_key: [u8; 32] = { let master_password = PasswordInput::with_theme(&ColorfulTheme::default()) .with_prompt("Master password") .interact() .unwrap(); crypt::get_master_key(&db_conn, &master_password).unwrap() }; let mut hmac = Hmac::new(Sha256::new(), &master_key); hmac.input(known_string.as_bytes()); let existing_hmac = db_conn .get_db_property_bytes("known_hmac") .expect("There was a problem reading from the db") .unwrap_or_else(|| { let mut raw_result: Vec = std::iter::repeat(0).take(hmac.output_bytes()).collect(); hmac.raw_result(&mut raw_result); db_conn.set_db_property_bytes("known_hmac", &raw_result); raw_result }); if hmac.result() != MacResult::new(&existing_hmac[..]) { panic!("Incorrect master password"); } master_key } fn list(db_conn: &mut db::DbHandle, master_key: [u8; 32], namespace: &str) { for note in db_conn.read_notes(master_key).unwrap() { if note.namespace == namespace && note.category == "account" { println!("{}", note.title); } } } fn get(db_conn: &mut db::DbHandle, master_key: [u8; 32], namespace: &str) { println!("Reading a site from the database"); let host: String = Input::with_theme(&ColorfulTheme::default()) .with_prompt("hostname") .interact() .unwrap(); for note in db_conn.read_notes(master_key).unwrap() { if note.namespace == namespace && note.title == host && note.category == "account" { println!("===== note ====="); println!("namespace: {}", note.namespace); println!("category: {}", note.category); println!("title: {}", note.title); println!("{}", note.value); } } } fn set(db_conn: &mut db::DbHandle, master_key: [u8; 32], namespace: &str) { println!("Adding a site to the database"); let host: String = Input::with_theme(&ColorfulTheme::default()) .with_prompt("hostname") .interact() .unwrap(); let username: String = Input::with_theme(&ColorfulTheme::default()) .with_prompt("username") .interact() .unwrap(); let password: String = PasswordInput::with_theme(&ColorfulTheme::default()) .with_prompt("Site password") .with_confirmation("Repeat site password", "Error: the passwords don't match.") .interact() .unwrap(); db_conn.write_note( master_key, db::Note { id: 0, namespace: namespace.to_string(), category: "account".to_string(), title: host, value: format!("username: {}\npassword: {}\n", username, password), }, ); println!("Successfully added password"); } fn dump(db_conn: &mut db::DbHandle, master_key: [u8; 32]) { for note in db_conn.read_notes(master_key).unwrap() { println!("===== note ====="); println!("namespace: {}", note.namespace); println!("category: {}", note.category); println!("title: {}", note.title); println!("{}", note.value); } } fn shell_generate() { let spec: String = Input::with_theme(&ColorfulTheme::default()) .with_prompt("Password Spec") .default("30,AAAa111..".to_owned()) .interact() .unwrap(); generate::generate(&spec); } fn change_namespace(db_conn: &mut db::DbHandle, master_key: [u8; 32]) -> String { let namespaces = db_conn.list_namespaces(master_key).unwrap(); let selection = Select::with_theme(&ColorfulTheme::default()) .with_prompt("Change Namespace") .default(0) .items(&namespaces[..]) .interact() .unwrap(); namespaces[selection].name.to_owned() } fn create_namespace(db_conn: &mut db::DbHandle, master_key: [u8; 32]) -> String { let namespace: String = Input::with_theme(&ColorfulTheme::default()) .with_prompt("Namespace") .default("main".to_owned()) .interact() .unwrap(); namespace } fn shell(db_conn: &mut db::DbHandle, master_key: [u8; 32], _namespace: &str) { let mut namespace: String = _namespace.to_owned(); loop { let selections = &[ "get", "list", "set", "generate", "change namespace", "create namespace", "exit", ]; let main_menu_prompt = format!("Main Menu ({})", namespace); let selection = Select::with_theme(&ColorfulTheme::default()) .with_prompt(&main_menu_prompt) .default(0) .items(&selections[..]) .interact() .unwrap(); match selections[selection] { "get" => get(db_conn, master_key, &namespace), "list" => list(db_conn, master_key, &namespace), "set" => set(db_conn, master_key, &namespace), "generate" => shell_generate(), "change namespace" => { namespace = change_namespace(db_conn, master_key); } "create namespace" => { namespace = create_namespace(db_conn, master_key); } "exit" => break, _ => panic!("Unrecognized command"), }; } } fn main() -> Result<(), Box> { pretty_env_logger::init(); let args: Args = Docopt::new(USAGE) .and_then(|dopt| dopt.deserialize()) .unwrap_or_else(|e| e.exit()); debug!("{:?}", args); if args.cmd_generate { generate::generate(&args.arg_spec.unwrap()); return Ok(()); } let mut db_conn: db::DbHandle = db::DbHandle::new(&args.flag_db); let master_key: [u8; 32] = get_master_key(&mut db_conn); if args.cmd_set { set(&mut db_conn, master_key, &args.flag_namespace); } else if args.cmd_get { get(&mut db_conn, master_key, &args.flag_namespace); } else if args.cmd_list { list(&mut db_conn, master_key, &args.flag_namespace); } else if args.cmd_dump { dump(&mut db_conn, master_key); } else if args.cmd_shell { shell(&mut db_conn, master_key, &args.flag_namespace); } Ok(()) }