commit 73763c807a53f9351936f64d3011a4d4a7788814 Author: Mystikfluu Date: Mon Jan 2 19:28:19 2023 +0100 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d703159 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,353 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aead" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "3.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cipher" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "home" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747309b4b440c06d57b0b25f2aee03ee9b5e5397d288c60e21fc709bb98a7408" +dependencies = [ + "winapi", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipass_library" +version = "0.1.0" +dependencies = [ + "aes-gcm", + "brotli", + "hex", + "home", + "rand", + "rpassword", + "sha2", +] + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "polyval" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rpassword" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322" +dependencies = [ + "libc", + "rtoolbox", + "winapi", +] + +[[package]] +name = "rtoolbox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "universal-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8bbe622 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "ipass_library" +version = "0.1.0" +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" } + +[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 + +[lib] +name = "ip_lib" +path = "src/lib.rs" +doc = true +crate-type = ["staticlib"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..908f89d --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +"# IPass-library" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2ea44b1 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,298 @@ +use std::io::{Read, Write}; +use aes_gcm::{ + aead::{Aead, KeyInit}, + Aes256Gcm, Nonce +}; +use sha2::{Sha256, Digest}; +use std::fs::{ + File, + read_dir, + read_to_string +}; +use rand::{ + rngs::OsRng, + RngCore +}; + +pub fn get_args() -> Vec { + //! [0] = file path; [n>0] = argument + std::env::args().collect() +} + +// pub fn isync_upload() { +// todo!("Post request"); +// } + +// pub fn isync_download() -> String { +// todo!("Get request"); +// } + +pub fn import_file(location:&String) -> bool { + if std::path::Path::new(&(location.clone()+"/export.ipassx")).exists() { + let mut reader = brotli::Decompressor::new( + 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 = 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 = read_dir(get_ipass_folder()).unwrap(); + + for p in paths { + if let Ok(path) = p { + let content = &mut 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) = 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 = 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 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 = 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 = 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 { + prompt_answer_nolower(toprint).to_lowercase() +} + +pub fn prompt_answer_nolower(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_string(); +} + +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 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 = File::create(get_ipass_folder()+new_name+".ipass").unwrap(); + file.write_all(pw.as_bytes()).unwrap(); +} + +pub fn get_entries() -> std::fs::ReadDir { + read_dir(get_ipass_folder()).unwrap() +} + +pub fn random_password() -> String { + 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(); + } + return chars; +} \ No newline at end of file