smart_locker/commands/
decrypt.rs1use crate::commands::migrate::migrate_metadata;
2use crate::utils::config::EncryptionConfig;
3use crate::utils::metadata::{
4 has_this_secret_metadata, is_secret_expired, mark_secret_as_expired, read_metadata,
5};
6use crate::utils::toolbox::{get_locker_dir, is_this_secret};
7use crate::LockerResult;
8use crate::MetadataFile;
9use crate::SmartLockerError;
10use aes_gcm::aead::Aead;
11use aes_gcm::Nonce;
12use colored::Colorize;
13use flate2::read::GzDecoder;
14use std::fs;
15use std::io::{self, Read, Write};
16
17pub fn decrypt(name: &str) -> LockerResult<String> {
18 let config = EncryptionConfig::new();
19 let locker_dir = get_locker_dir()?;
20 let key_path = locker_dir.join("locker.key");
21 let secret_path = locker_dir.join(format!("{}.slock", name));
22
23 let (is_valid, _secret_name) = is_this_secret(&secret_path, false);
25 if !is_valid {
26 return Err(SmartLockerError::FileSystemError(format!(
27 "The file '{}' is not a valid secret file.",
28 secret_path.display()
29 )));
30 }
31
32 let metadata_result = read_metadata();
34 let mut metadata = metadata_result.unwrap_or_else(|_| MetadataFile {
35 secrets: Default::default(),
36 });
37
38 if !has_this_secret_metadata(name, &metadata) {
40 println!(
41 "{}",
42 format!(
43 "⚠️ Metadata for secret '{}' is missing or outdated. Do you want to migrate it? (yes/no): ",
44 name
45 )
46 .yellow()
47 );
48
49 let mut input = String::new();
51 io::stdout().flush().unwrap();
52 io::stdin().read_line(&mut input).unwrap();
53 let input = input.trim().to_lowercase();
54
55 if input == "yes" {
56 println!(
57 "{}",
58 format!("Migrating metadata for secret '{}'...", name).blue()
59 );
60 migrate_metadata(Some(name))?;
61 println!(
62 "{}",
63 "✅ Metadata migration completed successfully.".green()
64 );
65 metadata = read_metadata()?; } else {
67 return Err(SmartLockerError::DecryptionError(format!(
68 "Metadata for secret '{}' is invalid. Migration was skipped.",
69 name
70 )));
71 }
72 }
73
74 let secret_metadata = metadata.secrets.get(name).ok_or_else(|| {
76 SmartLockerError::DecryptionError(format!(
77 "Metadata for secret '{}' is missing after migration.",
78 name
79 ))
80 })?;
81
82 if is_secret_expired(secret_metadata) {
84 mark_secret_as_expired(name, &mut metadata)?;
85 return Err(SmartLockerError::DecryptionError(format!(
86 "The secret '{}' has expired. Please renew it to use it again.",
87 name
88 )));
89 }
90
91 let encrypted_data = fs::read(&secret_path).map_err(|_| {
93 SmartLockerError::FileSystemError("Unable to read the encrypted file".to_string())
94 })?;
95
96 if !encrypted_data.starts_with(config.signature) {
98 return Err(SmartLockerError::DecryptionError(format!(
99 "The secret '{}' is not in the current format. Please re-encrypt it using the latest version of smart-locker.",
100 name
101 )));
102 }
103
104 let version = encrypted_data[config.signature.len()];
106 if version != config.format_version {
107 return Err(SmartLockerError::DecryptionError(format!(
108 "The secret '{}' uses an unsupported format version ({}). Please update smart-locker.",
109 name, version
110 )));
111 }
112
113 let data_without_header = &encrypted_data[config.signature.len() + 1..];
115 let (nonce, ciphertext) = data_without_header.split_at(config.nonce_size);
116 let nonce = Nonce::from_slice(nonce);
117
118 let key_data = fs::read(&key_path).map_err(|_| {
120 SmartLockerError::FileSystemError("Unable to read the symmetric key".to_string())
121 })?;
122 let cipher = config
123 .init_cipher(&key_data)
124 .map_err(SmartLockerError::DecryptionError)?;
125
126 let decrypted_data = cipher
128 .decrypt(nonce, ciphertext)
129 .map_err(|_| SmartLockerError::DecryptionError("Decryption failed".to_string()))?;
130
131 let mut decoder = GzDecoder::new(&decrypted_data[..]);
133 let mut decompressed_data = String::new();
134 decoder
135 .read_to_string(&mut decompressed_data)
136 .map_err(|_| {
137 SmartLockerError::FileSystemError("Failed to decompress the data".to_string())
138 })?;
139 Ok(decompressed_data)
140}