1use crate::{
19 algorithm::Algorithm,
20 errors::{FileError, HeaderError, KeyDerivationError},
21 file::{FileMetadata, PlaintextFile},
22 memory::{SecureBytes, SecureKey, SecureString},
23 report::KeyDerivationReport,
24 v1, v2, v3,
25 version::{Version, read_file_version},
26};
27
28pub const MAX_HEADER_LEN: usize = {
34 let v1_len = v1::header::FileHeader::min_length();
35 let v2_len = v2::header::FileHeader::min_length();
36 let v3_len = v3::header::FileHeader::min_length();
37 let max12 = if v1_len > v2_len { v1_len } else { v2_len };
38 (if max12 > v3_len { max12 } else { v3_len }) + u16::MAX as usize
39};
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub struct KdfRequest {
48 pub salt: [u8; 16],
49 pub memory_cost: u32,
50 pub time_cost: u32,
51 pub parallelism: u32,
52 pub key_size: u8,
53}
54
55pub struct ParsedFile(Inner);
57
58enum Inner {
59 V1(v1::file::EncryptedFile),
60 V2(v2::file::EncryptedFile),
61 V3(v3::file::EncryptedFile),
62}
63
64impl ParsedFile {
65 pub fn parse(bytes: &[u8]) -> Result<Self, HeaderError> {
73 let inner = match read_file_version(bytes)? {
74 Version::V1 => Inner::V1(v1::file::EncryptedFile::from_bytes(bytes)?),
75 Version::V2 => Inner::V2(v2::file::EncryptedFile::from_bytes(bytes)?),
76 Version::V3 => Inner::V3(v3::file::EncryptedFile::from_bytes(bytes)?),
77 };
78 Ok(Self(inner))
79 }
80
81 pub fn version(&self) -> Version {
82 match &self.0 {
83 Inner::V1(_) => Version::V1,
84 Inner::V2(_) => Version::V2,
85 Inner::V3(_) => Version::V3,
86 }
87 }
88
89 pub fn algorithm(&self) -> Algorithm {
91 match &self.0 {
92 Inner::V1(_) => v1::ALGORITHM,
93 Inner::V2(_) => v2::ALGORITHM,
94 Inner::V3(_) => v3::ALGORITHM,
95 }
96 }
97
98 pub fn header_length(&self) -> usize {
101 match &self.0 {
102 Inner::V1(f) => f.header().header_length(),
103 Inner::V2(f) => f.header().header_length(),
104 Inner::V3(f) => f.header().header_length(),
105 }
106 }
107
108 pub fn kdf_request(&self) -> KdfRequest {
112 match &self.0 {
113 Inner::V1(f) => {
114 let p = f.header().kdf_params();
115 KdfRequest {
116 salt: *f.header().salt(),
117 memory_cost: p.memory_cost,
118 time_cost: p.time_cost,
119 parallelism: p.parallelism,
120 key_size: p.key_size,
121 }
122 }
123 Inner::V2(f) => {
124 let p = f.header().kdf_params();
125 KdfRequest {
126 salt: *f.header().salt(),
127 memory_cost: p.memory_cost,
128 time_cost: p.time_cost,
129 parallelism: p.parallelism,
130 key_size: p.key_size,
131 }
132 }
133 Inner::V3(f) => {
134 let p = f.header().kdf_params();
135 KdfRequest {
136 salt: *f.header().salt(),
137 memory_cost: p.memory_cost,
138 time_cost: p.time_cost,
139 parallelism: p.parallelism,
140 key_size: p.key_size,
141 }
142 }
143 }
144 }
145
146 pub fn derive_key(
149 &self,
150 password: &[u8],
151 ) -> Result<(SecureKey, KeyDerivationReport), KeyDerivationError> {
152 match &self.0 {
153 Inner::V1(f) => f
154 .header()
155 .kdf_params()
156 .derive_key(password, f.header().salt()),
157 Inner::V2(f) => f
158 .header()
159 .kdf_params()
160 .derive_key(password, f.header().salt()),
161 Inner::V3(f) => f
162 .header()
163 .kdf_params()
164 .derive_key(password, f.header().salt()),
165 }
166 }
167
168 pub fn decrypt(&self, key: &SecureKey) -> Result<PlaintextFile, FileError> {
172 match &self.0 {
173 Inner::V1(f) => f.decrypt(key),
174 Inner::V2(f) => f.decrypt(key),
175 Inner::V3(f) => f.decrypt(key),
176 }
177 }
178
179 pub fn decrypt_filename(&self, key: &SecureKey) -> Result<SecureString, FileError> {
181 match &self.0 {
182 Inner::V1(f) => f.header().decrypt_filename(key),
183 Inner::V2(f) => f.header().decrypt_filename(key),
184 Inner::V3(f) => Ok(f.header().decrypt_metadata(key)?.filename().clone()),
185 }
186 }
187
188 pub fn decrypt_metadata(&self, key: &SecureKey) -> Result<FileMetadata, FileError> {
191 match &self.0 {
192 Inner::V1(f) => Ok(FileMetadata::new(
193 f.header().decrypt_filename(key)?,
194 None,
195 None,
196 )),
197 Inner::V2(f) => Ok(FileMetadata::new(
198 f.header().decrypt_filename(key)?,
199 None,
200 None,
201 )),
202 Inner::V3(f) => f.header().decrypt_metadata(key),
203 }
204 }
205
206 pub fn content_decryptor(&self, key: &SecureKey) -> ContentDecryptor<'_> {
211 let inner = match &self.0 {
212 Inner::V1(f) => DecryptorInner::V1 {
213 header: f.header(),
214 key: key.clone(),
215 done: false,
216 },
217 Inner::V2(f) => DecryptorInner::V2 {
218 header: f.header(),
219 key: key.clone(),
220 done: false,
221 },
222 Inner::V3(f) => DecryptorInner::V3(v3::stream::StreamOpener::new(f.header(), key)),
223 };
224 ContentDecryptor { inner }
225 }
226}
227
228pub struct ContentDecryptor<'a> {
236 inner: DecryptorInner<'a>,
237}
238
239enum DecryptorInner<'a> {
240 V1 {
241 header: &'a v1::header::FileHeader,
242 key: SecureKey,
243 done: bool,
244 },
245 V2 {
246 header: &'a v2::header::FileHeader,
247 key: SecureKey,
248 done: bool,
249 },
250 V3(v3::stream::StreamOpener),
251}
252
253impl ContentDecryptor<'_> {
254 pub fn chunk_len(&self) -> Option<usize> {
257 match &self.inner {
258 DecryptorInner::V1 { .. } | DecryptorInner::V2 { .. } => None,
259 DecryptorInner::V3(opener) => Some(opener.chunk_ciphertext_len()),
260 }
261 }
262
263 pub fn decrypt_chunk(
266 &mut self,
267 ciphertext: &[u8],
268 is_last: bool,
269 ) -> Result<SecureBytes, FileError> {
270 match &mut self.inner {
271 DecryptorInner::V1 { header, key, done } => {
272 whole_content_chunk(done, is_last)?;
273 header.decrypt_content(ciphertext, key)
274 }
275 DecryptorInner::V2 { header, key, done } => {
276 whole_content_chunk(done, is_last)?;
277 header.decrypt_content(ciphertext, key)
278 }
279 DecryptorInner::V3(opener) => opener.open_chunk(ciphertext, is_last),
280 }
281 }
282
283 pub fn finished(&self) -> bool {
286 match &self.inner {
287 DecryptorInner::V1 { done, .. } | DecryptorInner::V2 { done, .. } => *done,
288 DecryptorInner::V3(opener) => opener.finished(),
289 }
290 }
291}
292
293fn whole_content_chunk(done: &mut bool, is_last: bool) -> Result<(), FileError> {
295 if *done || !is_last {
296 return Err(FileError::Crypt(
297 crate::errors::CryptError::DecryptionError(
298 "this format's content must be decrypted as a single final chunk".to_string(),
299 ),
300 ));
301 }
302 *done = true;
303 Ok(())
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309
310 fn seal_v3(password: &[u8], filename: &str, content: &[u8]) -> Vec<u8> {
311 let salt = [1u8; 16];
312 let params = v3::key::KeyDerivationParams::test_defaults();
313 let (key, _) = params.derive_key(password, &salt).unwrap();
314 let metadata =
315 FileMetadata::new(SecureString::new(filename.to_string()), None, Some(0o600));
316 v3::file::EncryptedFile::seal(&metadata, content, &key, params, salt, [2u8; 16], [3u8; 24])
317 .unwrap()
318 .to_bytes()
319 }
320
321 fn seal_v2(password: &[u8], filename: &str, content: &[u8]) -> Vec<u8> {
322 let salt = [1u8; 16];
323 let params = v2::key::KeyDerivationParams::test_defaults();
324 let (key, _) = params.derive_key(password, &salt).unwrap();
325 let plaintext = PlaintextFile::new(
326 SecureString::new(filename.to_string()),
327 SecureBytes::new(content.to_vec()),
328 );
329 v2::file::EncryptedFile::seal(&plaintext, &key, params, salt, [2u8; 24], [3u8; 24])
330 .unwrap()
331 .to_bytes()
332 }
333
334 fn seal_v1(password: &[u8], filename: &str, content: &[u8]) -> Vec<u8> {
335 let salt = [1u8; 16];
336 let params = v1::key::KeyDerivationParams::test_defaults();
337 let content_nonce = [2u8; 24];
338 let filename_nonce = [3u8; 24];
339 let (key, _) = params.derive_key(password, &salt).unwrap();
340 let (filename_ct, _) =
341 v1::crypt::encrypt_bytes(filename.as_bytes(), key.as_bytes(), &filename_nonce).unwrap();
342 let (content_ct, _) =
343 v1::crypt::encrypt_bytes(content, key.as_bytes(), &content_nonce).unwrap();
344 let header =
345 v1::header::FileHeader::new(salt, params, content_nonce, filename_nonce, filename_ct);
346 v1::file::EncryptedFile::new(header, content_ct).to_bytes()
347 }
348
349 #[test]
350 fn parse_dispatches_on_version_byte() {
351 let v1_bytes = seal_v1(b"pw", "a.txt", b"one");
352 let v2_bytes = seal_v2(b"pw", "b.txt", b"two");
353 let v3_bytes = seal_v3(b"pw", "c.txt", b"three");
354
355 assert_eq!(ParsedFile::parse(&v1_bytes).unwrap().version(), Version::V1);
356 assert_eq!(ParsedFile::parse(&v2_bytes).unwrap().version(), Version::V2);
357 assert_eq!(ParsedFile::parse(&v3_bytes).unwrap().version(), Version::V3);
358 }
359
360 #[test]
361 fn parse_rejects_unknown_version() {
362 let mut bytes = seal_v2(b"pw", "a.txt", b"x");
363 bytes[6] = 99;
364 assert!(ParsedFile::parse(&bytes).is_err());
365 }
366
367 #[test]
368 fn decrypt_round_trips_all_versions() {
369 for bytes in [
370 seal_v1(b"pw", "name.txt", b"content"),
371 seal_v2(b"pw", "name.txt", b"content"),
372 seal_v3(b"pw", "name.txt", b"content"),
373 ] {
374 let parsed = ParsedFile::parse(&bytes).unwrap();
375 let (key, _) = parsed.derive_key(b"pw").unwrap();
376 let plaintext = parsed.decrypt(&key).unwrap();
377 assert_eq!(plaintext.filename().as_str(), "name.txt");
378 assert_eq!(plaintext.content().as_slice(), b"content");
379 }
380 }
381
382 #[test]
383 fn decrypt_filename_works_on_header_only_prefix() {
384 for bytes in [
385 seal_v1(b"pw", "name.txt", b"content"),
386 seal_v2(b"pw", "name.txt", b"content"),
387 seal_v3(b"pw", "name.txt", b"content"),
388 ] {
389 let prefix = &bytes[..bytes.len().min(MAX_HEADER_LEN)];
392 let parsed = ParsedFile::parse(prefix).unwrap();
393 let (key, _) = parsed.derive_key(b"pw").unwrap();
394 assert_eq!(parsed.decrypt_filename(&key).unwrap().as_str(), "name.txt");
395 }
396 }
397
398 #[test]
401 fn content_decryptor_round_trips_all_versions() {
402 for bytes in [
403 seal_v1(b"pw", "name.txt", b"content"),
404 seal_v2(b"pw", "name.txt", b"content"),
405 seal_v3(b"pw", "name.txt", b"content"),
406 ] {
407 let parsed = ParsedFile::parse(&bytes).unwrap();
408 let (key, _) = parsed.derive_key(b"pw").unwrap();
409 let content_bytes = &bytes[parsed.header_length()..];
410
411 let mut decryptor = parsed.content_decryptor(&key);
412 let mut out = Vec::new();
413 match decryptor.chunk_len() {
414 None => {
415 out.extend_from_slice(
416 decryptor
417 .decrypt_chunk(content_bytes, true)
418 .unwrap()
419 .as_slice(),
420 );
421 }
422 Some(n) => {
423 let pieces: Vec<&[u8]> = content_bytes.chunks(n).collect();
424 for (i, piece) in pieces.iter().enumerate() {
425 out.extend_from_slice(
426 decryptor
427 .decrypt_chunk(piece, i == pieces.len() - 1)
428 .unwrap()
429 .as_slice(),
430 );
431 }
432 }
433 }
434
435 assert!(decryptor.finished());
436 assert_eq!(out, b"content");
437 }
438 }
439
440 #[test]
441 fn decrypt_metadata_reports_fields_by_version() {
442 let v2_bytes = seal_v2(b"pw", "name.txt", b"content");
443 let parsed = ParsedFile::parse(&v2_bytes).unwrap();
444 let (key, _) = parsed.derive_key(b"pw").unwrap();
445 let meta = parsed.decrypt_metadata(&key).unwrap();
446 assert_eq!(meta.filename().as_str(), "name.txt");
447 assert_eq!(meta.mode(), None);
448
449 let v3_bytes = seal_v3(b"pw", "name.txt", b"content");
450 let parsed = ParsedFile::parse(&v3_bytes).unwrap();
451 let (key, _) = parsed.derive_key(b"pw").unwrap();
452 let meta = parsed.decrypt_metadata(&key).unwrap();
453 assert_eq!(meta.filename().as_str(), "name.txt");
454 assert_eq!(meta.mode(), Some(0o600));
455 }
456
457 #[test]
458 fn kdf_request_reflects_header_params() {
459 let bytes = seal_v2(b"pw", "a.txt", b"x");
460 let parsed = ParsedFile::parse(&bytes).unwrap();
461 let req = parsed.kdf_request();
462
463 let expected = v2::key::KeyDerivationParams::test_defaults();
464 assert_eq!(req.salt, [1u8; 16]);
465 assert_eq!(req.memory_cost, expected.memory_cost);
466 assert_eq!(req.time_cost, expected.time_cost);
467 assert_eq!(req.parallelism, expected.parallelism);
468 assert_eq!(req.key_size, expected.key_size);
469 }
470}