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}