pants_store/
vault_encrypted.rs

1use std::{cell::RefCell, collections::HashMap, error::Error, rc::Rc};
2
3use aes_gcm::{aead::OsRng, Aes256Gcm, Key};
4use argon2::password_hash::SaltString;
5use inquire::Confirm;
6use serde::{Deserialize, Serialize};
7
8use crate::{
9    action::Record,
10    cli::CLICommands,
11    command::{Commands, Instructions, Interaction},
12    file::{BackupFile, ProjectFile, RecordFile, SchemaFile, VaultFile},
13    output::Output,
14    schema::Schema,
15    secure::{Encrypted, SecureData},
16    vault::Vault,
17};
18
19#[derive(Debug, Serialize, Deserialize)]
20pub struct PasswordEncrypted<Data> {
21    data: Encrypted<Data>,
22    salt: String,
23}
24
25impl<Data> SecureData for PasswordEncrypted<Data> {
26    type Item = Data;
27    fn salt(&self) -> &str {
28        &self.salt
29    }
30    fn data(&self) -> &Encrypted<Self::Item> {
31        &self.data
32    }
33}
34
35pub type VaultEncrypted = PasswordEncrypted<Vault>;
36
37impl VaultEncrypted {
38    fn new(password: String) -> Result<Self, Box<dyn Error>> {
39        let salt = SaltString::generate(&mut OsRng).to_string();
40        let key = Self::get_key(&salt, password);
41        Encrypted::encrypt(&Vault::new(), key).map(|vault| Self { data: vault, salt })
42    }
43
44    fn update(&mut self, data: &Vault, key: Key<Aes256Gcm>) -> Result<(), Box<dyn Error>> {
45        let updated = Encrypted::encrypt(data, key)?;
46        self.data = updated;
47        Ok(())
48    }
49}
50
51pub type RecordEncrypted = PasswordEncrypted<Record>;
52
53impl RecordEncrypted {
54    fn new(password: String) -> Result<Self, Box<dyn Error>> {
55        let salt = SaltString::generate(&mut OsRng).to_string();
56        let key = Self::get_key(&salt, password);
57        Encrypted::encrypt(&Record::new(), key).map(|vault| Self { data: vault, salt })
58    }
59
60    fn update(&mut self, data: &Record, key: Key<Aes256Gcm>) -> Result<(), Box<dyn Error>> {
61        let updated = Encrypted::encrypt(data, key)?;
62        self.data = updated;
63        Ok(())
64    }
65}
66
67pub struct VaultInterface {
68    vault_file: VaultFile,
69    schema_file: Rc<RefCell<SchemaFile>>,
70    vault: VaultEncrypted,
71    record: RecordEncrypted,
72    key: Key<Aes256Gcm>,
73}
74
75impl VaultInterface {
76    pub fn create(
77        vault_file: VaultFile,
78        schema_file: Rc<RefCell<SchemaFile>>,
79        password: String,
80    ) -> VaultInterface {
81        let vault = VaultEncrypted::new(password.clone()).unwrap();
82        let record = RecordEncrypted::new(password.clone()).unwrap();
83        let key = vault.key(password);
84        Self {
85            vault_file,
86            schema_file,
87            vault,
88            record,
89            key,
90        }
91    }
92
93    pub fn open(
94        vault_file: VaultFile,
95        schema_file: Rc<RefCell<SchemaFile>>,
96        password: String,
97    ) -> Result<VaultInterface, Box<dyn Error>> {
98        let vault: VaultEncrypted = vault_file.read()?.deserialize();
99        let record = RecordEncrypted::new(password.clone()).unwrap();
100        let key = vault.key(password);
101        Ok(Self {
102            vault_file,
103            schema_file,
104            vault,
105            record,
106            key,
107        })
108    }
109
110    // TODO: working around this function is clunky and error prone, refactor
111    fn save(&mut self) -> Result<(), Box<dyn Error>> {
112        self.vault_file.write(&self.vault)?;
113        self.schema_file
114            .borrow_mut()
115            .write(&self.vault.decrypt(self.key)?.deserialize().schema())?;
116        Ok(())
117    }
118
119    fn transact(
120        transaction: Commands,
121        schema_file: Rc<RefCell<SchemaFile>>,
122    ) -> Result<Output, Box<dyn Error>> {
123        let mut interface = Self::get_interface(schema_file)?;
124        let mut vault = interface.vault.decrypt(interface.key)?.deserialize();
125        let (reads, record) = vault.transaction(transaction);
126        interface.record.update(&record, interface.key)?;
127
128        let mut record_file = RecordFile::default();
129        record_file.write(&interface.record)?;
130        vault.apply_record(record);
131        interface.vault.update(&vault, interface.key)?;
132        interface.save()?;
133        record_file.delete()?;
134        Ok(reads.into())
135    }
136
137    fn interact(
138        interaction: Interaction,
139        schema_file: Rc<RefCell<SchemaFile>>,
140    ) -> Result<Output, Box<dyn Error>> {
141        match interaction {
142            Interaction::List => {
143                let schema = schema_file.borrow_mut().read()?.deserialize();
144                Ok(Output::List(schema.all_info()))
145            }
146            Interaction::Backup => {
147                // Not 100% on this, do not want to reuse the nonce from the original vault, but
148                // the salt should be ok to reuse
149                let interface = Self::get_interface(schema_file)?;
150                let backup = interface.backup()?;
151
152                Ok(Output::Backup(backup.path()))
153            }
154            Interaction::Rotate => {
155                // TODO: does extra unnecessary decryption
156                let mut interface = Self::get_interface(schema_file)?;
157                let backup = interface.backup()?;
158                let vault = interface.vault.decrypt(interface.key)?.deserialize();
159                let new_password = inquire::Password::new("New vault password: ")
160                    .with_display_toggle_enabled()
161                    .with_display_mode(inquire::PasswordDisplayMode::Masked)
162                    .prompt()?;
163                let mut new_vault = VaultEncrypted::new(new_password.clone())?;
164                new_vault.update(&vault, new_vault.key(new_password.clone()))?;
165                interface.vault = new_vault;
166                interface.key = interface.vault.key(new_password);
167                interface.save()?;
168                Ok(Output::Backup(backup.path()))
169            }
170        }
171    }
172
173    fn backup(&self) -> Result<BackupFile, Box<dyn Error>> {
174        let vault = self.vault.decrypt(self.key)?.deserialize();
175        let mut backup_file = BackupFile::default();
176        let backup = VaultEncrypted {
177            salt: self.vault.salt.clone(),
178            data: Encrypted::encrypt(&vault, self.key)?,
179        };
180        backup_file.write(&backup)?;
181        Ok(backup_file)
182    }
183
184    fn check_unfinished(&mut self) -> Result<(), Box<dyn Error>> {
185        if let Some(file) = RecordFile::last() {
186            let ans = Confirm::new("There appears to be an unapplied update, apply it? Will clear out old record if not applied.")
187                .with_default(true)
188                .with_help_message("Likely occurred due to some failure in saving off the updates from the previous interaction.")
189                .prompt();
190
191            match ans {
192                Ok(true) => self.apply_unfinished(file)?,
193                Ok(false) => file.delete()?,
194                Err(_) => (),
195            }
196        }
197
198        Ok(())
199    }
200
201    fn apply_unfinished(&mut self, record_file: RecordFile) -> Result<(), Box<dyn Error>> {
202        let mut vault = self.vault.decrypt(self.key)?.deserialize();
203        let record = record_file
204            .read()?
205            .deserialize()
206            .decrypt(self.key)?
207            .deserialize();
208        vault.apply_record(record);
209        self.vault.update(&vault, self.key)?;
210        self.save()?;
211        record_file.delete()?;
212        Ok(())
213    }
214
215    fn get_interface(
216        schema_file: Rc<RefCell<SchemaFile>>,
217    ) -> Result<VaultInterface, Box<dyn Error>> {
218        let vault_file = VaultFile::default();
219        let mut interface = if vault_file.check() {
220            let password = inquire::Password::new("Vault password: ")
221                .without_confirmation()
222                .with_display_toggle_enabled()
223                .with_display_mode(inquire::PasswordDisplayMode::Masked)
224                .prompt()?;
225            let res = Self::open(vault_file, schema_file.clone(), password)?;
226
227            Ok::<VaultInterface, Box<dyn Error>>(res)
228        } else {
229            let new_password = inquire::Password::new("Vault doesn't exist, create password: ")
230                .with_display_toggle_enabled()
231                .with_display_mode(inquire::PasswordDisplayMode::Masked)
232                .prompt()?;
233            let mut interface = Self::create(vault_file, schema_file.clone(), new_password);
234            interface.save()?;
235            Ok(interface)
236        }?;
237
238        interface.check_unfinished()?;
239        Ok(interface)
240    }
241
242    pub fn interaction(transaction: CLICommands) -> Result<Output, Box<dyn Error>> {
243        let schema_file: Rc<RefCell<SchemaFile>> = Rc::new(RefCell::new(SchemaFile::default()));
244        let schema = if schema_file.borrow().check() {
245            schema_file.borrow_mut().read()?.deserialize()
246        } else {
247            // ensure a file exists since it can get used later
248            // TODO: should rework to make this not necessary
249            let s = Schema::new(HashMap::new());
250            schema_file.borrow_mut().write(&s)?;
251            s
252        };
253
254        let commands = Instructions::from_commands(transaction, schema)?;
255        match commands {
256            Instructions::Interaction(i) => Self::interact(i, schema_file),
257            Instructions::Commands(c) => Self::transact(c, schema_file),
258        }
259    }
260}