From f3c0b89bdfa6ef0153c4d735c413c06786d447f1 Mon Sep 17 00:00:00 2001 From: Mystikfluu Date: Mon, 2 Jan 2023 20:21:11 +0100 Subject: [PATCH] move utils to a seperate library --- .gitignore | 3 +- .gitmodules | 3 + Cargo.lock | 12 ++- Cargo.toml | 11 +-- build.rs | 4 +- library | 1 + src/main.rs | 84 ++++++++--------- src/utils.rs | 257 --------------------------------------------------- 8 files changed, 59 insertions(+), 316 deletions(-) create mode 100644 .gitmodules create mode 160000 library delete mode 100644 src/utils.rs diff --git a/.gitignore b/.gitignore index 43befc1..169ed2a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ comp.bat comp.sh report.json *.ipass -*.ipassx \ No newline at end of file +*.ipassx +library/* \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..7051d49 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "library"] + path = library + url = https://github.com/002Hub/IPass-library diff --git a/Cargo.lock b/Cargo.lock index e4a112f..a98aaea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -192,8 +192,8 @@ dependencies = [ ] [[package]] -name = "ipass" -version = "0.4.0" +name = "ip_lib" +version = "0.1.0" dependencies = [ "aes-gcm", "brotli", @@ -204,6 +204,14 @@ dependencies = [ "sha2", ] +[[package]] +name = "ipass" +version = "0.4.1" +dependencies = [ + "ip_lib", + "rpassword", +] + [[package]] name = "libc" version = "0.2.139" diff --git a/Cargo.toml b/Cargo.toml index 81d43df..e6653c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,22 +1,17 @@ [package] name = "ipass" -version = "0.4.0" +version = "0.4.1" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -home = "0.5.4" rpassword = "7.2" -rand = { features = ["std"], default-features = false, version = "0.8.5" } -aes-gcm = { features = ["alloc", "aes"], default-features = false, version = "0.10.1" } -sha2 = { default-features = false, version = "0.10.6" } -hex = "0.4.3" -brotli = { features = ["std"], default-features = false, version = "3.3.4" } +ip_lib = { path = "library/" } [profile.release] opt-level = 'z' # Optimize for size lto = true # Enable link-time optimization codegen-units = 1 # Reduce number of codegen units to increase optimizations panic = 'abort' # Abort on panic -# strip = true # Strip symbols from binary; remove pdb +# strip = true # Strip symbols from binary; remove pdb \ No newline at end of file diff --git a/build.rs b/build.rs index 8ae7c60..ab9074c 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,5 @@ fn main() { - println!("cargo:rustc-link-arg=.res"); + if std::env::consts::OS == "windows" { + println!("cargo:rustc-link-arg=.res"); + } } \ No newline at end of file diff --git a/library b/library new file mode 160000 index 0000000..380ae20 --- /dev/null +++ b/library @@ -0,0 +1 @@ +Subproject commit 380ae204378be877fffb7ee7f9167748defc2156 diff --git a/src/main.rs b/src/main.rs index 20c1c13..3f3b5f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,17 +3,16 @@ //! IPass Password Manager +extern crate ip_lib; + use std::collections::HashMap; use std::io::Write; -use rand::rngs::OsRng; -use rand::RngCore; -mod utils; fn main() { let version = option_env!("CARGO_PKG_VERSION").unwrap_or("x.x.x"); println!("IPass v{}\n", version); - let args = utils::get_args(); + let args = ip_lib::get_args(); if args.len() < 2 { help_message(&args); @@ -22,15 +21,16 @@ fn main() { let mode: &str = &args[1].trim().to_lowercase().to_owned(); - let sync_file_loc = utils::get_home_folder_str()+"/.sync.ipass"; + let sync_file_loc = ip_lib::get_home_folder_str()+"/.sync.ipass"; let sync_file_path = std::path::Path::new(&sync_file_loc); let sync_enabled = sync_file_path.clone().exists(); let sync_loc: String; if sync_enabled { + // println!("Sync enabled, syncing..."); sync_loc = std::fs::read_to_string(sync_file_path).expect("Should have been able to read the sync file"); - utils::import_file(&sync_loc); + ip_lib::import_file(&sync_loc); } else { sync_loc = "".to_string(); //sync is disabled, no location needed } @@ -52,7 +52,7 @@ fn main() { _ => help_message(&args), } if sync_enabled { - utils::export_file(&sync_loc); + ip_lib::export_file(&sync_loc); } } @@ -134,13 +134,13 @@ fn help_message(args: &Vec) { } fn list() { - let mut paths: std::fs::ReadDir = std::fs::read_dir(utils::get_ipass_folder()).unwrap(); + let mut paths = ip_lib::get_entries(); let mut has_entry:bool = false; println!("Total entries: {}\n", paths.count()); - paths = std::fs::read_dir(utils::get_ipass_folder()).unwrap(); + paths = ip_lib::get_entries(); for path in paths { has_entry = true; @@ -167,17 +167,7 @@ fn add(args: &Vec) { if args.len() > 4 { pw = username+";"+args[4].trim(); } else { - let alphabet: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!\"$%&/()=?{[]}\\,.-;:_><|+*#'"; - let alph_len: usize = alphabet.chars().count(); - let char_set:Vec = alphabet.chars().collect(); - let mut chars_index: Vec = vec![0;20]; - OsRng.fill_bytes(&mut chars_index); - let mut chars: String = String::new(); - - for index in chars_index { - // println!("{} - {} - {}",index,(index as usize)%(alph_len-1),alph_len); - chars += &char_set[(index as usize)%(alph_len-1)].to_string(); - } + let chars = ip_lib::random_password(); pw = username+";"+chars.as_str(); println!("Using auto generated password"); @@ -186,7 +176,7 @@ fn add(args: &Vec) { } println!("Adding password for {}",args[2]); - let succeeded: bool = utils::create_entry(&args[2], pw); + let succeeded: bool = ip_lib::create_entry(&args[2], pw); if !succeeded { println!("You already have an entry with that name! Did you mean to use \"edit\"?"); return; @@ -201,10 +191,10 @@ fn get(args: &Vec) { return; } let name: &String = &args[2]; - let filepath = &(utils::get_ipass_folder()+name+".ipass"); + let filepath = &(ip_lib::get_ipass_folder()+name+".ipass"); if std::path::Path::new(filepath).exists() { println!("Getting entry"); - let entry: String = utils::get_entry(name); + let entry: String = ip_lib::get_entry(name); let mut data = entry.split(";"); println!("Username: '{}' Password: '{}'",data.next().unwrap(),data.next().unwrap()); } else { @@ -219,7 +209,7 @@ fn changepw(args: &Vec) { println!("Invalid usage of \"changepw\""); return; } - let filepath = &(utils::get_ipass_folder()+&args[2]+".ipass"); + let filepath = &(ip_lib::get_ipass_folder()+&args[2]+".ipass"); if std::path::Path::new(filepath).exists() { let output: String; if args.len() != 4 { @@ -228,7 +218,7 @@ fn changepw(args: &Vec) { output = args[3].clone(); } - utils::edit_password(&args[2], output); + ip_lib::edit_password(&args[2], output); println!("Changed Password of {}!", args[2]); } else { @@ -242,16 +232,16 @@ fn changeuser(args: &Vec) { println!("Invalid usage of \"changeuser\""); return; } - let filepath = &(utils::get_ipass_folder()+&args[2]+".ipass"); + let filepath = &(ip_lib::get_ipass_folder()+&args[2]+".ipass"); if std::path::Path::new(filepath).exists() { let output: String; if args.len() != 4 { - output = utils::prompt_answer("Enter new Username: ".to_string()); + output = ip_lib::prompt_answer("Enter new Username: ".to_string()); } else { output = args[3].clone(); } - utils::edit_username(&args[2], output); + ip_lib::edit_username(&args[2], output); println!("Changed Username of {}!", args[2]); } else { @@ -265,9 +255,9 @@ fn rename(args: &Vec) { println!("Invalid usage of \"rename\""); return; } - let filepath = &(utils::get_ipass_folder()+&args[2]+".ipass"); + let filepath = &(ip_lib::get_ipass_folder()+&args[2]+".ipass"); if std::path::Path::new(filepath).exists() { - utils::rename(&args[2],&args[3]); + ip_lib::rename(&args[2],&args[3]); println!("Renamed {} to {}", args[2], args[3]); } else { println!("No such file"); @@ -281,9 +271,9 @@ fn remove(args: &Vec) { return; } let name = &args[2]; - let filepath = &(utils::get_ipass_folder()+name+".ipass"); + let filepath = &(ip_lib::get_ipass_folder()+name+".ipass"); if std::path::Path::new(filepath).exists() { - if utils::prompt_answer(format!("Are you sure you want to delete {}? [y/N] ", name)) == "y" { + if ip_lib::prompt_answer(format!("Are you sure you want to delete {}? [y/N] ", name)) == "y" { std::fs::remove_file(filepath).unwrap(); println!("Removed entry \"{}\"", name); } else { @@ -297,16 +287,16 @@ fn remove(args: &Vec) { fn import(args: &Vec) { //! arguments: program import {location} - let mut location = utils::get_home_folder_str(); + let mut location = ip_lib::get_home_folder_str(); if args.len() == 3 { location = args[2].clone(); } else { - if utils::prompt_answer(format!("No location specified, defaulting to {} continue? [Y/n] ", location.clone())) == "n" { + if ip_lib::prompt_answer(format!("No location specified, defaulting to {} continue? [Y/n] ", location.clone())) == "n" { println!("Operation cancelled"); return; } } - if utils::import_file(&location) { + if ip_lib::import_file(&location) { println!("Imported all entries!"); } else { println!("No such file found!"); @@ -316,16 +306,16 @@ fn import(args: &Vec) { fn export(args: &Vec) { //! arguments: program export {location} - let mut location = utils::get_home_folder_str(); + let mut location = ip_lib::get_home_folder_str(); if args.len() == 3 { location = args[2].clone(); } else { - if utils::prompt_answer(format!("No location specified, defaulting to {} continue? [Y/n] ", location.clone())) == "n" { + if ip_lib::prompt_answer(format!("No location specified, defaulting to {} continue? [Y/n] ", location.clone())) == "n" { println!("Operation cancelled"); return; } } - if utils::export_file(&location) { + if ip_lib::export_file(&location) { println!("Saved at: '{}/export.ipassx'", location); } else { println!("Failed saving at '{}/export.ipassx' does it exist?",location); @@ -333,16 +323,16 @@ fn export(args: &Vec) { } fn clear() { - if utils::prompt_answer("Are you sure you want to clear everything? [y/N] ".to_string()) != "y" { + if ip_lib::prompt_answer("Are you sure you want to clear everything? [y/N] ".to_string()) != "y" { println!("operation cancelled!"); return; } - let paths = std::fs::read_dir(utils::get_ipass_folder()).unwrap(); + let paths = std::fs::read_dir(ip_lib::get_ipass_folder()).unwrap(); for path in paths { if let Ok(p) = path { - std::fs::remove_file(utils::get_ipass_folder()+"/"+p.file_name().into_string().unwrap().as_str()).unwrap(); + std::fs::remove_file(ip_lib::get_ipass_folder()+"/"+p.file_name().into_string().unwrap().as_str()).unwrap(); } } println!("Cleared all entries!"); @@ -363,19 +353,19 @@ fn sync(args: &Vec) { let location: String; if args.len() < 4 { - location = utils::prompt_answer("No location specified, please provide the location of the file you want to sync: ".to_string()); + location = ip_lib::prompt_answer_nolower("No location specified, please provide the location of the file you want to sync: ".to_string()); } else { location = args[3].clone(); } - let mut sync_file = std::fs::File::create(utils::get_home_folder_str()+"/.sync.ipass").expect("could not open sync file"); + let mut sync_file = std::fs::File::create(ip_lib::get_home_folder_str()+"/.sync.ipass").expect("could not open sync file"); sync_file.write(location.as_bytes()).expect("could not write to sync file"); - if !utils::export_file(&location) { + if !ip_lib::export_file(&location) { eprintln!("Test sync error, make sure you specified a valid folder!"); } else { println!("Sync is now Enabled!"); - println!("Sync file: {}",location); + println!("Sync file: {}\\export.ipassx",location); } @@ -384,14 +374,14 @@ fn sync(args: &Vec) { }, "off" => { - let sync_file_loc = utils::get_home_folder_str()+"/.sync.ipass"; + let sync_file_loc = ip_lib::get_home_folder_str()+"/.sync.ipass"; let sync_file_path = std::path::Path::new(&sync_file_loc); let sync_enabled = sync_file_path.clone().exists(); if sync_enabled { - std::fs::remove_file(utils::get_home_folder_str()+"/.sync.ipass").expect("could not disable sync, is it already disabled?"); + std::fs::remove_file(ip_lib::get_home_folder_str()+"/.sync.ipass").expect("could not disable sync, is it already disabled?"); println!("Sync is now Disabled!"); } else { diff --git a/src/utils.rs b/src/utils.rs deleted file mode 100644 index e7d962f..0000000 --- a/src/utils.rs +++ /dev/null @@ -1,257 +0,0 @@ -use std::io::{Read, Write}; -use aes_gcm::{ - aead::{Aead, KeyInit}, - Aes256Gcm, Nonce -}; -use sha2::{Sha256, Digest}; - -pub fn get_args() -> Vec { - std::env::args().collect() // [0] = file path; [n>0] = argument -} - -pub fn import_file(location:&String) -> bool { - if std::path::Path::new(&(location.clone()+"/export.ipassx")).exists() { - let mut reader = brotli::Decompressor::new( - std::fs::File::open(location.clone()+"/export.ipassx").unwrap(), - 4096, // buffer size - ); - let mut content: String = String::new(); - let mut buf = [0u8; 4096]; - loop { - match reader.read(&mut buf[..]) { - Err(e) => { - if let std::io::ErrorKind::Interrupted = e.kind() { - continue; - } - panic!("{}", e); - } - Ok(size) => { - if size == 0 { - break; - } - content += &std::str::from_utf8(&buf[..size]).unwrap(); - } - } - } - - let lines = content.lines(); - let mut name = ""; - for i in lines { - if name == "" { - name = i; - continue; - } - - let mut file = std::fs::File::create(format!("{}/{}.ipass",get_ipass_folder(), name)).unwrap(); - file.write_all(i.as_bytes()).unwrap(); - name = ""; - } - return true; - } else { - return false; - } -} - -pub fn export_file(location:&String) -> bool { - let mut collected_data: String = String::new(); - - let paths = std::fs::read_dir(get_ipass_folder()).unwrap(); - - for p in paths { - if let Ok(path) = p { - let content = &mut std::fs::read_to_string(get_ipass_folder()+&path.file_name().into_string().unwrap()).expect("Should have been able to read the file"); - collected_data += format!("{}\n{}\n", path.file_name().into_string().unwrap().replace(".ipass", ""),content).as_str(); - } - } - - if let Ok(file) = std::fs::File::create(location.clone()+"/export.ipassx") { - let mut writer = brotli::CompressorWriter::new( - file, - 4096, - 11, - 22); - - match writer.write_all(collected_data.as_bytes()) { - Err(e) => panic!("{}", e), - Ok(_) => {}, - } - - return true; - } else { - return false; - } -} - -fn vecu8_to_string(vec: Vec) -> String { - let mut do_print_warning = false; - let mut out: String = String::new(); - for ind in vec { - if let Ok(a) = std::str::from_utf8(&[ind]) { - out += a; - } else { - do_print_warning = true; - eprintln!("[WARNING] malformed character {}",ind); - let mut temp_vec: Vec = Vec::new(); - temp_vec.insert(0,ind%128); - out += vecu8_to_string(temp_vec).as_str(); - } - } - if do_print_warning { - println!("[WARNING] Output may be corrupt"); - } - return out; -} - -fn encrypt_pass(nonce_arg:String, pass: String,mpw: String) -> String { - let mut nonce_argument = String::new(); - if nonce_arg.len() < 12 { - nonce_argument = nonce_arg.clone() + &" ".repeat(12-nonce_arg.len()); - } - if nonce_arg.len() > 12 { - nonce_argument = nonce_arg[0..12].to_string(); - } - - let mut nonce_hasher = Sha256::new(); - nonce_hasher.update(nonce_argument.as_bytes()); - - let nonce_final = &nonce_hasher.finalize()[0..12]; - - - let mut hasher = Sha256::new(); - hasher.update(mpw.as_bytes()); - - let master_pw = &hasher.finalize(); - - let cipher = Aes256Gcm::new(master_pw); - let nonce = Nonce::from_slice(nonce_final); // 96-bits; unique per message - let ciphertext = cipher.encrypt(nonce, pass.as_ref()).unwrap(); - return hex::encode(ciphertext); -} - -fn decrypt_pass(nonce_arg:String, pass: Vec,mpw: String) -> String { - let mut nonce_argument = String::new(); - if nonce_arg.len() < 12 { - nonce_argument = nonce_arg.clone() + &" ".repeat(12-nonce_arg.len()); - } - if nonce_arg.len() > 12 { - nonce_argument = nonce_arg[0..12].to_string(); - } - - let mut nonce_hasher = Sha256::new(); - nonce_hasher.update(nonce_argument.as_bytes()); - - let nonce_final = &nonce_hasher.finalize()[0..12]; - - let mut hasher = Sha256::new(); - hasher.update(mpw.as_bytes()); - - let master_pw = &hasher.finalize(); - let cipher = Aes256Gcm::new(master_pw); - let nonce = Nonce::from_slice(nonce_final); // 96-bits; unique per message - - let plaintext = cipher.decrypt(nonce, pass.as_ref()); - match plaintext { - Ok(res) => { - return vecu8_to_string(res); - } - Err(_) => { - eprintln!("[ERROR] Error decrypting data, check your master password"); - std::process::exit(1); - } - } -} - -pub fn get_home_folder_str() -> String { - match home::home_dir() { - Some(path) => { - let p = path.to_str(); - match p { - Some(pa) => return pa.to_owned(), - None => return "".to_owned(), - } - }, - None => return "".to_owned(), - } -} - -pub fn get_ipass_folder() -> String { - let path = get_home_folder_str()+"/.IPass/"; - std::fs::create_dir_all(&path).unwrap(); - return path; -} - -pub fn create_entry(name: &String, pw: String) -> bool { - if std::path::Path::new(&(get_ipass_folder()+name+".ipass")).exists() { - return false; - } - let mpw = ask_for_pw(); - // println!("{pw}"); - let pw = encrypt_pass(name.to_owned(), pw,mpw); - let mut file = std::fs::File::create(get_ipass_folder()+name+".ipass").unwrap(); - file.write_all(pw.as_bytes()).unwrap(); - return true; -} - -fn read_entry(name:&String,mpw:String) -> String { - let content = &mut std::fs::read_to_string(get_ipass_folder()+name+".ipass").expect("Should have been able to read the file"); - return decrypt_pass(name.to_owned(),hex::decode(content).unwrap(),mpw).to_owned(); -} - -pub fn get_entry(name:&String) -> String { - let mpw = ask_for_pw(); - return read_entry(name,mpw); -} - -pub fn edit_password(name:&String, password:String) { - let mpw = ask_for_pw(); - let entry = read_entry(name, mpw.clone()); - // println!("entry: {entry}"); - let mut parts = entry.split(";"); - let username = parts.next().unwrap().to_string(); - let _old_password = parts.next().unwrap(); - let data = encrypt_pass(name.to_owned(), username+";"+password.as_str(),mpw); - let mut file = std::fs::File::create(get_ipass_folder()+name+".ipass").unwrap(); - file.write_all(data.as_bytes()).unwrap(); -} - -pub fn edit_username(name:&String, username: String) { - let mpw = ask_for_pw(); - let entry = read_entry(name, mpw.clone()); - // println!("entry: {entry}"); - let mut parts = entry.split(";"); - let _old_username = parts.next().unwrap(); - let password = parts.next().unwrap(); - let data = encrypt_pass(name.to_owned(), username+";"+password,mpw); - let mut file = std::fs::File::create(get_ipass_folder()+name+".ipass").unwrap(); - file.write_all(data.as_bytes()).unwrap(); -} - -fn ask_for_pw() -> String { - let output = rpassword::prompt_password("Please enter the master password: ").unwrap(); - return output.replace("\n", "").replace("\r",""); -} - -pub fn prompt_answer(toprint: String) -> String { - print!("{toprint}"); - std::io::stdout().flush().unwrap(); - let mut choice = String::new(); - std::io::stdin().read_line(&mut choice).expect("Failed to read choice"); - - return choice.trim().to_lowercase(); -} - -pub fn rename(name: &String, new_name: &String) { - if !std::path::Path::new(&(get_ipass_folder()+name+".ipass")).exists() { - return; - } - if std::path::Path::new(&(get_ipass_folder()+new_name+".ipass")).exists() { - return; - } - let content = &mut std::fs::read_to_string(get_ipass_folder()+name+".ipass").expect("Should have been able to read the file"); - let mpw = ask_for_pw(); - let mut pw = decrypt_pass(name.to_owned(),hex::decode(content).unwrap(),mpw.clone()).to_owned(); - - pw = encrypt_pass(new_name.to_owned(), pw,mpw); - let mut file = std::fs::File::create(get_ipass_folder()+new_name+".ipass").unwrap(); - file.write_all(pw.as_bytes()).unwrap(); -} \ No newline at end of file