diff --git a/Cargo.toml b/Cargo.toml index 13d4ed3..28d0e77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ pretty_env_logger = "0.3.0" rand = "0.6.5" dirs = "2.0.0" rust-crypto = "0.2.36" +dialoguer = "0.4.0" +rustc-serialize = "0.3.24" [dependencies.rusqlite] version = "0.18.0" diff --git a/src/crypt.rs b/src/crypt.rs new file mode 100644 index 0000000..e3d9c83 --- /dev/null +++ b/src/crypt.rs @@ -0,0 +1,39 @@ +use super::db; +use crypto::scrypt::{self, ScryptParams}; +use rand::rngs::OsRng; +use rand::Rng; +use rustc_serialize::base64; +use rustc_serialize::base64::{FromBase64, ToBase64}; +use std::io; + +pub fn get_master_key(db_conn: &db::DbHandle, master_password: &str) -> io::Result<[u8; 32]> { + let scrypt_params: ScryptParams = ScryptParams::new(12, 16, 2); + let salt: Vec = get_salt(db_conn)?; + // 256 bit derived key + let mut derived_key = [0u8; 32]; + + scrypt::scrypt( + master_password.as_bytes(), + &*salt, + &scrypt_params, + &mut derived_key, + ); + Ok(derived_key) +} + +fn get_salt(db_conn: &db::DbHandle) -> io::Result> { + let existing_salt: Option = db_conn + .get_db_property("salt") + .expect("There was a problem reading from the db"); + + match existing_salt { + Some(salt) => Ok(salt.from_base64().unwrap()), + None => { + let mut rng = OsRng::new()?; + // 128 bit salt + let salt: Vec = rng.gen::<[u8; 16]>().to_vec(); + db_conn.set_db_property("salt", &salt.to_base64(base64::STANDARD)); + Ok(salt) + } + } +} diff --git a/src/db.rs b/src/db.rs index 99f2180..6576643 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,4 +1,6 @@ use rusqlite::Connection; +use rustc_serialize::base64; +use rustc_serialize::base64::{FromBase64, ToBase64}; use std::error::Error; use std::path::PathBuf; @@ -53,4 +55,14 @@ impl DbHandle { ) .unwrap(); } + + pub fn get_db_property_bytes(&self, name: &str) -> Result>, Box> { + self.get_db_property(name) + .map(|option| option.map(|prop| prop.from_base64().unwrap())) + } + + pub fn set_db_property_bytes(&self, name: &str, value: &Vec) { + let b64value: String = value.to_base64(base64::STANDARD); + self.set_db_property(name, &b64value); + } } diff --git a/src/main.rs b/src/main.rs index 4b927ab..00d3e60 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,7 @@ +use crypto::hmac::Hmac; +use crypto::mac::{Mac, MacResult}; +use crypto::sha2::Sha256; +use dialoguer::{theme::ColorfulTheme, PasswordInput}; use docopt::Docopt; use log::debug; use rand::rngs::OsRng; @@ -6,6 +10,7 @@ use rand::seq::SliceRandom; use serde::Deserialize; use std::error::Error; +pub mod crypt; pub mod db; pub mod generate; @@ -38,9 +43,7 @@ struct Args { fn get_master_key(db_conn: &mut db::DbHandle) -> [u8; 32] { let known_string = db_conn .get_db_property("known_string") - .unwrap_or_else(|error| { - panic!("There was a problem reading from the db: {:?}", error); - }) + .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(); @@ -55,7 +58,34 @@ fn get_master_key(db_conn: &mut db::DbHandle) -> [u8; 32] { new_known }); - let master_key: [u8; 32] = [0; 32]; + let master_key: [u8; 32] = { + let master_password = PasswordInput::with_theme(&ColorfulTheme::default()) + .with_prompt("Master password") + .with_confirmation( + "Repeat master password", + "Error: the passwords don't match.", + ) + .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 }