envelope_cli/cli/
encrypt.rs1use clap::Subcommand;
6
7use crate::config::{paths::EnvelopePaths, settings::Settings};
8use crate::crypto::{
9 decrypt_string, derive_key, encrypt_string, EncryptedData, KeyDerivationParams,
10};
11use crate::error::{EnvelopeError, EnvelopeResult};
12use crate::storage::Storage;
13
14#[derive(Subcommand)]
16pub enum EncryptCommands {
17 Enable,
19
20 Disable,
22
23 #[command(alias = "change")]
25 ChangePassphrase,
26
27 Status,
29
30 Verify,
32}
33
34pub fn handle_encrypt_command(
36 paths: &EnvelopePaths,
37 settings: &mut Settings,
38 storage: &Storage,
39 cmd: EncryptCommands,
40) -> EnvelopeResult<()> {
41 match cmd {
42 EncryptCommands::Enable => enable_encryption(paths, settings, storage),
43 EncryptCommands::Disable => disable_encryption(paths, settings, storage),
44 EncryptCommands::ChangePassphrase => change_passphrase(paths, settings),
45 EncryptCommands::Status => show_status(settings),
46 EncryptCommands::Verify => verify_passphrase(settings),
47 }
48}
49
50fn enable_encryption(
52 paths: &EnvelopePaths,
53 settings: &mut Settings,
54 _storage: &Storage,
55) -> EnvelopeResult<()> {
56 if settings.is_encryption_enabled() {
57 println!("Encryption is already enabled.");
58 println!("Use 'envelope encrypt change-passphrase' to change your passphrase.");
59 return Ok(());
60 }
61
62 println!("Enable Encryption");
63 println!("================");
64 println!();
65 println!("Encryption protects your budget data with AES-256-GCM encryption.");
66 println!("You will need to enter your passphrase each time you use EnvelopeCLI.");
67 println!();
68 println!("IMPORTANT: If you forget your passphrase, your data cannot be recovered!");
69 println!();
70
71 let passphrase = prompt_new_passphrase()?;
73
74 let key_params = KeyDerivationParams::new();
76
77 println!("Deriving encryption key...");
79 let key = derive_key(&passphrase, &key_params)?;
80
81 let verification = encrypt_string("envelope_verify", &key)?;
83 let verification_json = serde_json::to_string(&verification).map_err(|e| {
84 EnvelopeError::Encryption(format!("Failed to serialize verification: {}", e))
85 })?;
86
87 settings.encryption.enabled = true;
89 settings.encryption.key_params = Some(key_params);
90 settings.encryption.verification_hash = Some(verification_json);
91 settings.encryption_enabled = true; settings.save(paths)?;
95
96 println!();
97 println!("Encryption enabled successfully!");
98 println!();
99 println!("Your data will be encrypted on the next save operation.");
100 println!("Remember to keep your passphrase safe - there is no recovery mechanism!");
101
102 Ok(())
103}
104
105fn disable_encryption(
107 paths: &EnvelopePaths,
108 settings: &mut Settings,
109 _storage: &Storage,
110) -> EnvelopeResult<()> {
111 if !settings.is_encryption_enabled() {
112 println!("Encryption is not enabled.");
113 return Ok(());
114 }
115
116 println!("Disable Encryption");
117 println!("==================");
118 println!();
119
120 let passphrase = prompt_passphrase("Enter current passphrase: ")?;
122 verify_passphrase_internal(settings, &passphrase)?;
123
124 println!("Passphrase verified.");
125 println!();
126
127 print!("Are you sure you want to disable encryption? (yes/no): ");
129 std::io::Write::flush(&mut std::io::stdout())?;
130
131 let mut confirm = String::new();
132 std::io::stdin().read_line(&mut confirm)?;
133
134 if confirm.trim().to_lowercase() != "yes" {
135 println!("Aborted.");
136 return Ok(());
137 }
138
139 settings.encryption.enabled = false;
141 settings.encryption.key_params = None;
142 settings.encryption.verification_hash = None;
143 settings.encryption_enabled = false;
144
145 settings.save(paths)?;
147
148 println!();
149 println!("Encryption disabled successfully!");
150 println!("Your data is now stored unencrypted.");
151
152 Ok(())
153}
154
155fn change_passphrase(paths: &EnvelopePaths, settings: &mut Settings) -> EnvelopeResult<()> {
157 if !settings.is_encryption_enabled() {
158 println!("Encryption is not enabled.");
159 println!("Use 'envelope encrypt enable' to enable encryption first.");
160 return Ok(());
161 }
162
163 println!("Change Passphrase");
164 println!("=================");
165 println!();
166
167 let current = prompt_passphrase("Enter current passphrase: ")?;
169 verify_passphrase_internal(settings, ¤t)?;
170
171 println!("Current passphrase verified.");
172 println!();
173
174 let new_passphrase = prompt_new_passphrase()?;
176
177 let new_key_params = KeyDerivationParams::new();
179
180 println!("Deriving new encryption key...");
182 let new_key = derive_key(&new_passphrase, &new_key_params)?;
183
184 let verification = encrypt_string("envelope_verify", &new_key)?;
186 let verification_json = serde_json::to_string(&verification).map_err(|e| {
187 EnvelopeError::Encryption(format!("Failed to serialize verification: {}", e))
188 })?;
189
190 settings.encryption.key_params = Some(new_key_params);
192 settings.encryption.verification_hash = Some(verification_json);
193
194 settings.save(paths)?;
196
197 println!();
198 println!("Passphrase changed successfully!");
199 println!("Your data will be re-encrypted with the new key on the next save.");
200
201 Ok(())
202}
203
204fn show_status(settings: &Settings) -> EnvelopeResult<()> {
206 println!("Encryption Status");
207 println!("=================");
208 println!();
209
210 if settings.is_encryption_enabled() {
211 println!("Status: ENABLED");
212 println!();
213 if let Some(ref params) = settings.encryption.key_params {
214 println!("Key Derivation Parameters:");
215 println!(" Algorithm: Argon2id");
216 println!(" Memory Cost: {} KiB", params.memory_cost);
217 println!(" Time Cost: {} iterations", params.time_cost);
218 println!(" Parallelism: {} threads", params.parallelism);
219 }
220 } else {
221 println!("Status: DISABLED");
222 println!();
223 println!("Your data is stored unencrypted.");
224 println!("Run 'envelope encrypt enable' to enable encryption.");
225 }
226
227 Ok(())
228}
229
230fn verify_passphrase(settings: &Settings) -> EnvelopeResult<()> {
232 if !settings.is_encryption_enabled() {
233 println!("Encryption is not enabled.");
234 return Ok(());
235 }
236
237 let passphrase = prompt_passphrase("Enter passphrase: ")?;
238
239 match verify_passphrase_internal(settings, &passphrase) {
240 Ok(()) => {
241 println!("Passphrase is correct!");
242 Ok(())
243 }
244 Err(_) => {
245 println!("Passphrase is incorrect.");
246 Err(EnvelopeError::Encryption("Invalid passphrase".to_string()))
247 }
248 }
249}
250
251fn verify_passphrase_internal(settings: &Settings, passphrase: &str) -> EnvelopeResult<()> {
253 let key_params = settings
254 .encryption
255 .key_params
256 .as_ref()
257 .ok_or_else(|| EnvelopeError::Encryption("No key parameters found".to_string()))?;
258
259 let verification_json = settings
260 .encryption
261 .verification_hash
262 .as_ref()
263 .ok_or_else(|| EnvelopeError::Encryption("No verification hash found".to_string()))?;
264
265 let encrypted: EncryptedData = serde_json::from_str(verification_json)
266 .map_err(|e| EnvelopeError::Encryption(format!("Invalid verification data: {}", e)))?;
267
268 let key = derive_key(passphrase, key_params)?;
269
270 let decrypted = decrypt_string(&encrypted, &key)?;
271
272 if decrypted != "envelope_verify" {
273 return Err(EnvelopeError::Encryption("Invalid passphrase".to_string()));
274 }
275
276 Ok(())
277}
278
279fn prompt_new_passphrase() -> EnvelopeResult<String> {
281 loop {
282 let pass1 = prompt_passphrase("Enter new passphrase: ")?;
283
284 if pass1.len() < 8 {
285 println!("Passphrase must be at least 8 characters. Please try again.");
286 continue;
287 }
288
289 let pass2 = prompt_passphrase("Confirm passphrase: ")?;
290
291 if pass1 != pass2 {
292 println!("Passphrases do not match. Please try again.");
293 continue;
294 }
295
296 return Ok(pass1);
297 }
298}
299
300fn prompt_passphrase(prompt: &str) -> EnvelopeResult<String> {
302 rpassword::prompt_password(prompt)
303 .map_err(|e| EnvelopeError::Encryption(format!("Failed to read passphrase: {}", e)))
304}
305
306pub fn get_encryption_key(
308 settings: &Settings,
309 passphrase: &str,
310) -> EnvelopeResult<crate::crypto::DerivedKey> {
311 let key_params = settings
312 .encryption
313 .key_params
314 .as_ref()
315 .ok_or_else(|| EnvelopeError::Encryption("No key parameters found".to_string()))?;
316
317 derive_key(passphrase, key_params)
318}