shadow_crypt_shell/listing/
workflow.rs1use std::sync::Arc;
2
3use rayon::prelude::*;
4use shadow_crypt_core::{
5 memory::{SecureBytes, SecureKey, SecureString},
6 v1::{
7 crypt::decrypt_bytes, header::FileHeader, header_ops::get_kdf_params,
8 key::KeyDerivationParams, key_ops::derive_key,
9 },
10};
11
12use crate::{
13 errors::WorkflowResult,
14 listing::{
15 file::{FileInfoList, ListingInput, ShadowFile, ShadowFileInfo},
16 file_ops::{load_file_header, scan_directory_for_shadow_files},
17 },
18 ui,
19 utils::parse_string_from_bytes,
20};
21
22pub fn run_workflow(input: ListingInput) -> WorkflowResult<()> {
23 let shadow_files: Vec<ShadowFile> = scan_directory_for_shadow_files(&input.work_dir)?;
24 let password = Arc::new(input.password);
25
26 let file_infos: Vec<ShadowFileInfo> = shadow_files
28 .par_iter()
29 .map(|shadow_file| get_shadow_file_info(shadow_file, &password))
30 .filter_map(Result::ok)
31 .collect();
32
33 let info_list: FileInfoList = FileInfoList::new(file_infos);
34
35 ui::display_file_info_list(&info_list);
36 Ok(())
37}
38
39fn decipher_original_filename(header: FileHeader, password: &SecureString) -> Option<SecureString> {
40 let filename_nonce: &[u8; 24] = &header.filename_nonce;
41 let filename_ciphertext: &[u8] = header.filename_ciphertext.as_slice();
42
43 let salt: &[u8; 16] = &header.salt;
44 let kdf_params: KeyDerivationParams = get_kdf_params(&header);
45 let (key, _): (SecureKey, _) =
46 derive_key(password.as_str().as_bytes(), salt, &kdf_params).ok()?;
47
48 let (filename_bytes, _): (SecureBytes, _) =
49 decrypt_bytes(filename_ciphertext, key.as_bytes(), filename_nonce).ok()?;
50
51 parse_string_from_bytes(&filename_bytes).ok()
52}
53
54fn get_shadow_file_info(
55 shadow_file: &ShadowFile,
56 password: &SecureString,
57) -> WorkflowResult<ShadowFileInfo> {
58 let header = load_file_header(shadow_file)?;
59 let original_filename: Option<SecureString> = decipher_original_filename(header, password);
60
61 Ok(ShadowFileInfo::new(
62 original_filename,
63 shadow_file.filename.clone(),
64 shadow_file.version.clone(),
65 shadow_file.size,
66 ))
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72 use shadow_crypt_core::{
73 profile::SecurityProfile,
74 v1::{
75 crypt::encrypt_bytes, header::FileHeader, key::KeyDerivationParams, key_ops::derive_key,
76 },
77 };
78
79 #[test]
80 fn test_decipher_original_filename_correct_password() {
81 let password = "testpassword";
82 let original_filename = "test.txt";
83 let salt = [0u8; 16];
84 let kdf_params = KeyDerivationParams::from(SecurityProfile::Test);
85 let filename_nonce = [0u8; 24];
86
87 let (key, _) = derive_key(password.as_bytes(), &salt, &kdf_params).unwrap();
89
90 let (filename_ciphertext, _) = encrypt_bytes(
92 original_filename.as_bytes(),
93 key.as_bytes(),
94 &filename_nonce,
95 )
96 .unwrap();
97
98 let header = FileHeader::new(
100 salt,
101 kdf_params,
102 [0u8; 24], filename_nonce,
104 filename_ciphertext,
105 );
106
107 let password_secure = SecureString::new(password.to_string());
109 let result = decipher_original_filename(header, &password_secure);
110
111 assert!(result.is_some());
112 assert_eq!(result.unwrap().as_str(), original_filename);
113 }
114
115 #[test]
116 fn test_decipher_original_filename_wrong_password() {
117 let password = "testpassword";
118 let wrong_password = "wrongpassword";
119 let original_filename = "test.txt";
120 let salt = [0u8; 16];
121 let kdf_params = KeyDerivationParams::from(SecurityProfile::Test);
122 let filename_nonce = [0u8; 24];
123
124 let (key, _) = derive_key(password.as_bytes(), &salt, &kdf_params).unwrap();
126
127 let (filename_ciphertext, _) = encrypt_bytes(
129 original_filename.as_bytes(),
130 key.as_bytes(),
131 &filename_nonce,
132 )
133 .unwrap();
134
135 let header = FileHeader::new(
137 salt,
138 kdf_params,
139 [0u8; 24],
140 filename_nonce,
141 filename_ciphertext,
142 );
143
144 let wrong_password_secure = SecureString::new(wrong_password.to_string());
146 let result = decipher_original_filename(header, &wrong_password_secure);
147
148 assert!(result.is_none());
149 }
150}