shadow_crypt_shell/listing/
workflow.rs

1use 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    // Process files in parallel using rayon
27    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        // Derive key
88        let (key, _) = derive_key(password.as_bytes(), &salt, &kdf_params).unwrap();
89
90        // Encrypt filename
91        let (filename_ciphertext, _) = encrypt_bytes(
92            original_filename.as_bytes(),
93            key.as_bytes(),
94            &filename_nonce,
95        )
96        .unwrap();
97
98        // Create header
99        let header = FileHeader::new(
100            salt,
101            kdf_params,
102            [0u8; 24], // content_nonce, not used
103            filename_nonce,
104            filename_ciphertext,
105        );
106
107        // Test decipher
108        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        // Derive key with correct password
125        let (key, _) = derive_key(password.as_bytes(), &salt, &kdf_params).unwrap();
126
127        // Encrypt filename
128        let (filename_ciphertext, _) = encrypt_bytes(
129            original_filename.as_bytes(),
130            key.as_bytes(),
131            &filename_nonce,
132        )
133        .unwrap();
134
135        // Create header
136        let header = FileHeader::new(
137            salt,
138            kdf_params,
139            [0u8; 24],
140            filename_nonce,
141            filename_ciphertext,
142        );
143
144        // Test decipher with wrong password
145        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}