envelope_cli/cli/
encrypt.rs

1//! Encryption CLI commands
2//!
3//! Provides commands for enabling, disabling, and managing encryption.
4
5use 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/// Encryption management commands
15#[derive(Subcommand)]
16pub enum EncryptCommands {
17    /// Enable encryption for your budget data
18    Enable,
19
20    /// Disable encryption (requires current passphrase)
21    Disable,
22
23    /// Change your encryption passphrase
24    #[command(alias = "change")]
25    ChangePassphrase,
26
27    /// Show encryption status
28    Status,
29
30    /// Verify your passphrase is correct
31    Verify,
32}
33
34/// Handle encryption commands
35pub 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
50/// Enable encryption on budget data
51fn 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    // Get passphrase
72    let passphrase = prompt_new_passphrase()?;
73
74    // Generate key derivation params
75    let key_params = KeyDerivationParams::new();
76
77    // Derive key
78    println!("Deriving encryption key...");
79    let key = derive_key(&passphrase, &key_params)?;
80
81    // Create verification hash
82    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    // Update settings
88    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; // Legacy field
92
93    // Save settings
94    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
105/// Disable encryption
106fn 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    // Verify current passphrase
121    let passphrase = prompt_passphrase("Enter current passphrase: ")?;
122    verify_passphrase_internal(settings, &passphrase)?;
123
124    println!("Passphrase verified.");
125    println!();
126
127    // Confirm disable
128    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    // Update settings
140    settings.encryption.enabled = false;
141    settings.encryption.key_params = None;
142    settings.encryption.verification_hash = None;
143    settings.encryption_enabled = false;
144
145    // Save settings
146    settings.save(paths)?;
147
148    println!();
149    println!("Encryption disabled successfully!");
150    println!("Your data is now stored unencrypted.");
151
152    Ok(())
153}
154
155/// Change the encryption passphrase
156fn 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    // Verify current passphrase
168    let current = prompt_passphrase("Enter current passphrase: ")?;
169    verify_passphrase_internal(settings, &current)?;
170
171    println!("Current passphrase verified.");
172    println!();
173
174    // Get new passphrase
175    let new_passphrase = prompt_new_passphrase()?;
176
177    // Generate new key derivation params
178    let new_key_params = KeyDerivationParams::new();
179
180    // Derive new key
181    println!("Deriving new encryption key...");
182    let new_key = derive_key(&new_passphrase, &new_key_params)?;
183
184    // Create new verification hash
185    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    // Update settings
191    settings.encryption.key_params = Some(new_key_params);
192    settings.encryption.verification_hash = Some(verification_json);
193
194    // Save settings
195    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
204/// Show encryption status
205fn 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
230/// Verify the current passphrase
231fn 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
251/// Internal passphrase verification
252fn 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
279/// Prompt for a new passphrase with confirmation
280fn 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
300/// Prompt for a passphrase (hidden input)
301fn 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
306/// Get the derived key from a passphrase and settings
307pub 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}