Compare commits

...

8 Commits

Author SHA1 Message Date
Mystikfluu
0419033d81 Merge branch 'master' of https://github.com/002Hub/IPass 2023-06-23 15:21:28 +02:00
Mystikfluu
145d9edff1 make deepsource happier 2023-06-23 15:21:24 +02:00
Mystikfluu
ffd49c2819 update dependencies 2023-06-23 15:16:15 +02:00
deepsource-io[bot]
65ac1332b6
ci: Add .deepsource.toml 2023-06-23 13:05:16 +00:00
38a39f3d54 update ipass library 2023-04-17 11:48:49 +02:00
Mystikfluu
9a9a3c6654 fix bug in help command 2023-01-13 22:38:41 +01:00
Mystikfluu
3a80459b1c update library 2023-01-13 22:35:17 +01:00
Mystikfluu
f3c0b89bdf move utils to a seperate library 2023-01-02 20:21:11 +01:00
9 changed files with 1126 additions and 371 deletions

7
.deepsource.toml Normal file
View File

@ -0,0 +1,7 @@
version = 1
[[analyzers]]
name = "rust"
[analyzers.meta]
msrv = "stable"

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ comp.sh
report.json
*.ipass
*.ipassx
library/*

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "library"]
path = library
url = https://github.com/002Hub/IPass-library

1092
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,13 @@
[package]
name = "ipass"
version = "0.4.0"
version = "0.4.3"
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

View File

@ -1,3 +1,5 @@
fn main() {
if std::env::consts::OS == "windows" {
println!("cargo:rustc-link-arg=.res");
}
}

1
library Submodule

@ -0,0 +1 @@
Subproject commit 29f6a59b1fed936be05c4c512a4c4fc20d73429d

View File

@ -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,12 +52,17 @@ fn main() {
_ => help_message(&args),
}
if sync_enabled {
utils::export_file(&sync_loc);
ip_lib::export_file(&sync_loc);
}
}
fn ask_for_pw() -> String {
let output = rpassword::prompt_password("Please enter the master password: ").unwrap();
return output.replace(|c| matches!(c, '\n' | '\r'), "");
}
fn version_help(version: &str) {
let mut data = version.split(".");
let mut data = version.split('.');
print!("Major {} ", data.next().unwrap());
print!("Sub {} ", data.next().unwrap());
println!("Bugfix {}", data.next().unwrap());
@ -130,17 +135,21 @@ fn help_message(args: &Vec<String>) {
return;
}
if help_messages.contains_key(&args[2]) {
println!("{} {}", &args[2], &help_messages[&args[2]])
} else {
println!("Unknown option {}",&args[2])
}
}
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 +176,7 @@ fn add(args: &Vec<String>) {
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<char> = alphabet.chars().collect();
let mut chars_index: Vec<u8> = 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 +185,7 @@ fn add(args: &Vec<String>) {
}
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, ask_for_pw());
if !succeeded {
println!("You already have an entry with that name! Did you mean to use \"edit\"?");
return;
@ -201,11 +200,11 @@ fn get(args: &Vec<String>) {
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 mut data = entry.split(";");
let entry: String = ip_lib::get_entry(name, ask_for_pw()).expect("Failed to get entry");
let mut data = entry.split(';');
println!("Username: '{}' Password: '{}'",data.next().unwrap(),data.next().unwrap());
} else {
println!("No such entry!");
@ -219,16 +218,16 @@ fn changepw(args: &Vec<String>) {
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;
let mut output: String = String::default();
if args.len() != 4 {
output = rpassword::prompt_password("Please enter the new password: ").unwrap();
} else {
output = args[3].clone();
output.clone_from(&args[3]);
}
utils::edit_password(&args[2], output);
ip_lib::edit_password(&args[2], output, ask_for_pw());
println!("Changed Password of {}!", args[2]);
} else {
@ -242,16 +241,16 @@ fn changeuser(args: &Vec<String>) {
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;
let mut output: String = String::default();
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();
output.clone_from(&args[3]);
}
utils::edit_username(&args[2], output);
ip_lib::edit_username(&args[2], output, ask_for_pw());
println!("Changed Username of {}!", args[2]);
} else {
@ -265,9 +264,9 @@ fn rename(args: &Vec<String>) {
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], ask_for_pw());
println!("Renamed {} to {}", args[2], args[3]);
} else {
println!("No such file");
@ -281,9 +280,9 @@ fn remove(args: &Vec<String>) {
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 +296,16 @@ fn remove(args: &Vec<String>) {
fn import(args: &Vec<String>) {
//! 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();
location.clone_from(&args[2]);
} 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 +315,16 @@ fn import(args: &Vec<String>) {
fn export(args: &Vec<String>) {
//! 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();
location.clone_from(&args[2]);
} 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 +332,16 @@ fn export(args: &Vec<String>) {
}
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!");
@ -360,22 +359,22 @@ fn sync(args: &Vec<String>) {
match arg.as_str() {
"on" => {
let location: String;
let mut location: String = String::default();
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();
location.clone_from(&args[3]);
}
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 +383,14 @@ fn sync(args: &Vec<String>) {
},
"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 {

View File

@ -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<String> {
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<u8>) -> 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<u8> = 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<u8>,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();
}