rlib/vault/
mod.rs

1mod crypto;
2use crate::cli;
3use crate::files;
4use anyhow::{anyhow, Context, Result};
5use openssl::base64::decode_block;
6use openssl::base64::encode_block;
7use openssl::symm::{decrypt, encrypt, Cipher};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::fs::File;
11use std::io::prelude::*;
12use std::option::Option;
13use std::path::PathBuf;
14use std::str::FromStr;
15use std::string::String;
16
17const SALT_LEN: usize = 256;
18const IV_LEN: usize = 16;
19const VAULT_EXT: &'static str = ".vlt";
20
21#[derive(Debug, Serialize, Deserialize, Clone)]
22pub struct Password {
23    pub id: String,
24    pub pw: String,
25}
26
27#[derive(Serialize, Deserialize, Debug, Clone)]
28pub struct LockedVault {
29    pub name: String,
30    pub iv: String,
31    pub salt: String,
32    pub enc: String,
33}
34
35pub struct UnlockedVault {
36    pub name: String,
37    pub salt: Vec<u8>,
38    pub pws: HashMap<String, String>,
39}
40
41impl LockedVault {
42    pub fn unlock(&self, pass: &str) -> Result<UnlockedVault> {
43        let salt = decode_block(&self.salt).context("Failed to decode salt")?;
44        let data = decode_block(&self.enc).context("Failed to decode data")?;
45        let iv = decode_block(&self.iv).context("Failed to decode iv")?;
46        let key = crypto::key(&pass.as_bytes(), &salt)?;
47        let cipher = Cipher::aes_256_cbc();
48        let plain =
49            decrypt(cipher, &key, Some(&iv), &data).context("Cipher could not be decrypted")?;
50
51        let json = String::from_utf8(plain).context("UTF8 conversion failed")?;
52        let passwords: HashMap<String, String> =
53            serde_json::from_str(&json).context("JSON conversion failed")?;
54
55        Ok(UnlockedVault {
56            name: self.name.clone(),
57            salt: salt,
58            pws: passwords,
59        })
60    }
61
62    pub fn exists(&self) -> bool {
63        let path = files::rpwd_path(&format!("{}{}", self.name, VAULT_EXT));
64        path.exists()
65    }
66
67    pub fn save(&self) -> Result<()> {
68        let path = files::rpwd_path(&format!("{}{}", self.name, VAULT_EXT));
69        let json = serde_json::to_string(&self).context("Failed to serialize passwords")?;
70        Ok(File::create(&path)
71            .and_then(|mut f| f.write_all(&json.as_bytes()))
72            .context("Failed to save vault")?)
73    }
74
75    pub fn delete(&self) -> Result<()> {
76        if cli::yesorno(
77            format!("Would you really like to delete the vault {}?", &self.name).as_str(),
78        ) && cli::yesorno("Are you reaaaaally sure? It's permanent.")
79        {
80            files::delete(format!("{}{}", &self.name, VAULT_EXT).as_str())?;
81            return Ok(());
82        }
83        return Err(anyhow!("Did not delete vault"));
84    }
85}
86
87impl FromStr for LockedVault {
88    type Err = anyhow::Error;
89    fn from_str(s: &str) -> Result<Self, Self::Err> {
90        let fname = files::rpwd_path(&format!("{}{}", s, VAULT_EXT));
91        let f = File::open(&fname)?;
92        Ok(serde_json::from_reader::<File, LockedVault>(f)?)
93    }
94}
95
96impl UnlockedVault {
97    pub fn new(vault: &str) -> UnlockedVault {
98        let mut salt = [0; SALT_LEN];
99        crypto::salt(&mut salt);
100
101        UnlockedVault {
102            name: vault.to_string(),
103            salt: salt.to_vec(),
104            pws: HashMap::new(),
105        }
106    }
107
108    pub fn import(&mut self, path: &PathBuf) -> Result<Vec<Password>> {
109        let f = File::open(&path)?;
110        let pws: Vec<Password> = serde_json::from_reader::<File, Vec<Password>>(f)?;
111
112        let dup = pws
113            .iter()
114            .filter(|p| !self.try_insert(p.id.clone(), p.pw.clone()))
115            .map(|p| p.clone())
116            .collect();
117
118        Ok(dup)
119    }
120
121    pub fn export(&self, path: &PathBuf) -> Result<()> {
122        let pws: Vec<Password> = self
123            .pws
124            .iter()
125            .map(|(k, v)| Password {
126                id: k.to_string(),
127                pw: v.to_string(),
128            })
129            .collect();
130        let json = serde_json::to_string_pretty(&pws).context("Failed to serialize vault")?;
131        File::create(&path).and_then(|mut f| f.write_all(&json.as_bytes()))?;
132        Ok(())
133    }
134
135    pub fn lock(&self, pass: &str) -> Result<LockedVault> {
136        let cipher = Cipher::aes_256_cbc();
137        let salt = &self.salt;
138        let key = crypto::key(&pass.as_bytes(), &salt).context("Failed to derive key")?;
139        let data =
140            serde_json::to_string_pretty(&self.pws).context("Failed to serialize passwords")?;
141        let key = key;
142
143        let mut iv = [0; IV_LEN];
144        crypto::rand_bytes(&mut iv);
145        let ciphertext = encrypt(cipher, &key, Some(&iv), data.as_bytes())
146            .context("Failed to encrypt plaintext")?;
147        Ok(LockedVault {
148            name: self.name.clone(),
149            iv: encode_block(&iv.to_vec()),
150            salt: encode_block(&self.salt.to_vec()),
151            enc: encode_block(&ciphertext),
152        })
153    }
154
155    pub fn try_insert(&mut self, id: String, password: String) -> bool {
156        if !self.pws.contains_key(&id) {
157            self.pws.insert(id, password);
158            return true;
159        }
160        return false;
161    }
162
163    pub fn insert(&mut self, id: String, password: String) {
164        self.pws.insert(id, password);
165    }
166
167    pub fn get(&self, id: String) -> Option<&String> {
168        self.pws.get(&id)
169    }
170}