Added zeroizing of passwords
This commit is contained in:
parent
165bd648aa
commit
7523034742
3 changed files with 51 additions and 29 deletions
|
|
@ -11,3 +11,4 @@ hex = "0.4.3"
|
||||||
rpassword = "7.3"
|
rpassword = "7.3"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
zeroize = { version = "1.8.2", features = ["derive"] }
|
||||||
|
|
|
||||||
59
src/main.rs
59
src/main.rs
|
|
@ -4,6 +4,8 @@ mod storage;
|
||||||
use args::{Cli, Commands};
|
use args::{Cli, Commands};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use rpassword::read_password;
|
use rpassword::read_password;
|
||||||
|
use std::io::Write;
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let cli: Cli = Cli::parse();
|
let cli: Cli = Cli::parse();
|
||||||
|
|
@ -13,55 +15,62 @@ fn main() {
|
||||||
|
|
||||||
let encryption_salt = storage::get_or_create_salt(salt_path);
|
let encryption_salt = storage::get_or_create_salt(salt_path);
|
||||||
|
|
||||||
let encryption_key: [u8; 32];
|
let mut encryption_key = if !std::path::Path::new(master_hash_path).exists() {
|
||||||
|
print!("No master password found. Setup required.\n");
|
||||||
if !std::path::Path::new(master_hash_path).exists() {
|
print!("Enter New Master Password: ");
|
||||||
print!("Enter Master Password : ");
|
std::io::stdout().flush().expect("Flush failed");
|
||||||
std::io::Write::flush(&mut std::io::stdout()).expect("Flush failed");
|
let mut masterp1 = read_password().expect("Failed to read password");
|
||||||
let masterp1 = read_password().expect("Failed to read password");
|
|
||||||
|
|
||||||
print!("Confirm Master Password: ");
|
print!("Confirm Master Password: ");
|
||||||
std::io::Write::flush(&mut std::io::stdout()).expect("Flush failed");
|
std::io::stdout().flush().expect("Flush failed");
|
||||||
let masterp2 = read_password().expect("Failed to read password");
|
let mut masterp2 = read_password().expect("Failed to read password");
|
||||||
|
|
||||||
if masterp1 == masterp2 {
|
if masterp1 == masterp2 {
|
||||||
println!("Creating new Master Password...");
|
println!("Creating new Master Password...");
|
||||||
let hash = storage::hash_master_password(&masterp1);
|
let hash = storage::hash_master_password(&masterp1);
|
||||||
std::fs::write(master_hash_path, hash).expect("Failed to save hash");
|
std::fs::write(master_hash_path, hash).expect("Failed to save hash");
|
||||||
encryption_key = storage::derive_key(&masterp1, &encryption_salt);
|
masterp2.zeroize();
|
||||||
|
storage::derive_key(&mut masterp1, &encryption_salt)
|
||||||
} else {
|
} else {
|
||||||
println!("Passwords did not match! Please try again.");
|
masterp1.zeroize();
|
||||||
return;
|
masterp2.zeroize();
|
||||||
|
panic!("Passwords did not match!");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
print!("Enter Master Password: ");
|
print!("Enter Master Password: ");
|
||||||
std::io::Write::flush(&mut std::io::stdout()).expect("Flush failed");
|
std::io::stdout().flush().expect("Flush failed");
|
||||||
let input = read_password().expect("Read failed");
|
let mut input = read_password().expect("Read failed");
|
||||||
let saved_hash = std::fs::read_to_string(master_hash_path).expect("Failed to read hash");
|
let saved_hash = std::fs::read_to_string(master_hash_path).expect("Failed to read hash");
|
||||||
|
|
||||||
if !storage::verify_master_password(&input, &saved_hash) {
|
if !storage::verify_master_password(&input, &saved_hash) {
|
||||||
|
input.zeroize();
|
||||||
println!("Wrong Master Password! Access Denied.");
|
println!("Wrong Master Password! Access Denied.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
encryption_key = storage::derive_key(&input, &encryption_salt);
|
storage::derive_key(&mut input, &encryption_salt)
|
||||||
}
|
};
|
||||||
|
|
||||||
let mut passwords = storage::load_passwords(file_path, &encryption_key);
|
let mut passwords = storage::load_passwords(file_path, &encryption_key);
|
||||||
|
|
||||||
match &cli.command {
|
match &cli.command {
|
||||||
Commands::Add(args) => {
|
Commands::Add(args) => {
|
||||||
print!("Enter password for {} : ", args.name);
|
print!("Enter password for {} : ", args.name);
|
||||||
std::io::Write::flush(&mut std::io::stdout()).expect("Flush failed");
|
std::io::stdout().flush().expect("Flush failed");
|
||||||
let p1 = read_password().expect("Failed to read password");
|
let mut p1 = read_password().expect("Failed to read password");
|
||||||
|
|
||||||
print!("Confirm Password : ");
|
print!("Confirm Password : ");
|
||||||
std::io::Write::flush(&mut std::io::stdout()).expect("Flush failed");
|
std::io::stdout().flush().expect("Flush failed");
|
||||||
let p2 = read_password().expect("Failed to read password");
|
let mut p2 = read_password().expect("Failed to read password");
|
||||||
|
|
||||||
if p1 == p2 {
|
if p1 == p2 {
|
||||||
passwords.insert(args.name.clone(), p1);
|
passwords.insert(args.name.clone(), p1.clone());
|
||||||
storage::save_passwords(file_path, &passwords, &encryption_key);
|
storage::save_passwords(file_path, &passwords, &encryption_key);
|
||||||
println!("Saved successfully!");
|
println!("Saved successfully!");
|
||||||
} else {
|
} else {
|
||||||
println!("Passwords did not match! Please try again.");
|
println!("Passwords did not match!");
|
||||||
}
|
}
|
||||||
|
p1.zeroize();
|
||||||
|
p2.zeroize();
|
||||||
}
|
}
|
||||||
Commands::Get(args) => match passwords.get(&args.name) {
|
Commands::Get(args) => match passwords.get(&args.name) {
|
||||||
Some(pw) => println!("Password for {} : {}", args.name, pw),
|
Some(pw) => println!("Password for {} : {}", args.name, pw),
|
||||||
|
|
@ -80,6 +89,12 @@ fn main() {
|
||||||
}
|
}
|
||||||
println!("-----------------------");
|
println!("-----------------------");
|
||||||
}
|
}
|
||||||
} // _ => {}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
encryption_key.zeroize();
|
||||||
|
|
||||||
|
for (_, v) in passwords.iter_mut() {
|
||||||
|
v.zeroize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ use chacha20poly1305::{
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
pub fn get_or_create_salt(path: &str) -> String {
|
pub fn get_or_create_salt(path: &str) -> String {
|
||||||
if std::path::Path::new(path).exists() {
|
if std::path::Path::new(path).exists() {
|
||||||
|
|
@ -24,7 +25,7 @@ pub fn get_or_create_salt(path: &str) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn derive_key(password: &str, salt: &str) -> [u8; 32] {
|
pub fn derive_key(password: &mut String, salt: &str) -> [u8; 32] {
|
||||||
let mut key = [0u8; 32];
|
let mut key = [0u8; 32];
|
||||||
let argon2 = Argon2::default();
|
let argon2 = Argon2::default();
|
||||||
|
|
||||||
|
|
@ -43,7 +44,7 @@ pub fn derive_key(password: &str, salt: &str) -> [u8; 32] {
|
||||||
} else {
|
} else {
|
||||||
panic!("Argon2 output too short! We need 32 bytes.");
|
panic!("Argon2 output too short! We need 32 bytes.");
|
||||||
}
|
}
|
||||||
|
password.zeroize();
|
||||||
key
|
key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,16 +61,19 @@ pub fn load_passwords(path: &str, key: &[u8; 32]) -> HashMap<String, String> {
|
||||||
let nonce = XNonce::from_slice(nonce_bytes);
|
let nonce = XNonce::from_slice(nonce_bytes);
|
||||||
|
|
||||||
let cipher = XChaCha20Poly1305::new(key.into());
|
let cipher = XChaCha20Poly1305::new(key.into());
|
||||||
let plaintext = cipher
|
let mut plaintext = cipher
|
||||||
.decrypt(nonce, ciphertext)
|
.decrypt(nonce, ciphertext)
|
||||||
.expect("Decryption failed! Data corrupted or wrong key.");
|
.expect("Decryption failed! Data corrupted or wrong key.");
|
||||||
|
|
||||||
let json_string = String::from_utf8(plaintext).expect("Invalid UTF-8");
|
let mut json_string = String::from_utf8(plaintext.clone()).expect("Invalid UTF-8");
|
||||||
serde_json::from_str(&json_string).unwrap_or_default()
|
let passwords = serde_json::from_str(&json_string).unwrap_or_default();
|
||||||
|
plaintext.zeroize();
|
||||||
|
json_string.zeroize();
|
||||||
|
passwords
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_passwords(path: &str, data: &HashMap<String, String>, key: &[u8; 32]) {
|
pub fn save_passwords(path: &str, data: &HashMap<String, String>, key: &[u8; 32]) {
|
||||||
let json = serde_json::to_string(data).expect("Failed to serialize");
|
let mut json = serde_json::to_string(data).expect("Failed to serialize");
|
||||||
|
|
||||||
let cipher = XChaCha20Poly1305::new(key.into());
|
let cipher = XChaCha20Poly1305::new(key.into());
|
||||||
let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
|
let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
|
||||||
|
|
@ -78,6 +82,8 @@ pub fn save_passwords(path: &str, data: &HashMap<String, String>, key: &[u8; 32]
|
||||||
.encrypt(&nonce, json.as_bytes())
|
.encrypt(&nonce, json.as_bytes())
|
||||||
.expect("Encryption failed");
|
.expect("Encryption failed");
|
||||||
|
|
||||||
|
json.zeroize();
|
||||||
|
|
||||||
let mut combined = nonce.to_vec();
|
let mut combined = nonce.to_vec();
|
||||||
combined.extend(ciphertext);
|
combined.extend(ciphertext);
|
||||||
fs::write(path, combined).expect("Failed to write to file");
|
fs::write(path, combined).expect("Failed to write to file");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue