use std::path::PathBuf;
use std::fs;
use base64::{encode, decode};
use std::io;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::BufRead;
use std::io::prelude::*;
use flypto::argon2::Argon2Hash;
use flypto::aes::AES;
use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
use crate::config::Config;
use std::sync::Arc;
use crate::spmgd::vault::genpwd::PwdGen;
pub struct Lock {
pub lock_path: PathBuf,
cache: HashMap<u32, Vec<u8>>,
cache_expiration: HashMap<u32, u64>,
config: Arc<Config>,
pub charset: Vec<char>
}
impl Lock {
pub fn new(config: Arc<Config>, lock_path: PathBuf) -> Self {
let charset_path = "/etc/spmg/character-set";
let charset = PwdGen::read_charset(charset_path).expect("Failed to read character set!");
Lock {
lock_path,
cache_expiration: HashMap::new(),
cache: HashMap::new(),
config,
charset
}
}
pub fn init(&mut self, ppid: u32, password: &str) {
if !self.lock_path.exists() {
let derived_key = Argon2Hash::derive_key(password, &self.config.argon_salt);
let mut lock_aes = AES::new(Some(derived_key.clone()));
match lock_aes.encrypt(self.config.lock_token.as_bytes()) {
Ok(encrypted) => {
let lock_line = format!("spmg-lock {}", encrypted);
fs::write(&self.lock_path, lock_line); self.set_cached_key(ppid, derived_key);
println!("File created and written: {}", self.lock_path.display());
}
Err(e) => eprintln!("Encryption failed: {}", e),
};
} else {
println!("File already exists: {}", self.lock_path.display());
}
}
fn set_cached_key(&mut self, ppid: u32, key: Vec<u8>) {
let time_now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_millis() as u64;
if !self.cache.contains_key(&ppid) {
self.cache.insert(ppid, key);
self.cache_expiration.insert(
ppid, time_now + self.config.cache_expiration_time * 1000
);
} else {
self.cache_expiration.insert(
ppid, time_now + self.config.cache_expiration_time * 1000
);
}
}
pub fn get_cached_key(&mut self, ppid: u32) -> io::Result<Vec<u8>> {
if self.cache.contains_key(&ppid) {
let expires = self.cache_expiration.get(&ppid).unwrap().clone();
let time_now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_millis() as u64;
if time_now < expires {
Ok(self.cache.get(&ppid).unwrap().clone())
} else {
self.cache_expiration.remove(&ppid);
self.cache.remove(&ppid);
Err(io::Error::new(io::ErrorKind::TimedOut, "Cached key expired!"))
}
} else {
Err(io::Error::new(io::ErrorKind::NotFound, "Key not cached for parent process!"))
}
}
pub fn clear_cache(&mut self, ppid: u32) -> io::Result<String> {
if self.cache.contains_key(&ppid) {
self.cache_expiration.remove(&ppid);
self.cache.remove(&ppid);
Ok(String::from("OK"))
} else {
let err = io::Error::new(io::ErrorKind::NotFound, "Key not cached for parent process!");
Err(err)
}
}
pub fn get_cache_expiration(&mut self, ppid: u32) -> io::Result<u64> {
if self.cache.contains_key(&ppid) {
let expires = self.cache_expiration.get(&ppid).unwrap().clone();
Ok(expires)
} else {
Err(io::Error::new(io::ErrorKind::NotFound, "Key not cached for parent process!"))
}
}
pub fn get(&mut self, ppid: u32, identifier: &str, lock_key: &str) -> io::Result<String> {
if self.lock_path.exists() {
println!("Get password request");
println!("PPID: {}, Identifier: {}", ppid, identifier);
let file = File::open(self.lock_path.clone());
let reader = std::io::BufReader::new(file.unwrap());
let line_prefix = format!("{} ", identifier);
for line in reader.lines() {
let line = line.unwrap();
if line.starts_with(&line_prefix) {
let mut line_parts = line.splitn(2, ' ');
let pwd_id = line_parts.next().unwrap_or("");
let pwd_enc = line_parts.next().unwrap_or("");
let mut lock_aes = AES::new(Some(
decode(lock_key).unwrap()
));
match lock_aes.decrypt(pwd_enc) {
Ok(decrypted) => {
self.set_cached_key(ppid, decode(lock_key).unwrap());
let plain_password = String::from_utf8_lossy(&decrypted).to_string();
return Ok(
PwdGen::strip_padding(&plain_password).unwrap()
);
}
Err(e) => {
println!("Decryption failed!");
return Err(io::Error::new(io::ErrorKind::InvalidData, "Decryption failed!"));
}
};
}
}
let err_str = format!("Password not found by identifier: {}", identifier);
Err(io::Error::new(io::ErrorKind::NotFound, err_str))
} else {
Err(io::Error::new(io::ErrorKind::NotConnected, "Lock file does not exist!"))
}
}
pub fn add(&mut self, ppid: u32, identifier: &str, password: &str, lock_key: &str) -> io::Result<String> {
if self.lock_path.exists() {
println!("Add password: {}", identifier);
let file = File::open(self.lock_path.clone());
let reader = std::io::BufReader::new(file.unwrap());
let line_prefix = format!("{} ", identifier);
for line in reader.lines() {
let line = line.unwrap();
if line.starts_with(&line_prefix) {
return Err(io::Error::new(io::ErrorKind::AlreadyExists, "Password with provided identifier already stored!"));
}
}
let padded = PwdGen::add_padding(32, 64, &self.charset, String::from(password));
let mut lock_aes = AES::new(Some(decode(lock_key).unwrap()));
match lock_aes.encrypt(padded.as_bytes()) {
Ok(encrypted) => {
self.set_cached_key(ppid, decode(lock_key).unwrap());
let lock_line = format!("{} {}", identifier, encrypted);
let mut file = OpenOptions::new()
.write(true)
.append(true)
.open(&self.lock_path)
.unwrap();
if let Err(e) = writeln!(file, "\n{}", lock_line) {
eprintln!("Couldn't write to file: {}", e);
}
Ok(String::from("OK")) }
Err(e) => {
Err(io::Error::new(io::ErrorKind::InvalidData, "Encryption failed!"))
}
}
} else {
Err(io::Error::new(io::ErrorKind::NotConnected, "Lock file does not exist!"))
}
}
}