You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
205 lines
6.3 KiB
Rust
205 lines
6.3 KiB
Rust
use crypto::hmac::Hmac;
|
|
use crypto::mac::{Mac, MacResult};
|
|
use crypto::sha2::Sha256;
|
|
use dialoguer::{theme::ColorfulTheme, Input, PasswordInput};
|
|
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: &'static str = "
|
|
foil
|
|
|
|
Usage:
|
|
foil set [--namespace=<ns>] [--db=<db>]
|
|
foil get [--namespace=<ns>] [--db=<db>]
|
|
foil list [--namespace=<ns>] [--db=<db>]
|
|
foil transfer [--db=<db>]
|
|
foil dump [--db=<db>]
|
|
foil generate <spec>
|
|
foil (-h | --help)
|
|
|
|
Options:
|
|
--db=<db> The path to the sqlite database [default: db.sqlite3].
|
|
-n DB, --namespace=<db> 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_transfer: bool,
|
|
cmd_dump: bool,
|
|
flag_db: Option<String>,
|
|
flag_namespace: String,
|
|
arg_spec: Option<String>,
|
|
}
|
|
|
|
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<char> = "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<u8> = 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(mut db_conn: 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(mut db_conn: 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(mut db_conn: 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(mut db_conn: 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 transfer(mut db_conn: db::DbHandle, master_key: [u8; 32]) {
|
|
for account in db_conn.list_accounts(master_key).into_iter() {
|
|
let new_note = db::Note {
|
|
id: 0,
|
|
namespace: "main".to_owned(),
|
|
category: "account".to_owned(),
|
|
title: account.host,
|
|
value: format!(
|
|
"username: {}\npassword: {}\n",
|
|
account.user, account.password
|
|
),
|
|
};
|
|
db_conn.write_note(master_key, new_note);
|
|
}
|
|
}
|
|
|
|
fn main() -> Result<(), Box<dyn Error>> {
|
|
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(db_conn, master_key, &args.flag_namespace);
|
|
} else if args.cmd_get {
|
|
get(db_conn, master_key, &args.flag_namespace);
|
|
} else if args.cmd_list {
|
|
list(db_conn, master_key, &args.flag_namespace);
|
|
} else if args.cmd_transfer {
|
|
transfer(db_conn, master_key);
|
|
} else if args.cmd_dump {
|
|
dump(db_conn, master_key);
|
|
}
|
|
|
|
Ok(())
|
|
}
|