Database encryption

feature/database-encription
German Lashevich 7 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"; const CONFIG_PATH: &str = ".rustotpony/db.json";
fn main() { fn main() {
Cli::new().run(); Cli::run();
} }
struct Cli { struct Cli {}
app: RusTOTPony<JsonDatabase>,
}
impl Cli { impl Cli {
fn new() -> Self { fn app() -> RusTOTPony<JsonDatabase> {
let secret = Self::get_secret(); let secret = Self::get_secret();
let db = JsonDatabase::new(Self::get_database_path(), &secret); let db = JsonDatabase::new(Self::get_database_path(), &secret);
Self { RusTOTPony::new(db)
app: RusTOTPony::new(db),
}
} }
fn get_secret() -> String { fn get_secret() -> String {
@ -35,35 +31,35 @@ impl Cli {
// fn get_secret_from_storage() -> String { } // fn get_secret_from_storage() -> String { }
fn run(&mut self) { fn run() {
match self.get_cli_api_matches().subcommand() { match Self::get_cli_api_matches().subcommand() {
("dash", Some(_)) => { ("dash", Some(_)) => {
self.show_dashboard(); Self::show_dashboard();
} }
("list", Some(_)) => { ("list", Some(_)) => {
self.show_applications_list(false); Self::show_applications_list(false);
} }
// ("show-all", Some(_)) => { // ("show-all", Some(_)) => {
// self.show_applications_list(true); // Self::show_applications_list(true);
// } // }
// ("show", Some(sub_app)) => { // ("show", Some(sub_app)) => {
// let app_name: &str = sub_app // let app_name: &str = sub_app
// .value_of("APPNAME") // .value_of("APPNAME")
// .expect("Couldn't read APPNAME for 'show' command"); // .expect("Couldn't read APPNAME for 'show' command");
// self.show_application(app_name); // Self::show_application(app_name);
// } // }
("add", Some(sub_app)) => { ("add", Some(sub_app)) => {
let app_name: &str = sub_app let app_name: &str = sub_app
.value_of("APPNAME") .value_of("APPNAME")
.expect("Couldn't read APPNAME for 'add' command"); .expect("Couldn't read APPNAME for 'add' command");
let key: &str = sub_app.value_of("USERNAME").unwrap_or(""); 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)) => { ("delete", Some(sub_app)) => {
let app_name: &str = sub_app let app_name: &str = sub_app
.value_of("APPNAME") .value_of("APPNAME")
.expect("Couldn't read APPNAME for 'delete' command"); .expect("Couldn't read APPNAME for 'delete' command");
self.delete_application(app_name); Self::delete_application(app_name);
} }
("rename", Some(sub_app)) => { ("rename", Some(sub_app)) => {
let app_name: &str = sub_app let app_name: &str = sub_app
@ -72,18 +68,18 @@ impl Cli {
let new_name: &str = sub_app let new_name: &str = sub_app
.value_of("NEWNAME") .value_of("NEWNAME")
.expect("Couldn't read NEWNAME for 'rename' command"); .expect("Couldn't read NEWNAME for 'rename' command");
self.rename_application(app_name, new_name); Self::rename_application(app_name, new_name);
} }
("eradicate", Some(_)) => { ("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") App::new("🐴 RusTOTPony")
.version(env!("CARGO_PKG_VERSION")) .version(env!("CARGO_PKG_VERSION"))
.author("German Lashevich <german.lashevich@gmail.com>") .author("German Lashevich <german.lashevich@gmail.com>")
@ -128,8 +124,8 @@ impl Cli {
home.join(Path::new(CONFIG_PATH)) home.join(Path::new(CONFIG_PATH))
} }
fn show_dashboard(&self) { fn show_dashboard() {
match self.app.get_applications() { match Self::app().get_applications() {
Ok(apps) => { Ok(apps) => {
let mut is_first_iteration = true; let mut is_first_iteration = true;
let lines_count = apps.len() + 1; let lines_count = apps.len() + 1;
@ -167,11 +163,12 @@ impl Cli {
println!("[{:60}]", "=".repeat(idx as usize)); 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 // 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 output_table: HashMap<&str, Vec<&str>> = HashMap::new();
let mut applications_count = 0; let mut applications_count = 0;
let apps = match self.app.get_applications() { let apps = match app.get_applications() {
Ok(v) => v, Ok(v) => v,
Err(e) => { Err(e) => {
println!("{}", e); println!("{}", e);
@ -238,35 +235,35 @@ impl Cli {
println!("{}", header_row_delimiter); println!("{}", header_row_delimiter);
} }
fn show_application(&self, name: &str) { fn show_application(name: &str) {
println!("{:?}", self.app.get_application(name)); 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(); 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(_) => { Ok(_) => {
self.app.flush(); Self::app().flush();
println!("New application created: {}", name) println!("New application created: {}", name)
} }
Err(err) => println!("{} Aborting…", err), Err(err) => println!("{} Aborting…", err),
} }
} }
fn delete_application(&mut self, name: &str) { fn delete_application(name: &str) {
match self.app.delete_application(name) { match Self::app().delete_application(name) {
Ok(_) => { Ok(_) => {
self.app.flush(); Self::app().flush();
println!("Application '{}' successfully deleted", name) println!("Application '{}' successfully deleted", name)
} }
Err(err) => println!("Couldn't delete application '{}': {}", name, err), Err(err) => println!("Couldn't delete application '{}': {}", name, err),
}; };
} }
fn rename_application(&mut self, name: &str, newname: &str) { fn rename_application(name: &str, newname: &str) {
match self.app.rename_application(name, newname) { match Self::app().rename_application(name, newname) {
Ok(_) => { Ok(_) => {
self.app.flush(); Self::app().flush();
println!( println!(
"Application '{}' successfully renamed to '{}'", "Application '{}' successfully renamed to '{}'",
name, newname name, newname
@ -276,9 +273,9 @@ impl Cli {
}; };
} }
fn eradicate_database(&mut self) { fn eradicate_database() {
self.app.delete_all_applications(); Self::app().delete_all_applications();
self.app.flush(); Self::app().flush();
println!("Done."); println!("Done.");
} }
} }

@ -1,3 +1,5 @@
#![feature(fs_read_write)]
extern crate base32; extern crate base32;
extern crate crypto; extern crate crypto;
extern crate oath; extern crate oath;
@ -15,6 +17,7 @@ use rand::{OsRng, Rng};
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::{create_dir_all, File, OpenOptions}; use std::fs::{create_dir_all, File, OpenOptions};
use std::io::ErrorKind; use std::io::ErrorKind;
use std::io::Write;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
const DATABASE_VERSION: u8 = 1; const DATABASE_VERSION: u8 = 1;
@ -130,6 +133,7 @@ pub struct JsonDatabase {
secret: String, secret: String,
} }
const IV_SIZE: usize = 16;
impl JsonDatabase { impl JsonDatabase {
pub fn new(path: PathBuf, secret: &str) -> JsonDatabase { pub fn new(path: PathBuf, secret: &str) -> JsonDatabase {
JsonDatabase { JsonDatabase {
@ -139,22 +143,50 @@ impl JsonDatabase {
} }
fn read_database_file(&self) -> JsonDatabaseSchema { fn read_database_file(&self) -> JsonDatabaseSchema {
let file = match File::open(&self.file_path) { let data = match std::fs::read_to_string(&self.file_path) {
Ok(f) => f, Ok(d) => d,
Err(ref err) if err.kind() == ErrorKind::NotFound => return Self::get_empty_schema(), Err(ref err) if err.kind() == ErrorKind::NotFound => return Self::get_empty_schema(),
Err(err) => panic!("There was a problem opening file: {:?}", err), 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) { 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, Ok(f) => f,
Err(ref err) if err.kind() == ErrorKind::NotFound => self.create_database_file() Err(ref err) if err.kind() == ErrorKind::NotFound => self.create_database_file()
.expect("Couldn't create database file"), .expect("Couldn't create database file"),
Err(err) => panic!("Couldn't open database file: {:?}", err), 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 // Encrypt a buffer with the given key and iv using

Loading…
Cancel
Save