diff --git a/Cargo.lock b/Cargo.lock index 373a23c..6408953 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 27a6603..fd805bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 diff --git a/TODO b/TODO new file mode 100644 index 0000000..7c538c0 --- /dev/null +++ b/TODO @@ -0,0 +1,21 @@ +# Change approach for saving and loading data + +## On load + +1. Read encrypted data into memory +2. Invoke decrypt function, which takes IV from the beginning of the data +3. Retrieve secret +4. Decrypt data +5. Unserialize data + +## On save + +1. Serialize data +2. Generate IV +3. Retrieve secret +4. Encrypt data +5. Concatenate IV + encrypted data +6. Save data to file + +# Implement secret retrieval from OS's keyring + diff --git a/src/bin/totp.rs b/src/bin/totp.rs index 8fb6297..26c8719 100644 --- a/src/bin/totp.rs +++ b/src/bin/totp.rs @@ -22,12 +22,19 @@ struct Cli { impl Cli { fn new() -> Self { - let db = JsonDatabase::new(Self::get_database_path()); + let secret = Self::get_secret(); + let db = JsonDatabase::new(Self::get_database_path(), &secret); Self { app: RusTOTPony::new(db), } } + fn get_secret() -> String { + return rpassword::prompt_password_stdout("Enter your database pass: ").unwrap(); + } + + // fn get_secret_from_storage() -> String { } + fn run(&mut self) { match self.get_cli_api_matches().subcommand() { ("dash", Some(_)) => { diff --git a/src/lib.rs b/src/lib.rs index bab7e2f..fff0348 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ extern crate base32; +extern crate crypto; extern crate oath; extern crate rand; extern crate serde_json; @@ -6,6 +7,11 @@ extern crate serde_json; #[macro_use] extern crate serde_derive; +use crypto::{aes, blockmodes, buffer, symmetriccipher}; +use crypto::buffer::{BufferResult, ReadBuffer, WriteBuffer}; + +use rand::{OsRng, Rng}; + use std::collections::HashMap; use std::fs::{create_dir_all, File, OpenOptions}; use std::io::ErrorKind; @@ -121,11 +127,15 @@ struct DatabaseContentSchema { pub struct JsonDatabase { file_path: PathBuf, + secret: String, } impl JsonDatabase { - pub fn new(path: PathBuf) -> JsonDatabase { - JsonDatabase { file_path: path } + pub fn new(path: PathBuf, secret: &str) -> JsonDatabase { + JsonDatabase { + file_path: path, + secret: String::from(secret), + } } fn read_database_file(&self) -> JsonDatabaseSchema { @@ -147,6 +157,105 @@ impl JsonDatabase { serde_json::to_writer(file, &content).expect("Couldn't write JSON 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, 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::::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, symmetriccipher::SymmetricCipherError> { + let mut decryptor = + aes::cbc_decryptor(aes::KeySize::KeySize256, key, iv, blockmodes::PkcsPadding); + + let mut final_result = Vec::::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 { let dir = std::env::home_dir().unwrap_or(PathBuf::from(".")); if let Some(parent_dir) = Path::new(&self.file_path).parent() {