Skip to main content

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    /// Changing the fixed fields above requires updating this value.
49    pub fn min_length() -> usize {
50        6  // magic ("SHADOW")
51        + 1  // version (u8)
52        + 4  // header_length (u32)
53        + 16 // salt ([u8; 16])
54        + 4  // kdf_memory (u32)
55        + 4  // kdf_iterations (u32)
56        + 4  // kdf_parallelism (u32)
57        + 1  // kdf_key_length (u8)
58        + 24 // content_nonce ([u8; 24])
59        + 24 // filename_nonce ([u8; 24])
60        + 2 // filename_ciphertext_length (u16)
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use crate::profile;
67
68    use super::*;
69
70    fn get_test_params() -> KeyDerivationParams {
71        let profile = profile::SecurityProfile::Test;
72        KeyDerivationParams::from(profile)
73    }
74
75    #[test]
76    fn default_values_are_correct() {
77        let header = FileHeader::new(
78            [0u8; 16],
79            get_test_params(),
80            [0u8; 24],
81            [0u8; 24],
82            vec![1, 2, 3, 4],
83        );
84
85        assert_eq!(&header.magic, b"SHADOW");
86        assert_eq!(header.version, 1);
87    }
88
89    #[test]
90    fn header_size_is_calculated_correctly() {
91        let filename_ciphertext = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
92        let header = FileHeader::new(
93            [0u8; 16],
94            get_test_params(),
95            [0u8; 24],
96            [0u8; 24],
97            filename_ciphertext.clone(),
98        );
99
100        let expected_size: u32 = 90 + filename_ciphertext.len() as u32;
101
102        assert_eq!(header.header_length, expected_size);
103    }
104}