volaris_tools/
pack.rs

1//! This contains the logic for traversing a given directory, placing all of the files within a zip file, and encrypting the zip file. The temporary zip file is then erased with one pass.
2//!
3//! This is known as "packing" within volaris.
4//!
5//! DISCLAIMER: Encryption with compression is generally not recommended, however here it is fine. As the data is at-rest, and it's assumed you have complete control over the data you're encrypting (e.g. not attacker-controlled), there should be no problems. Feel free to use no compression if you feel otherwise.
6
7use std::cell::RefCell;
8use std::io::{BufWriter, Read, Seek, Write};
9use std::sync::Arc;
10
11use corecrypto::header::{HashingAlgorithm, HeaderType};
12use corecrypto::primitives::BLOCK_SIZE;
13use corecrypto::protected::Protected;
14use zip::write::FileOptions;
15
16use crate::storage::Storage;
17
18#[derive(Debug)]
19pub enum Error {
20    CreateArchive,
21    AddDirToArchive,
22    AddFileToArchive,
23    FinishArchive,
24    ReadData,
25    WriteData,
26    Encrypt(crate::encrypt::Error),
27}
28
29impl std::fmt::Display for Error {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        match self {
32            Error::CreateArchive => f.write_str("Unable to create archive"),
33            Error::AddDirToArchive => f.write_str("Unable to add directory to archive"),
34            Error::AddFileToArchive => f.write_str("Unable to add file to archive"),
35            Error::FinishArchive => f.write_str("Unable to finish archive"),
36            Error::ReadData => f.write_str("Unable to read data"),
37            Error::WriteData => f.write_str("Unable to write data"),
38            Error::Encrypt(inner) => write!(f, "Unable to encrypt archive: {inner}"),
39        }
40    }
41}
42
43impl std::error::Error for Error {}
44
45pub struct Request<'a, RW>
46where
47    RW: Read + Write + Seek,
48{
49    pub writer: &'a RefCell<RW>,
50    pub compress_files: Vec<crate::storage::Entry<RW>>,
51    pub compression_method: zip::CompressionMethod,
52    pub header_writer: Option<&'a RefCell<RW>>,
53    pub raw_key: Protected<Vec<u8>>,
54    // TODO: don't use external types in logic
55    pub header_type: HeaderType,
56    pub hashing_algorithm: HashingAlgorithm,
57}
58
59pub fn execute<RW>(stor: Arc<impl Storage<RW>>, req: Request<'_, RW>) -> Result<(), Error>
60where
61    RW: Read + Write + Seek,
62{
63    // 1. Create zip archive.
64    let tmp_file = stor.create_temp_file().map_err(|_| Error::CreateArchive)?;
65    {
66        let mut tmp_writer = tmp_file
67            .try_writer()
68            .map_err(|_| Error::CreateArchive)?
69            .borrow_mut();
70        let mut zip_writer = zip::ZipWriter::new(BufWriter::new(&mut *tmp_writer));
71
72        // Explicitly specify the type parameter for `FileOptions`
73        let options: zip::write::FileOptions<'_, ()> = FileOptions::default()
74            .compression_method(req.compression_method)
75            .large_file(true)
76            .unix_permissions(0o755);
77
78        // 2. Add files to the archive.
79        req.compress_files.into_iter().try_for_each(|f| {
80            let file_path = f.path().to_str().ok_or(Error::ReadData)?;
81            if f.is_dir() {
82                zip_writer
83                    .add_directory(file_path, options)
84                    .map_err(|_| Error::AddDirToArchive)?;
85            } else {
86                zip_writer
87                    .start_file(file_path, options)
88                    .map_err(|_| Error::AddFileToArchive)?;
89
90                let mut reader = f.try_reader().map_err(|_| Error::ReadData)?.borrow_mut();
91                let mut buffer = vec![0u8; BLOCK_SIZE].into_boxed_slice();
92                loop {
93                    let read_count = reader.read(&mut buffer).map_err(|_| Error::ReadData)?;
94                    zip_writer
95                        .write_all(&buffer[..read_count])
96                        .map_err(|_| Error::WriteData)?;
97                    if read_count != BLOCK_SIZE {
98                        break;
99                    }
100                }
101            }
102
103            Ok(())
104        })?;
105
106        // 3. Close archive and switch writer to reader.
107        zip_writer.finish().map_err(|_| Error::FinishArchive)?;
108    }
109
110    let buf_capacity = stor.file_len(&tmp_file).map_err(|_| Error::FinishArchive)?;
111
112    // 4. Encrypt zip archive
113    let encrypt_res = crate::encrypt::execute(crate::encrypt::Request {
114        reader: tmp_file.try_reader().map_err(|_| Error::FinishArchive)?,
115        writer: req.writer,
116        header_writer: req.header_writer,
117        raw_key: req.raw_key,
118        header_type: req.header_type,
119        hashing_algorithm: req.hashing_algorithm,
120    })
121    .map_err(Error::Encrypt);
122
123    // 5. Finally erase zip archive with zeros.
124    crate::overwrite::execute(crate::overwrite::Request {
125        buf_capacity,
126        writer: tmp_file.try_writer().map_err(|_| Error::FinishArchive)?,
127        passes: 2,
128    })
129    .ok();
130
131    stor.remove_file(tmp_file).ok();
132
133    encrypt_res
134}
135
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use std::io::Read;
141
142    use corecrypto::header::{HeaderType, HeaderVersion};
143    use corecrypto::primitives::{Algorithm, Mode};
144
145    use crate::encrypt::tests::PASSWORD;
146    use crate::storage::{InMemoryStorage, Storage};
147
148    const ENCRYPTED_PACKED_BAR_DIR: [u8; 1202] = [
149        222, 5, 14, 1, 12, 1, 173, 240, 60, 45, 230, 243, 58, 160, 69, 50, 217, 192, 66, 223, 124,
150        190, 148, 91, 92, 129, 0, 0, 0, 0, 0, 0, 223, 181, 71, 240, 140, 106, 41, 36, 82, 150, 105,
151        215, 159, 108, 234, 246, 25, 19, 65, 206, 177, 146, 15, 174, 209, 129, 82, 2, 62, 76, 129,
152        34, 136, 189, 11, 98, 105, 54, 146, 71, 102, 166, 97, 177, 207, 62, 194, 132, 38, 87, 173,
153        240, 60, 45, 230, 243, 58, 160, 69, 50, 217, 192, 66, 223, 124, 190, 148, 91, 92, 129, 50,
154        126, 110, 254, 58, 206, 16, 183, 233, 128, 23, 223, 81, 30, 214, 132, 32, 104, 51, 119, 0,
155        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
156        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
157        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
158        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
159        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
160        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
161        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
162        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
163        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
164        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 64, 6, 177, 49,
165        139, 218, 8, 121, 228, 19, 5, 8, 117, 33, 131, 131, 70, 76, 147, 108, 49, 191, 191, 127,
166        223, 64, 127, 248, 65, 201, 130, 166, 129, 195, 245, 241, 188, 143, 148, 191, 86, 7, 102,
167        124, 253, 12, 44, 172, 79, 236, 207, 68, 229, 117, 49, 250, 55, 6, 48, 86, 48, 244, 189,
168        137, 27, 142, 241, 44, 118, 35, 5, 138, 237, 47, 248, 108, 30, 224, 42, 91, 16, 216, 14,
169        235, 132, 33, 123, 83, 188, 196, 205, 18, 71, 152, 231, 231, 127, 182, 29, 156, 157, 203,
170        178, 178, 3, 216, 51, 84, 28, 67, 91, 255, 14, 124, 180, 131, 80, 48, 27, 111, 195, 39,
171        127, 37, 231, 111, 82, 132, 168, 253, 149, 230, 199, 161, 78, 6, 175, 98, 210, 9, 25, 145,
172        199, 151, 38, 142, 199, 217, 35, 247, 168, 73, 138, 94, 175, 45, 0, 184, 252, 55, 250, 19,
173        8, 79, 247, 38, 230, 133, 143, 66, 27, 69, 96, 183, 201, 238, 81, 114, 131, 123, 229, 78,
174        39, 140, 151, 4, 196, 49, 37, 58, 12, 48, 243, 83, 111, 84, 6, 82, 249, 200, 120, 238, 190,
175        136, 135, 189, 34, 237, 52, 18, 23, 43, 164, 113, 31, 111, 221, 119, 216, 110, 0, 74, 53,
176        81, 86, 83, 234, 70, 69, 194, 224, 96, 26, 47, 133, 49, 147, 204, 96, 125, 165, 105, 182,
177        161, 2, 143, 225, 195, 95, 64, 24, 49, 236, 210, 124, 32, 214, 69, 201, 5, 73, 5, 7, 160,
178        233, 35, 202, 226, 40, 104, 45, 214, 0, 39, 55, 167, 203, 184, 145, 150, 233, 119, 115,
179        246, 55, 162, 5, 154, 147, 144, 69, 217, 185, 39, 82, 223, 87, 132, 164, 148, 85, 234, 15,
180        160, 2, 214, 133, 27, 73, 53, 27, 86, 53, 215, 96, 142, 85, 25, 127, 11, 111, 19, 1, 72,
181        74, 92, 16, 14, 98, 20, 203, 163, 227, 160, 192, 158, 223, 99, 116, 212, 137, 101, 150,
182        182, 125, 244, 59, 20, 157, 129, 149, 34, 21, 136, 185, 41, 242, 168, 45, 135, 100, 219,
183        239, 132, 211, 238, 37, 242, 139, 218, 120, 112, 158, 75, 53, 172, 162, 136, 202, 94, 117,
184        152, 175, 205, 34, 198, 99, 49, 174, 187, 80, 151, 225, 169, 120, 192, 77, 61, 38, 2, 158,
185        45, 216, 78, 215, 134, 255, 7, 46, 144, 119, 60, 168, 202, 24, 239, 147, 122, 58, 48, 50,
186        178, 58, 153, 243, 242, 169, 238, 42, 78, 123, 37, 181, 17, 109, 175, 84, 6, 212, 122, 89,
187        60, 111, 248, 41, 156, 214, 222, 151, 212, 52, 10, 221, 69, 1, 215, 170, 76, 149, 134, 241,
188        212, 217, 131, 179, 34, 240, 124, 224, 192, 105, 34, 254, 165, 211, 100, 169, 240, 171,
189        131, 50, 80, 54, 254, 128, 179, 233, 223, 22, 39, 56, 205, 221, 76, 177, 197, 164, 140,
190        181, 42, 154, 82, 239, 240, 127, 211, 45, 146, 57, 154, 151, 153, 112, 215, 222, 199, 37,
191        44, 98, 118, 182, 189, 15, 139, 88, 227, 37, 149, 107, 13, 123, 201, 51, 61, 67, 220, 161,
192        13, 72, 176, 39, 157, 128, 105, 144, 10, 46, 29, 113, 1, 76, 162, 157, 200, 213, 175, 107,
193        128, 13, 47, 170, 216, 107, 48, 241, 149, 219, 20, 186, 74, 210, 5, 210, 18, 201, 78, 159,
194        121, 180, 195, 154, 176, 154, 255, 21, 5, 86, 212, 181, 237, 131, 116, 59, 241, 57, 24,
195        102, 126, 132, 135, 154, 99, 217, 2, 201, 139, 202, 125, 64, 165, 195, 210, 255, 165, 197,
196        172, 166, 27, 200, 226, 158, 225, 224, 10, 150, 97, 2, 77, 73, 51, 112, 201, 146, 74, 245,
197        95, 191, 244, 128, 170, 109, 227, 44, 24, 11, 216, 35, 137, 61, 120, 207, 212, 57, 229, 70,
198        152, 118, 92, 235, 187, 55, 189, 231, 126, 15, 86, 66, 78, 251, 39, 181, 191, 193, 226,
199        199, 131, 61, 145, 177, 76, 168, 0, 235, 172, 21, 213, 87, 81, 176, 135, 139, 61, 3, 91,
200        67, 84, 199, 40, 113, 140, 68, 174, 34, 199, 50, 33, 187, 208, 209, 155, 237, 140, 16, 204,
201        135, 151, 241, 28, 95, 87, 91, 169, 160, 1, 206, 18, 220, 65, 236, 52, 63, 184, 226, 237,
202        129, 19, 170, 194, 11, 154, 168, 110, 242, 19, 167, 195, 205, 68, 4, 151, 99, 196, 164, 13,
203        137, 140, 175, 134, 102, 47, 63, 0, 229, 73, 218, 226, 121, 230, 98, 31, 102, 161, 40, 233,
204        229, 39, 224, 19, 92, 220, 151, 154, 193, 191, 30,
205    ];
206
207    #[test]
208    fn should_pack_bar_directory() {
209        let stor = Arc::new(InMemoryStorage::default());
210        stor.add_hello_txt();
211        stor.add_bar_foo_folder_with_hidden();
212
213        let file = stor.read_file("bar/").unwrap();
214        let mut compress_files = stor.read_dir(&file).unwrap();
215        compress_files.sort_by(|a, b| a.path().cmp(b.path()));
216
217        let output_file = stor.create_file("bar.zip.enc").unwrap();
218
219        let req = Request {
220            compress_files,
221            compression_method: zip::CompressionMethod::Stored,
222            writer: output_file.try_writer().unwrap(),
223            header_writer: None,
224            raw_key: Protected::new(PASSWORD.to_vec()),
225            header_type: HeaderType {
226                version: HeaderVersion::V5,
227                algorithm: Algorithm::XChaCha20Poly1305,
228                mode: Mode::StreamMode,
229            },
230            hashing_algorithm: HashingAlgorithm::Blake3Balloon(5),
231        };
232
233        match execute(stor, req) {
234            Ok(()) => {
235                let reader = &mut *output_file.try_writer().unwrap().borrow_mut();
236                reader.rewind().unwrap();
237
238                let mut content = vec![];
239                reader.read_to_end(&mut content).unwrap();
240
241                assert_eq!(content, ENCRYPTED_PACKED_BAR_DIR.to_vec());
242            }
243            _ => unreachable!(),
244        }
245    }
246}