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