Skip to main content

shadow_crypt_shell/encryption/
workflow.rs

1use rayon::prelude::*;
2use shadow_crypt_core::{
3    algorithm::Algorithm,
4    memory::SecureString,
5    progress::ProgressCounter,
6    report::EncryptionReport,
7    v2::{
8        crypt::encrypt_bytes,
9        file::{EncryptedFile, PlaintextFile},
10        header::{AadPurpose, FileHeader, HeaderBinding},
11        key::KeyDerivationParams,
12        key_ops::derive_key,
13    },
14};
15
16use crate::{
17    encryption::{
18        file::{EncryptionInput, EncryptionInputFile},
19        file_ops::{load_plaintext_file, store_encrypted_file},
20        nonce::generate_nonce,
21        salt::generate_salt,
22    },
23    errors::{WorkflowError, WorkflowResult},
24    kdf::with_kdf_memory_permit,
25    ui::{display_encryption_report, display_progress},
26};
27
28pub fn run_workflow(input: EncryptionInput) -> WorkflowResult<()> {
29    let params = KeyDerivationParams::from(input.security_profile);
30    let total = input.files.len();
31    let counter = ProgressCounter::new(total as u64);
32
33    // Each file gets its own salt and derived key so that files encrypted in the
34    // same session cannot be correlated by comparing header salts.
35    let failures: usize = input
36        .files
37        .par_iter()
38        .map(|input_file| {
39            let result = process_file_encryption(
40                input_file.to_owned(),
41                &input.password,
42                &params,
43                &input.output_dir,
44            );
45            counter.increment();
46            display_progress(&counter);
47            let failed = result.is_err();
48            display_encryption_report(result);
49            usize::from(failed)
50        })
51        .sum();
52
53    if failures > 0 {
54        return Err(WorkflowError::Encryption(format!(
55            "{} of {} file(s) failed to encrypt",
56            failures, total
57        )));
58    }
59
60    Ok(())
61}
62
63fn process_file_encryption(
64    file: EncryptionInputFile,
65    password: &SecureString,
66    kdf_params: &KeyDerivationParams,
67    output_dir: &std::path::Path,
68) -> WorkflowResult<EncryptionReport> {
69    let start_time = std::time::Instant::now();
70
71    let salt: [u8; 16] = generate_salt()?;
72    let (key, _) = with_kdf_memory_permit(kdf_params.memory_cost, || {
73        derive_key(password.as_str().as_bytes(), salt.as_ref(), kdf_params)
74    })?;
75
76    let filename_nonce: [u8; 24] = generate_nonce()?;
77    let content_nonce: [u8; 24] = generate_nonce()?;
78    let plaintext_file: PlaintextFile = load_plaintext_file(&file)?;
79
80    // Bind the header fields to both ciphertexts, with distinct domains for
81    // filename and content, so neither the header nor the pairing of the two
82    // ciphertexts can be tampered with undetected.
83    let binding = HeaderBinding::new(&salt, kdf_params, &content_nonce, &filename_nonce);
84
85    let (filename_ciphertext, _): (Vec<u8>, Algorithm) = encrypt_bytes(
86        file.filename.as_bytes(),
87        key.as_bytes(),
88        &filename_nonce,
89        &binding.aad(AadPurpose::Filename),
90    )?;
91
92    let (content_ciphertext, algorithm): (Vec<u8>, Algorithm) = encrypt_bytes(
93        plaintext_file.content().as_slice(),
94        key.as_bytes(),
95        &content_nonce,
96        &binding.aad(AadPurpose::Content),
97    )?;
98
99    let header = FileHeader::new(
100        salt,
101        kdf_params.clone(),
102        content_nonce,
103        filename_nonce,
104        filename_ciphertext,
105    )?;
106
107    let encrypted_file = EncryptedFile::new(header, content_ciphertext);
108    let output_file = store_encrypted_file(&encrypted_file, output_dir)?;
109
110    let duration = start_time.elapsed();
111
112    Ok(EncryptionReport::new(
113        file.filename,
114        output_file.filename,
115        duration,
116        algorithm,
117    ))
118}