Merge branch 'feature/database-encription'

refactoring/split-modules
German Lashevich 6 years ago
commit 8c72698939
No known key found for this signature in database
GPG Key ID: 3446FAE369C9A8B4

45
Cargo.lock generated

@ -42,6 +42,11 @@ name = "byte-tools"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byteorder"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.2"
@ -148,6 +153,11 @@ dependencies = [
"typenum 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "hex"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "hmac"
version = "0.1.1"
@ -174,13 +184,14 @@ dependencies = [
[[package]]
name = "keyring"
version = "0.5.1"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rpassword 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rpassword 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"secret-service 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -320,17 +331,6 @@ dependencies = [
"redox_syscall 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rpassword"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
"termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rpassword"
version = "2.0.0"
@ -378,10 +378,11 @@ dependencies = [
"base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ctrlc 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"keyring 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"keyring 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"oath 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rpassword 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
@ -491,14 +492,6 @@ dependencies = [
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "termios"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "textwrap"
version = "0.9.0"
@ -580,6 +573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf"
"checksum byte-tools 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0919189ba800c7ffe8778278116b7e0de3905ab81c72abb69c85cbfef7991279"
"checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23"
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
"checksum clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)" = "110d43e343eb29f4f51c1db31beb879d546db27998577e5715270a54bcf41d3f"
"checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e"
@ -594,10 +588,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum fuchsia-zircon-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "08b3a6f13ad6b96572b53ce7af74543132f1a7055ccceb6d073dd36c54481859"
"checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb"
"checksum generic-array 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe043cf9b85297937897087de81f590361686e1ac2d4d471b45435de5dfb6a6"
"checksum hex 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "459d3cf58137bb02ad4adeef5036377ff59f066dbb82517b7192e3a5462a2abc"
"checksum hmac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdb5aa9647ba4711e9d6968dc1c810cd23989ed435443ca962e1bf6d8b8b83ff"
"checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum keyring 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a13a4970024d20522520ecf2218f31fa9fc12dd4497a3901f79a137b425a184e"
"checksum keyring 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c503cc96b828521df71d8ef94edb17d3b7c0ef2caededd064b78fc5e1205de7b"
"checksum libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "36fbc8a8929c632868295d0178dd8f63fc423fd7537ad0738372bd010b3ac9b0"
"checksum nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2c5afeb0198ec7be8569d666644b574345aad2e95a53baf3a532da3e0f3fb32"
"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2"
@ -614,7 +609,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum rand 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9d5f78082e6a6d042862611e9640cf20776185fee506cf6cf67e93c6225cee31"
"checksum redox_syscall 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "07b8f011e3254d5a9b318fde596d409a0001c9ae4c6e7907520c2eaa4d988c99"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum rpassword 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8f0eee8d16609fa0ec2a13c8d812fc6049f71d3cdd93f6f713fedf6397868c5c"
"checksum rpassword 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d127299b02abda51634f14025aec43ae87a7aa7a95202b6a868ec852607d1451"
"checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a"
"checksum rust-gmp 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4cd7d57377b309a73f69e164109203aa9ab3fee6ea68ac5fb76e2edb50662e9b"
@ -631,7 +625,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
"checksum termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a"
"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
"checksum time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d788d3aa77bc0ef3e9621256885555368b47bd495c13dd2e7413c89f845520"
"checksum typenum 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13a99dc6780ef33c78780b826cf9d2a78840b72cae9474de4bcaf9051e60ebbd"

@ -14,14 +14,18 @@ version = "0.2.1"
[dependencies]
base32 = "0.3.1"
clap = "^2.29.0"
ctrlc = { version = "3.0", features = ["termination"] }
keyring = "0.5.1"
keyring = "0.6.0"
oath = "0.10.2"
rand = "0.4.1"
rpassword = "2.0.0"
rust-crypto = "0.2.36"
serde = "1.0.24"
serde_derive = "1.0.24"
serde_json = "1.0.8"
[dependencies.ctrlc]
features = ["termination"]
version = "3.0"
[profile.release]
debug = false
lto = true

@ -0,0 +1,5 @@
# Cache password ?
# Update README
# Implement secret retrieval from OS's keyring
# Decompose lib into modules
# Implement tests

@ -13,50 +13,52 @@ 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 {
let db = JsonDatabase::new(Self::get_database_path());
Self {
app: RusTOTPony::new(db),
}
fn app() -> RusTOTPony<JsonDatabase> {
let db = JsonDatabase::new(Self::get_database_path(), &Self::get_secret);
RusTOTPony::new(db)
}
fn run(&mut self) {
match self.get_cli_api_matches().subcommand() {
fn get_secret() -> String {
return rpassword::prompt_password_stdout("Enter your database pass: ").unwrap();
}
// fn get_secret_from_storage() -> String { }
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
@ -65,18 +67,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>")
@ -121,8 +123,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;
@ -164,11 +166,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);
@ -235,35 +238,38 @@ 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) {
let mut app = Self::app();
match app.create_application(name, username, &secret) {
Ok(_) => {
self.app.flush();
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) {
let mut app = Self::app();
match app.delete_application(name) {
Ok(_) => {
self.app.flush();
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) {
let mut app = Self::app();
match app.rename_application(name, newname) {
Ok(_) => {
self.app.flush();
app.flush();
println!(
"Application '{}' successfully renamed to '{}'",
name, newname
@ -273,9 +279,10 @@ impl Cli {
};
}
fn eradicate_database(&mut self) {
self.app.delete_all_applications();
self.app.flush();
fn eradicate_database() {
let mut app = Self::app();
app.delete_all_applications();
app.flush();
println!("Done.");
}
}

@ -1,4 +1,7 @@
#![feature(fs_read_write)]
extern crate base32;
extern crate crypto;
extern crate oath;
extern crate rand;
extern crate serde_json;
@ -6,9 +9,17 @@ extern crate serde_json;
#[macro_use]
extern crate serde_derive;
use crypto::buffer::{BufferResult, ReadBuffer, WriteBuffer};
use crypto::digest::Digest;
use crypto::sha2::Sha256;
use crypto::{aes, blockmodes, buffer, symmetriccipher};
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;
@ -121,30 +132,171 @@ struct DatabaseContentSchema {
pub struct JsonDatabase {
file_path: PathBuf,
secret_fn: &'static Fn() -> String,
}
const IV_SIZE: usize = 16;
const KEY_SIZE: usize = 32;
impl JsonDatabase {
pub fn new(path: PathBuf) -> JsonDatabase {
JsonDatabase { file_path: path }
pub fn new(path: PathBuf, secret_fn: &'static Fn() -> String) -> JsonDatabase {
JsonDatabase {
file_path: path,
secret_fn: secret_fn,
}
}
fn form_secret_key(input: &str) -> [u8; KEY_SIZE] {
let mut sha = Sha256::new();
sha.input_str(input);
let mut res: [u8; KEY_SIZE] = [0; KEY_SIZE];
sha.result(&mut res);
return res;
}
fn read_database_file(&self) -> JsonDatabaseSchema {
let file = match File::open(&self.file_path) {
Ok(f) => f,
let data = match std::fs::read(&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::form_secret_key((self.secret_fn)().as_str()));
serde_json::from_str(decrypted_data.as_str())
.expect("Couldn't parse JSON from database file")
}
fn decrypt_data(data: &[u8], key: &[u8]) -> String {
let iv = &data[..IV_SIZE];
String::from_utf8(Self::decrypt(&data[IV_SIZE..], key, iv).expect("Couldn't decrypt data"))
.ok()
.unwrap()
}
fn encrypt_data(data: &str, key: &[u8]) -> Vec<u8> {
let iv = Self::create_iv();
let encrypted_data =
Self::encrypt(data.as_bytes(), key, &iv).expect("Couldn't encrypt data");
[&iv, &encrypted_data[..]].concat()
}
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::form_secret_key((self.secret_fn)().as_str()));
file.write_all(&encrypted_data)
.expect("Couldn't write data to database file");
}
// Encrypt a buffer with the given key and iv using
// AES-256/CBC/Pkcs encryption.
fn encrypt(
data: &[u8],
key: &[u8],
iv: &[u8],
) -> Result<Vec<u8>, symmetriccipher::SymmetricCipherError> {
// Create an encryptor instance of the best performing
// type available for the platform.
let mut encryptor =
aes::cbc_encryptor(aes::KeySize::KeySize256, key, iv, blockmodes::PkcsPadding);
// Each encryption operation encrypts some data from
// an input buffer into an output buffer. Those buffers
// must be instances of RefReaderBuffer and RefWriteBuffer
// (respectively) which keep track of how much data has been
// read from or written to them.
let mut final_result = Vec::<u8>::new();
let mut read_buffer = buffer::RefReadBuffer::new(data);
let mut buffer = [0; 4096];
let mut write_buffer = buffer::RefWriteBuffer::new(&mut buffer);
// Each encryption operation will "make progress". "Making progress"
// is a bit loosely defined, but basically, at the end of each operation
// either BufferUnderflow or BufferOverflow will be returned (unless
// there was an error). If the return value is BufferUnderflow, it means
// that the operation ended while wanting more input data. If the return
// value is BufferOverflow, it means that the operation ended because it
// needed more space to output data. As long as the next call to the encryption
// operation provides the space that was requested (either more input data
// or more output space), the operation is guaranteed to get closer to
// completing the full operation - ie: "make progress".
//
// Here, we pass the data to encrypt to the enryptor along with a fixed-size
// output buffer. The 'true' flag indicates that the end of the data that
// is to be encrypted is included in the input buffer (which is true, since
// the input data includes all the data to encrypt). After each call, we copy
// any output data to our result Vec. If we get a BufferOverflow, we keep
// going in the loop since it means that there is more work to do. We can
// complete as soon as we get a BufferUnderflow since the encryptor is telling
// us that it stopped processing data due to not having any more data in the
// input buffer.
loop {
let result = try!(encryptor.encrypt(&mut read_buffer, &mut write_buffer, true));
// "write_buffer.take_read_buffer().take_remaining()" means:
// from the writable buffer, create a new readable buffer which
// contains all data that has been written, and then access all
// of that data as a slice.
final_result.extend(
write_buffer
.take_read_buffer()
.take_remaining()
.iter()
.map(|&i| i),
);
match result {
BufferResult::BufferUnderflow => break,
BufferResult::BufferOverflow => {}
}
}
Ok(final_result)
}
// Decrypts a buffer with the given key and iv using
// AES-256/CBC/Pkcs encryption.
fn decrypt(
encrypted_data: &[u8],
key: &[u8],
iv: &[u8],
) -> Result<Vec<u8>, symmetriccipher::SymmetricCipherError> {
let mut decryptor =
aes::cbc_decryptor(aes::KeySize::KeySize256, key, iv, blockmodes::PkcsPadding);
let mut final_result = Vec::<u8>::new();
let mut read_buffer = buffer::RefReadBuffer::new(encrypted_data);
let mut buffer = [0; 4096];
let mut write_buffer = buffer::RefWriteBuffer::new(&mut buffer);
loop {
let result = try!(decryptor.decrypt(&mut read_buffer, &mut write_buffer, true));
final_result.extend(
write_buffer
.take_read_buffer()
.take_remaining()
.iter()
.map(|&i| i),
);
match result {
BufferResult::BufferUnderflow => break,
BufferResult::BufferOverflow => {}
}
}
Ok(final_result)
}
fn create_database_file(&self) -> Result<File, std::io::Error> {

Loading…
Cancel
Save