shadow_crypt_core/v1/
header.rs

1use crate::v1::key::KeyDerivationParams;
2
3/// Complete v1 file header
4#[derive(Debug, Clone)]
5pub struct FileHeader {
6    pub magic: [u8; 6],                  // 6 bytes: "SHADOW"
7    pub version: u8,                     // 1 byte: Version number (1)
8    pub header_length: u32,              // 4 byte: Total header size
9    pub salt: [u8; 16],                  // 16 bytes: Argon2id salt
10    pub kdf_memory: u32,                 // 4 bytes: Argon2id memory parameter
11    pub kdf_iterations: u32,             // 4 bytes: Argon2id iterations parameter
12    pub kdf_parallelism: u32,            // 4 bytes: Argon2id parallelism parameter
13    pub kdf_key_length: u8,              // 1 byte: XChaCha20 key length
14    pub content_nonce: [u8; 24],         // 24 bytes: XChaCha20 nonce
15    pub filename_nonce: [u8; 24],        // 24 bytes: XChaCha20 nonce for filename
16    pub filename_ciphertext_length: u16, // 2 bytes: Length of encrypted filename ciphertext
17    pub filename_ciphertext: Vec<u8>,    // Encrypted filename ciphertext (variable length)
18}
19
20impl FileHeader {
21    pub fn new(
22        salt: [u8; 16],
23        kdf_params: KeyDerivationParams,
24        content_nonce: [u8; 24],
25        filename_nonce: [u8; 24],
26        filename_ciphertext: Vec<u8>,
27    ) -> Self {
28        let filename_ciphertext_length = filename_ciphertext.len() as u16;
29        let size = Self::min_length() + filename_ciphertext.len();
30
31        FileHeader {
32            magic: *b"SHADOW",
33            version: 1,
34            header_length: size as u32,
35            salt,
36            kdf_memory: kdf_params.memory_cost,
37            kdf_iterations: kdf_params.time_cost,
38            kdf_parallelism: kdf_params.parallelism,
39            kdf_key_length: kdf_params.key_size,
40            content_nonce,
41            filename_nonce,
42            filename_ciphertext_length,
43            filename_ciphertext,
44        }
45    }
46
47    /// Minimum length of the header without the variable-length filename ciphertext
48    pub fn min_length() -> usize {
49        90 // Fixed length of the header without the variable-length filename ciphertext
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use crate::profile;
56
57    use super::*;
58
59    fn get_test_params() -> KeyDerivationParams {
60        let profile = profile::SecurityProfile::Test;
61        KeyDerivationParams::from(profile)
62    }
63
64    #[test]
65    fn default_values_are_correct() {
66        let header = FileHeader::new(
67            [0u8; 16],
68            get_test_params(),
69            [0u8; 24],
70            [0u8; 24],
71            vec![1, 2, 3, 4],
72        );
73
74        assert_eq!(&header.magic, b"SHADOW");
75        assert_eq!(header.version, 1);
76    }
77
78    #[test]
79    fn header_size_is_calculated_correctly() {
80        let filename_ciphertext = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
81        let header = FileHeader::new(
82            [0u8; 16],
83            get_test_params(),
84            [0u8; 24],
85            [0u8; 24],
86            filename_ciphertext.clone(),
87        );
88
89        let expected_size: u32 = 90 + filename_ciphertext.len() as u32;
90
91        assert_eq!(header.header_length, expected_size);
92    }
93}