Database encryption

feature/database-encription
German Lashevich 6 years ago
parent 49ffb08499
commit 4c25bad63c
No known key found for this signature in database
GPG Key ID: 3446FAE369C9A8B4

@ -13,20 +13,16 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH};
const CONFIG_PATH: &str = ".rustotpony/db.json";
fn main() {
Cli::new().run();
Cli::run();
}
struct Cli {
app: RusTOTPony<JsonDatabase>,
}
struct Cli {}
impl Cli {
fn new() -> Self {
fn app() -> RusTOTPony<JsonDatabase> {
let secret = Self::get_secret();
let db = JsonDatabase::new(Self::get_database_path(), &secret);
Self {
app: RusTOTPony::new(db),
}
RusTOTPony::new(db)
}
fn get_secret() -> String {
@ -35,35 +31,35 @@ impl Cli {
// fn get_secret_from_storage() -> String { }
fn run(&mut self) {
match self.get_cli_api_matches().subcommand() {
fn run() {
match Self::get_cli_api_matches().subcommand() {
("dash", Some(_)) => {
self.show_dashboard();
Self::show_dashboard();
}
("list", Some(_)) => {
self.show_applications_list(false);
Self::show_applications_list(false);
}
// ("show-all", Some(_)) => {
// self.show_applications_list(true);
// Self::show_applications_list(true);
// }
// ("show", Some(sub_app)) => {
// let app_name: &str = sub_app
// .value_of("APPNAME")
// .expect("Couldn't read APPNAME for 'show' command");
// self.show_application(app_name);
// Self::show_application(app_name);
// }
("add", Some(sub_app)) => {
let app_name: &str = sub_app
.value_of("APPNAME")
.expect("Couldn't read APPNAME for 'add' command");
let key: &str = sub_app.value_of("USERNAME").unwrap_or("");
self.create_application(app_name, key);
Self::create_application(app_name, key);
}
("delete", Some(sub_app)) => {
let app_name: &str = sub_app
.value_of("APPNAME")
.expect("Couldn't read APPNAME for 'delete' command");
self.delete_application(app_name);
Self::delete_application(app_name);
}
("rename", Some(sub_app)) => {
let app_name: &str = sub_app
@ -72,18 +68,18 @@ impl Cli {
let new_name: &str = sub_app
.value_of("NEWNAME")
.expect("Couldn't read NEWNAME for 'rename' command");
self.rename_application(app_name, new_name);
Self::rename_application(app_name, new_name);
}
("eradicate", Some(_)) => {
self.eradicate_database();
Self::eradicate_database();
}
_ => {
self.show_dashboard();
Self::show_dashboard();
}
}
}
fn get_cli_api_matches(&self) -> clap::ArgMatches<'static> {
fn get_cli_api_matches() -> clap::ArgMatches<'static> {
App::new("🐴 RusTOTPony")
.version(env!("CARGO_PKG_VERSION"))
.author("German Lashevich <german.lashevich@gmail.com>")
@ -128,8 +124,8 @@ impl Cli {
home.join(Path::new(CONFIG_PATH))
}
fn show_dashboard(&self) {
match self.app.get_applications() {
fn show_dashboard() {
match Self::app().get_applications() {
Ok(apps) => {
let mut is_first_iteration = true;
let lines_count = apps.len() + 1;
@ -167,11 +163,12 @@ impl Cli {
println!("[{:60}]", "=".repeat(idx as usize));
}
fn show_applications_list(&self, _: bool) {
fn show_applications_list(_: bool) {
// TODO Create Table structure with HashMap as follows and metadata about columns - width, titles, names
let app = Self::app();
let mut output_table: HashMap<&str, Vec<&str>> = HashMap::new();
let mut applications_count = 0;
let apps = match self.app.get_applications() {
let apps = match app.get_applications() {
Ok(v) => v,
Err(e) => {
println!("{}", e);
@ -238,35 +235,35 @@ impl Cli {
println!("{}", header_row_delimiter);
}
fn show_application(&self, name: &str) {
println!("{:?}", self.app.get_application(name));
fn show_application(name: &str) {
println!("{:?}", Self::app().get_application(name));
}
fn create_application(&mut self, name: &str, username: &str) {
fn create_application(name: &str, username: &str) {
let secret = rpassword::prompt_password_stdout("Enter your secret code: ").unwrap();
match self.app.create_application(name, username, &secret) {
match Self::app().create_application(name, username, &secret) {
Ok(_) => {
self.app.flush();
Self::app().flush();
println!("New application created: {}", name)
}
Err(err) => println!("{} Aborting…", err),
}
}
fn delete_application(&mut self, name: &str) {
match self.app.delete_application(name) {
fn delete_application(name: &str) {
match Self::app().delete_application(name) {
Ok(_) => {
self.app.flush();
Self::app().flush();
println!("Application '{}' successfully deleted", name)
}
Err(err) => println!("Couldn't delete application '{}': {}", name, err),
};
}
fn rename_application(&mut self, name: &str, newname: &str) {
match self.app.rename_application(name, newname) {
fn rename_application(name: &str, newname: &str) {
match Self::app().rename_application(name, newname) {
Ok(_) => {
self.app.flush();
Self::app().flush();
println!(
"Application '{}' successfully renamed to '{}'",
name, newname
@ -276,9 +273,9 @@ impl Cli {
};
}
fn eradicate_database(&mut self) {
self.app.delete_all_applications();
self.app.flush();
fn eradicate_database() {
Self::app().delete_all_applications();
Self::app().flush();
println!("Done.");
}
}

@ -1,3 +1,5 @@
#![feature(fs_read_write)]
extern crate base32;
extern crate crypto;
extern crate oath;
@ -15,6 +17,7 @@ use rand::{OsRng, Rng};
use std::collections::HashMap;
use std::fs::{create_dir_all, File, OpenOptions};
use std::io::ErrorKind;
use std::io::Write;
use std::path::{Path, PathBuf};
const DATABASE_VERSION: u8 = 1;
@ -130,6 +133,7 @@ pub struct JsonDatabase {
secret: String,
}
const IV_SIZE: usize = 16;
impl JsonDatabase {
pub fn new(path: PathBuf, secret: &str) -> JsonDatabase {
JsonDatabase {
@ -139,22 +143,50 @@ impl JsonDatabase {
}
fn read_database_file(&self) -> JsonDatabaseSchema {
let file = match File::open(&self.file_path) {
Ok(f) => f,
let data = match std::fs::read_to_string(&self.file_path) {
Ok(d) => d,
Err(ref err) if err.kind() == ErrorKind::NotFound => return Self::get_empty_schema(),
Err(err) => panic!("There was a problem opening file: {:?}", err),
};
serde_json::from_reader(file).expect("Couldn't parse JSON from database file")
let decrypted_data = Self::decrypt_data(&data, self.secret.as_str());
serde_json::from_str(decrypted_data.as_str())
.expect("Couldn't parse JSON from database file")
}
fn decrypt_data(data: &str, key: &str) -> String {
let bytes = data.as_bytes();
String::from_utf8(
Self::decrypt(&bytes[IV_SIZE..], key.as_bytes(), &bytes[..IV_SIZE])
.expect("Couldn't decrypt data"),
).ok()
.unwrap()
}
fn encrypt_data(data: &str, key: &str) -> Vec<u8> {
let mut data_with_iv = Self::create_iv();
data_with_iv.extend(data.as_bytes());
return Self::encrypt(&data_with_iv, key.as_bytes(), &data_with_iv[..IV_SIZE])
.expect("Couldn't encrypt data");
}
fn create_iv() -> Vec<u8> {
let mut iv = vec![0; IV_SIZE];
let mut rng = OsRng::new().ok().unwrap();
rng.fill_bytes(&mut iv);
iv
}
fn save_database_file(&self, content: JsonDatabaseSchema) {
let file = match self.open_database_file_for_write() {
let mut file = match self.open_database_file_for_write() {
Ok(f) => f,
Err(ref err) if err.kind() == ErrorKind::NotFound => self.create_database_file()
.expect("Couldn't create database file"),
Err(err) => panic!("Couldn't open database file: {:?}", err),
};
serde_json::to_writer(file, &content).expect("Couldn't write JSON data to database file");
let data = serde_json::to_string(&content).expect("Couldn't serialize data to JSON");
let encrypted_data = Self::encrypt_data(&data, &self.secret);
file.write_all(&encrypted_data)
.expect("Couldn't write data to database file");
}
// Encrypt a buffer with the given key and iv using

Loading…
Cancel
Save