diff --git a/src/crypt.rs b/src/crypt.rs index e3d9c83..a37bffb 100644 --- a/src/crypt.rs +++ b/src/crypt.rs @@ -1,11 +1,21 @@ use super::db; +use crypto::aes::{self, KeySize}; +use crypto::hmac::Hmac; +use crypto::mac::{Mac, MacResult}; use crypto::scrypt::{self, ScryptParams}; +use crypto::sha2::Sha256; use rand::rngs::OsRng; use rand::Rng; use rustc_serialize::base64; use rustc_serialize::base64::{FromBase64, ToBase64}; use std::io; +pub struct EncryptedValue { + pub ciphertext: Vec, + pub iv: [u8; 32], + pub mac: MacResult, +} + 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)?; @@ -37,3 +47,16 @@ fn get_salt(db_conn: &db::DbHandle) -> io::Result> { } } } + +pub fn decrypt_value(value: Vec, master_key: [u8; 32], iv: [u8; 32], mac: [u8; 32]) -> Vec { + let mut hmac = Hmac::new(Sha256::new(), &master_key); + hmac.input(&value[..]); + if hmac.result() != MacResult::new(&mac) { + panic!("Mac did not match, corrupted data"); + } + + let mut cipher = aes::ctr(KeySize::KeySize256, &master_key, &iv); + let mut output: Vec = vec![0; value.len() as usize]; + cipher.process(&value, output.as_mut_slice()); + output +} diff --git a/src/db.rs b/src/db.rs index 6576643..403cbfb 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,4 +1,5 @@ -use rusqlite::Connection; +use super::crypt; +use rusqlite::{Connection, NO_PARAMS}; use rustc_serialize::base64; use rustc_serialize::base64::{FromBase64, ToBase64}; use std::error::Error; @@ -16,6 +17,14 @@ struct DbProperty { value: Option, } +#[derive(Debug)] +pub struct Account { + pub id: i64, + pub host: String, + pub user: String, + pub password: String, +} + impl DbHandle { pub fn new(db_path: &Option) -> DbHandle { let path: PathBuf = db_path @@ -25,7 +34,7 @@ impl DbHandle { let mut conn: Connection = Connection::open(path).unwrap(); let tx = conn.transaction().unwrap(); tx.execute_batch(DB_INIT_QUERY).unwrap(); - tx.commit(); + tx.commit().unwrap(); DbHandle { conn: conn } } @@ -65,4 +74,65 @@ impl DbHandle { let b64value: String = value.to_base64(base64::STANDARD); self.set_db_property(name, &b64value); } + + pub fn list_accounts(&mut self, master_key: [u8; 32]) -> Vec { + let mut stmt = self.conn + .prepare("SELECT h.iv, h.ciphertext, h.mac, u.iv, u.ciphertext, u.mac, p.iv, p.ciphertext, p.mac, a.id FROM accounts a \ + LEFT JOIN encrypted_values h ON a.server=h.id \ + LEFT JOIN encrypted_values u ON a.user=u.id \ + LEFT JOIN encrypted_values p ON a.password=p.id") + .unwrap(); + let result: Vec = { + let props = stmt + .query_map(NO_PARAMS, |row| { + let host: String = String::from_utf8(decrypt_base64( + row.get(0).unwrap(), + row.get(1).unwrap(), + row.get(2).unwrap(), + master_key, + )) + .unwrap(); + let user: String = String::from_utf8(decrypt_base64( + row.get(3).unwrap(), + row.get(4).unwrap(), + row.get(5).unwrap(), + master_key, + )) + .unwrap(); + let password: String = String::from_utf8(decrypt_base64( + row.get(6).unwrap(), + row.get(7).unwrap(), + row.get(8).unwrap(), + master_key, + )) + .unwrap(); + Ok(Account { + id: row.get(9).unwrap(), + host: host, + user: user, + password: password, + }) + }) + .unwrap(); + + props.map(move |x| x.unwrap()).collect() + }; + result + } +} + +fn decrypt_base64(iv: String, ciphertext: String, mac: String, master_key: [u8; 32]) -> Vec { + let mut iv_bytes: [u8; 32] = [0; 32]; + let mut mac_bytes: [u8; 32] = [0; 32]; + + iv_bytes.clone_from_slice(&iv.from_base64().unwrap()); + mac_bytes.clone_from_slice(&mac.from_base64().unwrap()); + + let decrypted: Vec = crypt::decrypt_value( + ciphertext.from_base64().unwrap(), + master_key, + iv_bytes, + mac_bytes, + ); + decrypted } diff --git a/src/main.rs b/src/main.rs index 00d3e60..4bef0a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -90,6 +90,16 @@ fn get_master_key(db_conn: &mut db::DbHandle) -> [u8; 32] { master_key } +fn list(mut db_conn: db::DbHandle, master_key: [u8; 32]) { + for host in db_conn + .list_accounts(master_key) + .into_iter() + .map(|account: db::Account| account.host) + { + println!("{}", host); + } +} + fn main() -> Result<(), Box> { pretty_env_logger::init(); let args: Args = Docopt::new(USAGE) @@ -106,5 +116,9 @@ fn main() -> Result<(), Box> { let master_key: [u8; 32] = get_master_key(&mut db_conn); + if args.cmd_list { + list(db_conn, master_key); + } + Ok(()) }