1use crate::crypto::*;
6
7pub struct CypherChunk {
8 index: u64,
9 blob: Blob,
10}
11
12impl CypherChunk {
13 pub fn new(index: u64, blob: Blob) -> Self {
14 Self {
15 index: index,
16 blob: blob,
17 }
18 }
19
20 pub fn get_index(&self) -> u64 {
21 self.index
22 }
23}
24
25pub struct StreamDecryptor {
26 decryption_context: CypherContext,
28 manifest: Manifest,
30}
31
32impl StreamDecryptor {
33 pub fn with_password(
35 password: &str,
36 manifest_blob: &mut Blob,
37 ) -> Result<Self, crate::error::Error> {
38 let mut decryption_context = setup_file_decryption(manifest_blob, password)?;
39 log::debug!("Decryption context created");
40 let decrypted_blob = crypto::decrypt_blob(manifest_blob, &decryption_context)?;
41 let metadata: &Metadata = decrypted_blob
42 .get_metadata()
43 .as_ref()
44 .ok_or_else(|| crate::error::Error::DecryptionError("Missing metadata".to_string()))?;
45 if metadata.file_type != FileType::Manifest {
46 return Err(crate::error::Error::DecryptionError(
47 "Unexpected file type, a manifest was expected".to_string(),
48 ));
49 }
50 decryption_context.set_file_name(&metadata.file_name);
51 log::debug!("From decrypted data: file_name=`{}'", metadata.file_name);
52 log::debug!(
53 "About to deserialize manifest from {:02x?}",
54 decrypted_blob.get_text()
55 );
56 let manifest = Manifest::from_bytes(decrypted_blob.get_text())?;
57 decryption_context.set_mfp(manifest.mfp().clone());
58
59 Ok(Self {
60 decryption_context: decryption_context,
61 manifest,
62 })
63 }
64
65 pub(crate) fn get_decryption_context(&self) -> &CypherContext {
67 &self.decryption_context
68 }
69
70 pub fn file_name(&self) -> &String {
72 self.manifest.file_name()
73 }
74
75 pub fn file_size(&self) -> u64 {
77 self.manifest.file_size()
78 }
79
80 pub fn decrypt_chunk(
83 &self,
84 cypherchunk: &mut CypherChunk,
85 ) -> Result<DecryptedBlob, crate::error::Error> {
86 Self::do_decrypt_chunk(
87 &self.decryption_context,
88 cypherchunk,
89 self.manifest.checksum_algorithm(),
90 self.get_chunk_checksum(cypherchunk.get_index())?,
91 )
92 }
93
94 pub(crate) fn do_decrypt_chunk(
101 decryption_context: &CypherContext,
102 cypherchunk: &mut CypherChunk,
103 checksum_algorithm: ChecksumAlgorithm,
104 checksum: &Vec<u8>,
105 ) -> Result<DecryptedBlob, crate::error::Error> {
106 let mut hasher = checksum_algorithm.get_checksum_computer();
107 hasher.update(cypherchunk.blob.data());
108 let computed_checksum = hasher.finalize();
109 if checksum != &computed_checksum {
110 return Err(crate::error::Error::ChunkChecksumError(format!(
111 "Chunk {} has wrong checksum",
112 cypherchunk.get_index()
113 )));
114 }
115
116 let mut chunk_decryption_context = decryption_context.clone();
117 chunk_decryption_context.setup_chunk_decryption(cypherchunk.get_index());
118
119 crypto::decrypt_blob(&mut cypherchunk.blob, &chunk_decryption_context)
120 }
121
122 pub fn get_manifest(&self) -> &Manifest {
124 &self.manifest
125 }
126
127 fn get_chunk_checksum(&self, chunk_index: u64) -> Result<&Vec<u8>, crate::error::Error> {
130 if chunk_index >= self.manifest.chunks_count() as u64 {
131 return Err(crate::error::Error::DecryptionError(format!(
132 "Invalid chunk index: {}",
133 chunk_index
134 )));
135 }
136 Ok(self.manifest.chunks()[chunk_index as usize].checksum())
137 }
138}
139
140#[cfg(test)]
141mod tests {
142
143 use super::*;
144 use crate::{chunking::*, lcg::*, stream_encryptor::StreamEncryptor, test_utils::*};
145
146 mod utils {
147 use super::*;
148
149 pub(crate) fn create_file_contents(length: usize, lcg: &mut Lcg) -> Vec<u8> {
150 if length == 0 {
151 return Vec::new();
152 }
153 let mut buffer = Vec::<u8>::with_capacity(length);
154 let lcg_value_size = size_of_val(&lcg.clone().scrambled_next());
155 for _ in 0..(length / lcg_value_size) {
156 buffer.extend(lcg.scrambled_next().to_le_bytes());
157 }
158 let remainder = length % lcg_value_size;
159 if remainder != 0 {
160 buffer.extend_from_slice(&lcg.scrambled_next().to_le_bytes()[0..remainder]);
161 }
162 buffer
163 }
164 }
165
166 #[test]
167 fn test_decrypt_manifest() {
169 log::debug!("Test test_decrypt_manifest starts");
175 let chunk_generator = RandomChunkGenerator::with_seed(
176 20 * 1024 * 1024,
177 5 * 1024 * 1024,
178 10 * 1024 * 1024,
179 1u128,
180 );
181 let mut encryptor = StreamEncryptor::new("whatever_file_name.txt", chunk_generator, |k| {
182 Ok(AnyKeyWrapper::Argon2id(Argon2idKeyWrapper::new(
183 "password",
184 &create_argon2id_params_for_tests(),
185 k,
186 )?))
187 })
188 .expect("Encryptor creation should succeed");
189
190 let mut lcg = Lcg::new(LCG_PARAMS[0].0, LCG_PARAMS[0].1);
191 let file_contents = utils::create_file_contents(10, &mut lcg);
192 let mut chunks = Vec::new();
193 chunks.extend(encryptor.process_data(&file_contents));
194 chunks.extend(encryptor.on_end_of_data());
195 let mut encrypted_blobs = encryptor.encrypt_chunks(&chunks).unwrap();
196 encrypted_blobs
197 .iter()
198 .try_for_each(|blob| encryptor.register_encrypted_chunk(blob.0, &format!("id{}", blob.0)));
199 let mut manifest_blob = encryptor.finalize().unwrap();
200 assert_eq!(encrypted_blobs.len(), 1);
202
203 let decryptor = StreamDecryptor::with_password("password", &mut manifest_blob).unwrap();
204 let manifest = decryptor.get_manifest();
205
206 assert_eq!(manifest.file_size(), 10);
208 assert_eq!(manifest.file_name(), "whatever_file_name.txt");
209 assert_eq!(decryptor.file_name(), "whatever_file_name.txt");
210 assert_eq!(manifest.chunks_count(), 1);
211 let chunks = manifest.chunks();
212 assert_eq!(chunks.len(), 1);
213 assert_eq!(chunks[0].id(), &"id0".to_string());
214
215 let (chunk_index, blob) = encrypted_blobs.first_mut().unwrap();
217 let decrypted_blob = decryptor
218 .decrypt_chunk(&mut CypherChunk::new(*chunk_index, std::mem::take(blob)))
219 .unwrap();
220
221 assert_eq!(decrypted_blob.get_text(), &file_contents);
222 }
223
224 #[test]
225 fn test_decrypt_after_parallel_encrypt() {
227 let chunk_generator = RandomChunkGenerator::with_seed(
228 20 * 1024 * 1024,
229 5 * 1024 * 1024,
230 10 * 1024 * 1024,
231 1u128,
232 );
233 let mut encryptor = StreamEncryptor::new("whatever_file_name.txt", chunk_generator, |k| {
234 Ok(AnyKeyWrapper::Argon2id(Argon2idKeyWrapper::new(
235 "password",
236 &create_argon2id_params_for_tests(),
237 k,
238 )?))
239 })
240 .expect("Encryptor creation should succeed");
241
242 let mut lcg = Lcg::new(LCG_PARAMS[0].0, LCG_PARAMS[0].1);
243 let file_contents = utils::create_file_contents(10, &mut lcg);
244 let mut chunks = Vec::new();
245 chunks.extend(encryptor.process_data(&file_contents));
246 chunks.extend(encryptor.on_end_of_data());
247 let mut encrypted_blobs = encryptor.parallel_encrypt_chunks(2, &chunks).unwrap();
248 encrypted_blobs
249 .iter()
250 .try_for_each(|blob| encryptor.register_encrypted_chunk(blob.0, &format!("id{}", blob.0)));
251 let mut manifest_blob = encryptor.finalize().unwrap();
252 assert_eq!(encrypted_blobs.len(), 1);
254
255 let decryptor = StreamDecryptor::with_password("password", &mut manifest_blob).unwrap();
256 let manifest = decryptor.get_manifest();
257
258 assert_eq!(manifest.file_size(), 10);
260 assert_eq!(manifest.file_name(), "whatever_file_name.txt");
261 assert_eq!(decryptor.file_name(), "whatever_file_name.txt");
262 assert_eq!(manifest.chunks_count(), 1);
263 let chunks = manifest.chunks();
264 assert_eq!(chunks.len(), 1);
265 assert_eq!(chunks[0].id(), &"id0".to_string());
266
267 let (chunk_index, blob) = encrypted_blobs.first_mut().unwrap();
269 let decrypted_blob = decryptor
270 .decrypt_chunk(&mut CypherChunk::new(*chunk_index, std::mem::take(blob)))
271 .unwrap();
272
273 assert_eq!(decrypted_blob.get_text(), &file_contents);
274 }
275}