use clap::Subcommand;
use crate::config::{paths::EnvelopePaths, settings::Settings};
use crate::crypto::{
decrypt_string, derive_key, encrypt_string, EncryptedData, KeyDerivationParams,
};
use crate::error::{EnvelopeError, EnvelopeResult};
use crate::storage::Storage;
#[derive(Subcommand)]
pub enum EncryptCommands {
Enable,
Disable,
#[command(alias = "change")]
ChangePassphrase,
Status,
Verify,
}
pub fn handle_encrypt_command(
paths: &EnvelopePaths,
settings: &mut Settings,
storage: &Storage,
cmd: EncryptCommands,
) -> EnvelopeResult<()> {
match cmd {
EncryptCommands::Enable => enable_encryption(paths, settings, storage),
EncryptCommands::Disable => disable_encryption(paths, settings, storage),
EncryptCommands::ChangePassphrase => change_passphrase(paths, settings),
EncryptCommands::Status => show_status(settings),
EncryptCommands::Verify => verify_passphrase(settings),
}
}
fn enable_encryption(
paths: &EnvelopePaths,
settings: &mut Settings,
_storage: &Storage,
) -> EnvelopeResult<()> {
if settings.is_encryption_enabled() {
println!("Encryption is already enabled.");
println!("Use 'envelope encrypt change-passphrase' to change your passphrase.");
return Ok(());
}
println!("Enable Encryption");
println!("================");
println!();
println!("Encryption protects your budget data with AES-256-GCM encryption.");
println!("You will need to enter your passphrase each time you use EnvelopeCLI.");
println!();
println!("IMPORTANT: If you forget your passphrase, your data cannot be recovered!");
println!();
let passphrase = prompt_new_passphrase()?;
let key_params = KeyDerivationParams::new();
println!("Deriving encryption key...");
let key = derive_key(&passphrase, &key_params)?;
let verification = encrypt_string("envelope_verify", &key)?;
let verification_json = serde_json::to_string(&verification).map_err(|e| {
EnvelopeError::Encryption(format!("Failed to serialize verification: {}", e))
})?;
settings.encryption.enabled = true;
settings.encryption.key_params = Some(key_params);
settings.encryption.verification_hash = Some(verification_json);
settings.encryption_enabled = true;
settings.save(paths)?;
println!();
println!("Encryption enabled successfully!");
println!();
println!("Your data will be encrypted on the next save operation.");
println!("Remember to keep your passphrase safe - there is no recovery mechanism!");
Ok(())
}
fn disable_encryption(
paths: &EnvelopePaths,
settings: &mut Settings,
_storage: &Storage,
) -> EnvelopeResult<()> {
if !settings.is_encryption_enabled() {
println!("Encryption is not enabled.");
return Ok(());
}
println!("Disable Encryption");
println!("==================");
println!();
let passphrase = prompt_passphrase("Enter current passphrase: ")?;
verify_passphrase_internal(settings, &passphrase)?;
println!("Passphrase verified.");
println!();
print!("Are you sure you want to disable encryption? (yes/no): ");
std::io::Write::flush(&mut std::io::stdout())?;
let mut confirm = String::new();
std::io::stdin().read_line(&mut confirm)?;
if confirm.trim().to_lowercase() != "yes" {
println!("Aborted.");
return Ok(());
}
settings.encryption.enabled = false;
settings.encryption.key_params = None;
settings.encryption.verification_hash = None;
settings.encryption_enabled = false;
settings.save(paths)?;
println!();
println!("Encryption disabled successfully!");
println!("Your data is now stored unencrypted.");
Ok(())
}
fn change_passphrase(paths: &EnvelopePaths, settings: &mut Settings) -> EnvelopeResult<()> {
if !settings.is_encryption_enabled() {
println!("Encryption is not enabled.");
println!("Use 'envelope encrypt enable' to enable encryption first.");
return Ok(());
}
println!("Change Passphrase");
println!("=================");
println!();
let current = prompt_passphrase("Enter current passphrase: ")?;
verify_passphrase_internal(settings, ¤t)?;
println!("Current passphrase verified.");
println!();
let new_passphrase = prompt_new_passphrase()?;
let new_key_params = KeyDerivationParams::new();
println!("Deriving new encryption key...");
let new_key = derive_key(&new_passphrase, &new_key_params)?;
let verification = encrypt_string("envelope_verify", &new_key)?;
let verification_json = serde_json::to_string(&verification).map_err(|e| {
EnvelopeError::Encryption(format!("Failed to serialize verification: {}", e))
})?;
settings.encryption.key_params = Some(new_key_params);
settings.encryption.verification_hash = Some(verification_json);
settings.save(paths)?;
println!();
println!("Passphrase changed successfully!");
println!("Your data will be re-encrypted with the new key on the next save.");
Ok(())
}
fn show_status(settings: &Settings) -> EnvelopeResult<()> {
println!("Encryption Status");
println!("=================");
println!();
if settings.is_encryption_enabled() {
println!("Status: ENABLED");
println!();
if let Some(ref params) = settings.encryption.key_params {
println!("Key Derivation Parameters:");
println!(" Algorithm: Argon2id");
println!(" Memory Cost: {} KiB", params.memory_cost);
println!(" Time Cost: {} iterations", params.time_cost);
println!(" Parallelism: {} threads", params.parallelism);
}
} else {
println!("Status: DISABLED");
println!();
println!("Your data is stored unencrypted.");
println!("Run 'envelope encrypt enable' to enable encryption.");
}
Ok(())
}
fn verify_passphrase(settings: &Settings) -> EnvelopeResult<()> {
if !settings.is_encryption_enabled() {
println!("Encryption is not enabled.");
return Ok(());
}
let passphrase = prompt_passphrase("Enter passphrase: ")?;
match verify_passphrase_internal(settings, &passphrase) {
Ok(()) => {
println!("Passphrase is correct!");
Ok(())
}
Err(_) => {
println!("Passphrase is incorrect.");
Err(EnvelopeError::Encryption("Invalid passphrase".to_string()))
}
}
}
fn verify_passphrase_internal(settings: &Settings, passphrase: &str) -> EnvelopeResult<()> {
let key_params = settings
.encryption
.key_params
.as_ref()
.ok_or_else(|| EnvelopeError::Encryption("No key parameters found".to_string()))?;
let verification_json = settings
.encryption
.verification_hash
.as_ref()
.ok_or_else(|| EnvelopeError::Encryption("No verification hash found".to_string()))?;
let encrypted: EncryptedData = serde_json::from_str(verification_json)
.map_err(|e| EnvelopeError::Encryption(format!("Invalid verification data: {}", e)))?;
let key = derive_key(passphrase, key_params)?;
let decrypted = decrypt_string(&encrypted, &key)?;
if decrypted != "envelope_verify" {
return Err(EnvelopeError::Encryption("Invalid passphrase".to_string()));
}
Ok(())
}
fn prompt_new_passphrase() -> EnvelopeResult<String> {
loop {
let pass1 = prompt_passphrase("Enter new passphrase: ")?;
if pass1.len() < 8 {
println!("Passphrase must be at least 8 characters. Please try again.");
continue;
}
let pass2 = prompt_passphrase("Confirm passphrase: ")?;
if pass1 != pass2 {
println!("Passphrases do not match. Please try again.");
continue;
}
return Ok(pass1);
}
}
fn prompt_passphrase(prompt: &str) -> EnvelopeResult<String> {
rpassword::prompt_password(prompt)
.map_err(|e| EnvelopeError::Encryption(format!("Failed to read passphrase: {}", e)))
}
pub fn get_encryption_key(
settings: &Settings,
passphrase: &str,
) -> EnvelopeResult<crate::crypto::DerivedKey> {
let key_params = settings
.encryption
.key_params
.as_ref()
.ok_or_else(|| EnvelopeError::Encryption("No key parameters found".to_string()))?;
derive_key(passphrase, key_params)
}