//! Vault data module //! //! A vault represents a collection of records of sensitive data. Each record //! is encrypted before being written to disk. //! //! A vault can have multiple users which allows login-information to be //! shared between multiple people. By default only one (root) user //! is enabled though. //! use std::collections::{BTreeMap, HashMap}; use std::fs::{self, File}; use std::io::prelude::*; use std::path::{Path, PathBuf}; use record::{Payload, Record}; use security::{CryptoEngine, Key}; use serde_json; /// This should be made pretty with actual Errors at some point #[derive(Debug)] pub enum ErrorType { VaultAlreadyExists, DirectoryAlreadyExists, FailedToInitialise, Success, } pub struct Vault { name: String, path: String, engine: CryptoEngine, pub records: HashMap, } impl Vault { /// Attempt to create a new vault pub fn new(name: &str, path: &str, password: &str) -> Result { let mut buffer = PathBuf::new(); buffer.push(path); buffer.push(format!("{}.vault", name)); let mut me = Vault { name: String::from(name), path: buffer.to_str().unwrap().to_owned(), engine: CryptoEngine::generate(Key::generate()), records: HashMap::new(), }; /* Create relevant files */ match me.create_dirs() { ErrorType::Success => { let mut buffer = buffer.clone(); buffer.push("primary.key"); me.engine .save(buffer.to_str().unwrap(), password, &me.name) .unwrap(); } val => return Err(val), } return Ok(me); } pub fn load(name: &str, path: &str, password: &str) -> Result { /* Construct the base path */ let mut pathbuf = PathBuf::new(); pathbuf.push(path); pathbuf.push(format!("{}.vault", name)); /* Load the primary key */ pathbuf.push("primary.key"); let mut engine = match CryptoEngine::load(pathbuf.to_str().unwrap(), password, name) { Ok(e) => e, Err(e) => return Err(ErrorType::FailedToInitialise), }; pathbuf.pop(); /* Load all existing records */ pathbuf.push("records"); let records = fs::read_dir(pathbuf.as_path()).unwrap(); let mut record_map = HashMap::new(); pathbuf.pop(); /* Decrypt and map all existing records */ for entry in records { let mut encrypted = String::new(); let record = entry.unwrap(); let mut file = File::open(record.path().as_os_str()).unwrap(); file.read_to_string(&mut encrypted).unwrap(); /* Decrypt and decode the data */ let a_record: Record = engine.decrypt(encrypted).unwrap(); let name = a_record.header.name.clone(); record_map.insert(name, a_record); } return Ok(Vault { name: String::from(name), path: "".to_owned(), engine: engine, records: record_map, }); } /// Adds a new (empty) record to the vault pub fn add_record(&mut self, name: &str, category: &str, tags: Vec<&str>) { let mut record = Record::new(name, category); for tag in tags { record.add_tag(&tag); } self.records.insert(String::from(name), record); } /// Fill an existing record with data pub fn add_data(&mut self, record: &str, key: &str, data: Payload) { let r: &mut Record = self.records.get_mut(record).unwrap(); r.set_data(key, data); } /// Sync current records to disk, overwriting existing files pub fn sync(&mut self) { let mut buffer = PathBuf::new(); buffer.push(&self.path); buffer.push("records"); for (name, record) in &self.records { let encrypted = self.engine.encrypt(&record).unwrap(); /* /records/.data */ { buffer.push(&format!("{}.data", name)); let file = buffer.as_path(); // println!("Saving file '{}' to '{}'", name, file.to_str().unwrap()); let mut handle = match file.exists() { true => match File::open(file.as_os_str()) { Ok(k) => k, Err(e) => panic!("Failed to open file: {}", e), }, false => match File::create(file.as_os_str()) { Ok(k) => k, Err(e) => panic!("Failed to create file ({:?}): {}", file.as_os_str(), e), }, }; /* Write to disk */ match handle.write_all(encrypted.as_bytes()) { Err(e) => println!("An error was encountered while writing '{}': {}", name, e), _ => {} } } buffer.pop(); } } /**************************/ /// Create all relevant directories fn create_dirs(&mut self) -> ErrorType { let mut path = PathBuf::new(); path.push(&self.path); /* Check if the directories already exist */ if path.as_path().exists() { return ErrorType::DirectoryAlreadyExists; } /* Create the directory */ match fs::create_dir_all(path.as_path()) { Err(_) => return ErrorType::FailedToInitialise, _ => {} }; /* Create a few other directories */ path.push("records"); fs::create_dir_all(path.as_path()).unwrap(); return ErrorType::Success; } }