libspmg 0.2.1

Secure password manager library
Documentation


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); // Write to the file if it does not exist
                    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!"))
        }
    }

}