]>
Commit | Line | Data |
---|---|---|
3025b3a5 DM |
1 | use failure::*; |
2 | use std::convert::TryInto; | |
3025b3a5 | 3 | |
5485b579 | 4 | use proxmox::tools::io::{ReadExt, WriteExt}; |
9f83e0f7 | 5 | |
781ac11c DM |
6 | const MAX_BLOB_SIZE: usize = 128*1024*1024; |
7 | ||
3025b3a5 DM |
8 | use super::*; |
9 | ||
10 | /// Data blob binary storage format | |
11 | /// | |
863be2e6 | 12 | /// Data blobs store arbitrary binary data (< 128MB), and can be |
3025b3a5 DM |
13 | /// compressed and encrypted. A simply binary format is used to store |
14 | /// them on disk or transfer them over the network. Please use index | |
15 | /// files to store large data files (".fidx" of ".didx"). | |
16 | /// | |
3025b3a5 DM |
17 | pub struct DataBlob { |
18 | raw_data: Vec<u8>, // tagged, compressed, encryped data | |
19 | } | |
20 | ||
21 | impl DataBlob { | |
22 | ||
5d15cb49 DM |
23 | pub fn header_size(magic: &[u8; 8]) -> usize { |
24 | match magic { | |
25 | &UNCOMPRESSED_CHUNK_MAGIC_1_0 => std::mem::size_of::<DataChunkHeader>(), | |
26 | &COMPRESSED_CHUNK_MAGIC_1_0 => std::mem::size_of::<DataChunkHeader>(), | |
27 | &ENCRYPTED_CHUNK_MAGIC_1_0 => std::mem::size_of::<EncryptedDataChunkHeader>(), | |
28 | &ENCR_COMPR_CHUNK_MAGIC_1_0 => std::mem::size_of::<EncryptedDataChunkHeader>(), | |
29 | ||
30 | &UNCOMPRESSED_BLOB_MAGIC_1_0 => std::mem::size_of::<DataBlobHeader>(), | |
31 | &COMPRESSED_BLOB_MAGIC_1_0 => std::mem::size_of::<DataBlobHeader>(), | |
32 | &ENCRYPTED_BLOB_MAGIC_1_0 => std::mem::size_of::<EncryptedDataBlobHeader>(), | |
33 | &ENCR_COMPR_BLOB_MAGIC_1_0 => std::mem::size_of::<EncryptedDataBlobHeader>(), | |
34 | &AUTHENTICATED_BLOB_MAGIC_1_0 => std::mem::size_of::<AuthenticatedDataBlobHeader>(), | |
35 | &AUTH_COMPR_BLOB_MAGIC_1_0 => std::mem::size_of::<AuthenticatedDataBlobHeader>(), | |
36 | _ => panic!("unknown blob magic"), | |
37 | } | |
38 | } | |
39 | ||
3025b3a5 DM |
40 | /// accessor to raw_data field |
41 | pub fn raw_data(&self) -> &[u8] { | |
42 | &self.raw_data | |
43 | } | |
44 | ||
cb08ac3e DM |
45 | /// Consume self and returns raw_data |
46 | pub fn into_inner(self) -> Vec<u8> { | |
47 | self.raw_data | |
48 | } | |
49 | ||
3025b3a5 DM |
50 | /// accessor to chunk type (magic number) |
51 | pub fn magic(&self) -> &[u8; 8] { | |
52 | self.raw_data[0..8].try_into().unwrap() | |
53 | } | |
54 | ||
b7f4f27d DM |
55 | /// accessor to crc32 checksum |
56 | pub fn crc(&self) -> u32 { | |
991abfa8 DM |
57 | let crc_o = proxmox::tools::offsetof!(DataBlobHeader, crc); |
58 | u32::from_le_bytes(self.raw_data[crc_o..crc_o+4].try_into().unwrap()) | |
b7f4f27d DM |
59 | } |
60 | ||
61 | // set the CRC checksum field | |
62 | pub fn set_crc(&mut self, crc: u32) { | |
991abfa8 DM |
63 | let crc_o = proxmox::tools::offsetof!(DataBlobHeader, crc); |
64 | self.raw_data[crc_o..crc_o+4].copy_from_slice(&crc.to_le_bytes()); | |
b7f4f27d DM |
65 | } |
66 | ||
67 | /// compute the CRC32 checksum | |
cb08ac3e | 68 | pub fn compute_crc(&self) -> u32 { |
b7f4f27d | 69 | let mut hasher = crc32fast::Hasher::new(); |
5d15cb49 | 70 | let start = Self::header_size(self.magic()); // start after HEAD |
991abfa8 | 71 | hasher.update(&self.raw_data[start..]); |
b7f4f27d DM |
72 | hasher.finalize() |
73 | } | |
74 | ||
b208da83 DM |
75 | /// verify the CRC32 checksum |
76 | pub fn verify_crc(&self) -> Result<(), Error> { | |
77 | let expected_crc = self.compute_crc(); | |
78 | if expected_crc != self.crc() { | |
79 | bail!("Data blob has wrong CRC checksum."); | |
80 | } | |
81 | Ok(()) | |
82 | } | |
83 | ||
69ecd8d5 | 84 | /// Create a DataBlob, optionally compressed and/or encrypted |
3025b3a5 DM |
85 | pub fn encode( |
86 | data: &[u8], | |
87 | config: Option<&CryptConfig>, | |
88 | compress: bool, | |
89 | ) -> Result<Self, Error> { | |
90 | ||
781ac11c | 91 | if data.len() > MAX_BLOB_SIZE { |
3025b3a5 DM |
92 | bail!("data blob too large ({} bytes).", data.len()); |
93 | } | |
94 | ||
f889b158 | 95 | let mut blob = if let Some(config) = config { |
3025b3a5 | 96 | |
0066c6d9 DM |
97 | let compr_data; |
98 | let (_compress, data, magic) = if compress { | |
99 | compr_data = zstd::block::compress(data, 1)?; | |
100 | // Note: We only use compression if result is shorter | |
101 | if compr_data.len() < data.len() { | |
102 | (true, &compr_data[..], ENCR_COMPR_BLOB_MAGIC_1_0) | |
103 | } else { | |
104 | (false, data, ENCRYPTED_BLOB_MAGIC_1_0) | |
105 | } | |
106 | } else { | |
107 | (false, data, ENCRYPTED_BLOB_MAGIC_1_0) | |
108 | }; | |
109 | ||
110 | let header_len = std::mem::size_of::<EncryptedDataBlobHeader>(); | |
111 | let mut raw_data = Vec::with_capacity(data.len() + header_len); | |
112 | ||
113 | let dummy_head = EncryptedDataBlobHeader { | |
114 | head: DataBlobHeader { magic: [0u8; 8], crc: [0; 4] }, | |
115 | iv: [0u8; 16], | |
116 | tag: [0u8; 16], | |
117 | }; | |
5485b579 WB |
118 | unsafe { |
119 | raw_data.write_le_value(dummy_head)?; | |
120 | } | |
0066c6d9 DM |
121 | |
122 | let (iv, tag) = config.encrypt_to(data, &mut raw_data)?; | |
123 | ||
124 | let head = EncryptedDataBlobHeader { | |
125 | head: DataBlobHeader { magic, crc: [0; 4] }, iv, tag, | |
126 | }; | |
127 | ||
5485b579 WB |
128 | unsafe { |
129 | (&mut raw_data[0..header_len]).write_le_value(head)?; | |
130 | } | |
0066c6d9 | 131 | |
f889b158 | 132 | DataBlob { raw_data } |
3025b3a5 DM |
133 | } else { |
134 | ||
991abfa8 | 135 | let max_data_len = data.len() + std::mem::size_of::<DataBlobHeader>(); |
3025b3a5 | 136 | if compress { |
991abfa8 | 137 | let mut comp_data = Vec::with_capacity(max_data_len); |
3025b3a5 | 138 | |
991abfa8 DM |
139 | let head = DataBlobHeader { |
140 | magic: COMPRESSED_BLOB_MAGIC_1_0, | |
141 | crc: [0; 4], | |
142 | }; | |
5485b579 WB |
143 | unsafe { |
144 | comp_data.write_le_value(head)?; | |
145 | } | |
b7f4f27d | 146 | |
3025b3a5 DM |
147 | zstd::stream::copy_encode(data, &mut comp_data, 1)?; |
148 | ||
991abfa8 | 149 | if comp_data.len() < max_data_len { |
eecb2356 DM |
150 | let mut blob = DataBlob { raw_data: comp_data }; |
151 | blob.set_crc(blob.compute_crc()); | |
152 | return Ok(blob); | |
3025b3a5 DM |
153 | } |
154 | } | |
155 | ||
991abfa8 | 156 | let mut raw_data = Vec::with_capacity(max_data_len); |
3025b3a5 | 157 | |
991abfa8 DM |
158 | let head = DataBlobHeader { |
159 | magic: UNCOMPRESSED_BLOB_MAGIC_1_0, | |
160 | crc: [0; 4], | |
161 | }; | |
5485b579 WB |
162 | unsafe { |
163 | raw_data.write_le_value(head)?; | |
164 | } | |
3025b3a5 DM |
165 | raw_data.extend_from_slice(data); |
166 | ||
f889b158 DM |
167 | DataBlob { raw_data } |
168 | }; | |
169 | ||
170 | blob.set_crc(blob.compute_crc()); | |
171 | ||
172 | Ok(blob) | |
3025b3a5 DM |
173 | } |
174 | ||
175 | /// Decode blob data | |
176 | pub fn decode(self, config: Option<&CryptConfig>) -> Result<Vec<u8>, Error> { | |
177 | ||
178 | let magic = self.magic(); | |
179 | ||
180 | if magic == &UNCOMPRESSED_BLOB_MAGIC_1_0 { | |
991abfa8 DM |
181 | let data_start = std::mem::size_of::<DataBlobHeader>(); |
182 | return Ok(self.raw_data[data_start..].to_vec()); | |
3025b3a5 | 183 | } else if magic == &COMPRESSED_BLOB_MAGIC_1_0 { |
991abfa8 | 184 | let data_start = std::mem::size_of::<DataBlobHeader>(); |
781ac11c | 185 | let data = zstd::block::decompress(&self.raw_data[data_start..], MAX_BLOB_SIZE)?; |
3025b3a5 | 186 | return Ok(data); |
3025b3a5 | 187 | } else if magic == &ENCR_COMPR_BLOB_MAGIC_1_0 || magic == &ENCRYPTED_BLOB_MAGIC_1_0 { |
9f83e0f7 | 188 | let header_len = std::mem::size_of::<EncryptedDataBlobHeader>(); |
ba01828d DM |
189 | let head = unsafe { |
190 | (&self.raw_data[..header_len]).read_le_value::<EncryptedDataBlobHeader>()? | |
191 | }; | |
9f83e0f7 | 192 | |
3025b3a5 DM |
193 | if let Some(config) = config { |
194 | let data = if magic == &ENCR_COMPR_BLOB_MAGIC_1_0 { | |
9f83e0f7 | 195 | config.decode_compressed_chunk(&self.raw_data[header_len..], &head.iv, &head.tag)? |
3025b3a5 | 196 | } else { |
9f83e0f7 | 197 | config.decode_uncompressed_chunk(&self.raw_data[header_len..], &head.iv, &head.tag)? |
3025b3a5 DM |
198 | }; |
199 | return Ok(data); | |
200 | } else { | |
201 | bail!("unable to decrypt blob - missing CryptConfig"); | |
202 | } | |
69ecd8d5 DM |
203 | } else if magic == &AUTH_COMPR_BLOB_MAGIC_1_0 || magic == &AUTHENTICATED_BLOB_MAGIC_1_0 { |
204 | let header_len = std::mem::size_of::<AuthenticatedDataBlobHeader>(); | |
205 | let head = unsafe { | |
206 | (&self.raw_data[..header_len]).read_le_value::<AuthenticatedDataBlobHeader>()? | |
207 | }; | |
208 | ||
209 | let data_start = std::mem::size_of::<AuthenticatedDataBlobHeader>(); | |
210 | ||
211 | // Note: only verify if we have a crypt config | |
212 | if let Some(config) = config { | |
213 | let signature = config.compute_auth_tag(&self.raw_data[data_start..]); | |
214 | if signature != head.tag { | |
215 | bail!("verifying blob signature failed"); | |
216 | } | |
217 | } | |
218 | ||
219 | if magic == &AUTH_COMPR_BLOB_MAGIC_1_0 { | |
220 | let data = zstd::block::decompress(&self.raw_data[data_start..], 16*1024*1024)?; | |
221 | return Ok(data); | |
222 | } else { | |
223 | return Ok(self.raw_data[data_start..].to_vec()); | |
224 | } | |
3025b3a5 DM |
225 | } else { |
226 | bail!("Invalid blob magic number."); | |
227 | } | |
228 | } | |
a38c5d4d | 229 | |
69ecd8d5 DM |
230 | /// Create a signed DataBlob, optionally compressed |
231 | pub fn create_signed( | |
232 | data: &[u8], | |
233 | config: &CryptConfig, | |
234 | compress: bool, | |
235 | ) -> Result<Self, Error> { | |
236 | ||
781ac11c | 237 | if data.len() > MAX_BLOB_SIZE { |
69ecd8d5 DM |
238 | bail!("data blob too large ({} bytes).", data.len()); |
239 | } | |
240 | ||
241 | let compr_data; | |
242 | let (_compress, data, magic) = if compress { | |
243 | compr_data = zstd::block::compress(data, 1)?; | |
244 | // Note: We only use compression if result is shorter | |
245 | if compr_data.len() < data.len() { | |
246 | (true, &compr_data[..], AUTH_COMPR_BLOB_MAGIC_1_0) | |
247 | } else { | |
248 | (false, data, AUTHENTICATED_BLOB_MAGIC_1_0) | |
249 | } | |
250 | } else { | |
251 | (false, data, AUTHENTICATED_BLOB_MAGIC_1_0) | |
252 | }; | |
253 | ||
254 | let header_len = std::mem::size_of::<AuthenticatedDataBlobHeader>(); | |
255 | let mut raw_data = Vec::with_capacity(data.len() + header_len); | |
256 | ||
257 | let head = AuthenticatedDataBlobHeader { | |
258 | head: DataBlobHeader { magic, crc: [0; 4] }, | |
259 | tag: config.compute_auth_tag(data), | |
260 | }; | |
261 | unsafe { | |
262 | raw_data.write_le_value(head)?; | |
263 | } | |
264 | raw_data.extend_from_slice(data); | |
265 | ||
f889b158 DM |
266 | let mut blob = DataBlob { raw_data }; |
267 | blob.set_crc(blob.compute_crc()); | |
268 | ||
269 | return Ok(blob); | |
69ecd8d5 DM |
270 | } |
271 | ||
a38c5d4d DM |
272 | /// Create Instance from raw data |
273 | pub fn from_raw(data: Vec<u8>) -> Result<Self, Error> { | |
274 | ||
275 | if data.len() < std::mem::size_of::<DataBlobHeader>() { | |
276 | bail!("blob too small ({} bytes).", data.len()); | |
277 | } | |
278 | ||
279 | let magic = &data[0..8]; | |
280 | ||
281 | if magic == ENCR_COMPR_BLOB_MAGIC_1_0 || magic == ENCRYPTED_BLOB_MAGIC_1_0 { | |
282 | ||
283 | if data.len() < std::mem::size_of::<EncryptedDataBlobHeader>() { | |
284 | bail!("encrypted blob too small ({} bytes).", data.len()); | |
285 | } | |
286 | ||
287 | let blob = DataBlob { raw_data: data }; | |
288 | ||
289 | Ok(blob) | |
290 | } else if magic == COMPRESSED_BLOB_MAGIC_1_0 || magic == UNCOMPRESSED_BLOB_MAGIC_1_0 { | |
291 | ||
292 | let blob = DataBlob { raw_data: data }; | |
293 | ||
69ecd8d5 DM |
294 | Ok(blob) |
295 | } else if magic == AUTH_COMPR_BLOB_MAGIC_1_0 || magic == AUTHENTICATED_BLOB_MAGIC_1_0 { | |
296 | if data.len() < std::mem::size_of::<AuthenticatedDataBlobHeader>() { | |
297 | bail!("authenticated blob too small ({} bytes).", data.len()); | |
298 | } | |
299 | ||
300 | let blob = DataBlob { raw_data: data }; | |
301 | ||
a38c5d4d DM |
302 | Ok(blob) |
303 | } else { | |
304 | bail!("unable to parse raw blob - wrong magic"); | |
305 | } | |
306 | } | |
307 | ||
3025b3a5 | 308 | } |
1f26fdef | 309 | |
09785b27 | 310 | use std::io::{Read, BufRead, BufReader, Write, Seek, SeekFrom}; |
1f26fdef | 311 | |
2aa0bfff DM |
312 | struct CryptReader<R> { |
313 | reader: R, | |
60822163 | 314 | small_read_buf: Vec<u8>, |
2aa0bfff DM |
315 | block_size: usize, |
316 | crypter: openssl::symm::Crypter, | |
317 | finalized: bool, | |
318 | } | |
319 | ||
320 | impl <R: BufRead> CryptReader<R> { | |
321 | ||
322 | fn new(reader: R, iv: [u8; 16], tag: [u8; 16], config: &CryptConfig) -> Result<Self, Error> { | |
60822163 DM |
323 | let block_size = config.cipher().block_size(); // Note: block size is normally 1 byte for stream ciphers |
324 | if block_size.count_ones() != 1 || block_size > 512 { | |
2aa0bfff DM |
325 | bail!("unexpected Cipher block size {}", block_size); |
326 | } | |
327 | let mut crypter = config.data_crypter(&iv, openssl::symm::Mode::Decrypt)?; | |
328 | crypter.set_tag(&tag)?; | |
60822163 DM |
329 | |
330 | Ok(Self { reader, crypter, block_size, finalized: false, small_read_buf: Vec::new() }) | |
2aa0bfff DM |
331 | } |
332 | ||
333 | fn finish(self) -> Result<R, Error> { | |
334 | if !self.finalized { | |
335 | bail!("CryptReader not successfully finalized."); | |
336 | } | |
337 | Ok(self.reader) | |
338 | } | |
339 | } | |
340 | ||
341 | impl <R: BufRead> Read for CryptReader<R> { | |
342 | ||
343 | fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> { | |
60822163 DM |
344 | if self.small_read_buf.len() > 0 { |
345 | let max = if self.small_read_buf.len() > buf.len() { buf.len() } else { self.small_read_buf.len() }; | |
346 | let rest = self.small_read_buf.split_off(max); | |
347 | buf[..max].copy_from_slice(&self.small_read_buf); | |
348 | self.small_read_buf = rest; | |
349 | return Ok(max); | |
2aa0bfff DM |
350 | } |
351 | ||
352 | let data = self.reader.fill_buf()?; | |
353 | ||
60822163 DM |
354 | // handle small read buffers |
355 | if buf.len() <= 2*self.block_size { | |
356 | let mut outbuf = [0u8; 1024]; | |
357 | ||
358 | let count = if data.len() == 0 { // EOF | |
359 | let written = self.crypter.finalize(&mut outbuf)?; | |
360 | self.finalized = true; | |
361 | written | |
362 | } else { | |
363 | let mut read_size = outbuf.len() - self.block_size; | |
364 | if read_size > data.len() { | |
365 | read_size = data.len(); | |
366 | } | |
367 | let written = self.crypter.update(&data[..read_size], &mut outbuf)?; | |
368 | self.reader.consume(read_size); | |
369 | written | |
370 | }; | |
371 | ||
372 | if count > buf.len() { | |
373 | buf.copy_from_slice(&outbuf[..buf.len()]); | |
374 | self.small_read_buf = outbuf[buf.len()..count].to_vec(); | |
375 | return Ok(buf.len()); | |
376 | } else { | |
377 | buf[..count].copy_from_slice(&outbuf[..count]); | |
378 | return Ok(count); | |
379 | } | |
2aa0bfff | 380 | } else { |
60822163 DM |
381 | if data.len() == 0 { // EOF |
382 | let rest = self.crypter.finalize(buf)?; | |
383 | self.finalized = true; | |
384 | return Ok(rest) | |
385 | } else { | |
386 | let mut read_size = buf.len() - self.block_size; | |
387 | if read_size > data.len() { | |
388 | read_size = data.len(); | |
389 | } | |
390 | let count = self.crypter.update(&data[..read_size], buf)?; | |
391 | self.reader.consume(read_size); | |
392 | return Ok(count) | |
2aa0bfff | 393 | } |
2aa0bfff DM |
394 | } |
395 | } | |
396 | } | |
397 | ||
f4942e9f DM |
398 | struct CryptWriter<W> { |
399 | writer: W, | |
60822163 | 400 | block_size: usize, |
f4942e9f DM |
401 | encr_buf: [u8; 64*1024], |
402 | iv: [u8; 16], | |
403 | crypter: openssl::symm::Crypter, | |
404 | } | |
405 | ||
406 | impl <W: Write> CryptWriter<W> { | |
407 | ||
408 | fn new(writer: W, config: &CryptConfig) -> Result<Self, Error> { | |
409 | let mut iv = [0u8; 16]; | |
410 | proxmox::sys::linux::fill_with_random_data(&mut iv)?; | |
60822163 | 411 | let block_size = config.cipher().block_size(); |
f4942e9f | 412 | |
a32bd8a5 | 413 | let crypter = config.data_crypter(&iv, openssl::symm::Mode::Encrypt)?; |
f4942e9f | 414 | |
60822163 | 415 | Ok(Self { writer, iv, crypter, block_size, encr_buf: [0u8; 64*1024] }) |
f4942e9f DM |
416 | } |
417 | ||
418 | fn finish(mut self) -> Result<(W, [u8; 16], [u8; 16]), Error> { | |
419 | let rest = self.crypter.finalize(&mut self.encr_buf)?; | |
420 | if rest > 0 { | |
421 | self.writer.write_all(&self.encr_buf[..rest])?; | |
422 | } | |
423 | ||
424 | self.writer.flush()?; | |
425 | ||
426 | let mut tag = [0u8; 16]; | |
427 | self.crypter.get_tag(&mut tag)?; | |
428 | ||
429 | Ok((self.writer, self.iv, tag)) | |
430 | } | |
431 | } | |
432 | ||
433 | impl <W: Write> Write for CryptWriter<W> { | |
434 | ||
435 | fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> { | |
60822163 DM |
436 | let mut write_size = buf.len(); |
437 | if write_size > (self.encr_buf.len() - self.block_size) { | |
438 | write_size = self.encr_buf.len() - self.block_size; | |
439 | } | |
440 | let count = self.crypter.update(&buf[..write_size], &mut self.encr_buf) | |
f4942e9f DM |
441 | .map_err(|err| { |
442 | std::io::Error::new( | |
443 | std::io::ErrorKind::Other, | |
444 | format!("crypter update failed - {}", err)) | |
445 | })?; | |
446 | ||
447 | self.writer.write_all(&self.encr_buf[..count])?; | |
448 | ||
60822163 | 449 | Ok(write_size) |
f4942e9f DM |
450 | } |
451 | ||
452 | fn flush(&mut self) -> Result<(), std::io::Error> { | |
453 | Ok(()) | |
454 | } | |
455 | } | |
456 | ||
706638f8 DM |
457 | struct ChecksumWriter<'a, W> { |
458 | writer: W, | |
459 | hasher: crc32fast::Hasher, | |
460 | signer: Option<openssl::sign::Signer<'a>>, | |
461 | } | |
462 | ||
463 | impl <'a, W: Write> ChecksumWriter<'a, W> { | |
464 | ||
465 | fn new(writer: W, signer: Option<openssl::sign::Signer<'a>>) -> Self { | |
466 | let hasher = crc32fast::Hasher::new(); | |
467 | Self { writer, hasher, signer } | |
468 | } | |
469 | ||
470 | pub fn finish(mut self) -> Result<(W, u32, Option<[u8; 32]>), Error> { | |
471 | let crc = self.hasher.finalize(); | |
472 | ||
473 | if let Some(ref mut signer) = self.signer { | |
474 | let mut tag = [0u8; 32]; | |
475 | signer.sign(&mut tag)?; | |
476 | Ok((self.writer, crc, Some(tag))) | |
477 | } else { | |
478 | Ok((self.writer, crc, None)) | |
479 | } | |
480 | } | |
481 | } | |
482 | ||
483 | impl <'a, W: Write> Write for ChecksumWriter<'a, W> { | |
484 | ||
485 | fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> { | |
486 | self.hasher.update(buf); | |
487 | if let Some(ref mut signer) = self.signer { | |
f4942e9f DM |
488 | signer.update(buf) |
489 | .map_err(|err| { | |
706638f8 DM |
490 | std::io::Error::new( |
491 | std::io::ErrorKind::Other, | |
492 | format!("hmac update failed - {}", err)) | |
493 | })?; | |
494 | } | |
495 | self.writer.write(buf) | |
496 | } | |
497 | ||
498 | fn flush(&mut self) -> Result<(), std::io::Error> { | |
499 | self.writer.flush() | |
500 | } | |
501 | } | |
502 | ||
f796351c | 503 | enum BlobWriterState<'a, W: Write> { |
706638f8 DM |
504 | Uncompressed { csum_writer: ChecksumWriter<'a, W> }, |
505 | Compressed { compr: zstd::stream::write::Encoder<ChecksumWriter<'a, W>> }, | |
506 | Signed { csum_writer: ChecksumWriter<'a, W> }, | |
7776becf | 507 | SignedCompressed { compr: zstd::stream::write::Encoder<ChecksumWriter<'a, W>> }, |
f4942e9f | 508 | Encrypted { crypt_writer: CryptWriter<ChecksumWriter<'a, W>> }, |
5622a3fc | 509 | EncryptedCompressed { compr: zstd::stream::write::Encoder<CryptWriter<ChecksumWriter<'a, W>>> }, |
a762ce54 DM |
510 | } |
511 | ||
1f26fdef | 512 | /// Write compressed data blobs |
f796351c DM |
513 | pub struct DataBlobWriter<'a, W: Write> { |
514 | state: BlobWriterState<'a, W>, | |
1f26fdef DM |
515 | } |
516 | ||
f796351c | 517 | impl <'a, W: Write + Seek> DataBlobWriter<'a, W> { |
a762ce54 DM |
518 | |
519 | pub fn new_uncompressed(mut writer: W) -> Result<Self, Error> { | |
a762ce54 DM |
520 | writer.seek(SeekFrom::Start(0))?; |
521 | let head = DataBlobHeader { magic: UNCOMPRESSED_BLOB_MAGIC_1_0, crc: [0; 4] }; | |
522 | unsafe { | |
523 | writer.write_le_value(head)?; | |
524 | } | |
706638f8 DM |
525 | let csum_writer = ChecksumWriter::new(writer, None); |
526 | Ok(Self { state: BlobWriterState::Uncompressed { csum_writer }}) | |
a762ce54 | 527 | } |
1f26fdef | 528 | |
a762ce54 | 529 | pub fn new_compressed(mut writer: W) -> Result<Self, Error> { |
706638f8 | 530 | writer.seek(SeekFrom::Start(0))?; |
1f26fdef DM |
531 | let head = DataBlobHeader { magic: COMPRESSED_BLOB_MAGIC_1_0, crc: [0; 4] }; |
532 | unsafe { | |
a762ce54 | 533 | writer.write_le_value(head)?; |
1f26fdef | 534 | } |
706638f8 DM |
535 | let csum_writer = ChecksumWriter::new(writer, None); |
536 | let compr = zstd::stream::write::Encoder::new(csum_writer, 1)?; | |
537 | Ok(Self { state: BlobWriterState::Compressed { compr }}) | |
1f26fdef DM |
538 | } |
539 | ||
f796351c | 540 | pub fn new_signed(mut writer: W, config: &'a CryptConfig) -> Result<Self, Error> { |
f796351c DM |
541 | writer.seek(SeekFrom::Start(0))?; |
542 | let head = AuthenticatedDataBlobHeader { | |
543 | head: DataBlobHeader { magic: AUTHENTICATED_BLOB_MAGIC_1_0, crc: [0; 4] }, | |
544 | tag: [0u8; 32], | |
545 | }; | |
546 | unsafe { | |
547 | writer.write_le_value(head)?; | |
548 | } | |
549 | let signer = config.data_signer(); | |
706638f8 DM |
550 | let csum_writer = ChecksumWriter::new(writer, Some(signer)); |
551 | Ok(Self { state: BlobWriterState::Signed { csum_writer }}) | |
f796351c DM |
552 | } |
553 | ||
7776becf DM |
554 | pub fn new_signed_compressed(mut writer: W, config: &'a CryptConfig) -> Result<Self, Error> { |
555 | writer.seek(SeekFrom::Start(0))?; | |
556 | let head = AuthenticatedDataBlobHeader { | |
557 | head: DataBlobHeader { magic: AUTH_COMPR_BLOB_MAGIC_1_0, crc: [0; 4] }, | |
558 | tag: [0u8; 32], | |
559 | }; | |
560 | unsafe { | |
561 | writer.write_le_value(head)?; | |
562 | } | |
563 | let signer = config.data_signer(); | |
564 | let csum_writer = ChecksumWriter::new(writer, Some(signer)); | |
565 | let compr = zstd::stream::write::Encoder::new(csum_writer, 1)?; | |
566 | Ok(Self { state: BlobWriterState::SignedCompressed { compr }}) | |
567 | } | |
568 | ||
f4942e9f DM |
569 | pub fn new_encrypted(mut writer: W, config: &'a CryptConfig) -> Result<Self, Error> { |
570 | writer.seek(SeekFrom::Start(0))?; | |
571 | let head = EncryptedDataBlobHeader { | |
572 | head: DataBlobHeader { magic: ENCRYPTED_BLOB_MAGIC_1_0, crc: [0; 4] }, | |
573 | iv: [0u8; 16], | |
574 | tag: [0u8; 16], | |
575 | }; | |
576 | unsafe { | |
577 | writer.write_le_value(head)?; | |
578 | } | |
579 | ||
580 | let csum_writer = ChecksumWriter::new(writer, None); | |
581 | let crypt_writer = CryptWriter::new(csum_writer, config)?; | |
582 | Ok(Self { state: BlobWriterState::Encrypted { crypt_writer }}) | |
583 | } | |
584 | ||
5622a3fc DM |
585 | pub fn new_encrypted_compressed(mut writer: W, config: &'a CryptConfig) -> Result<Self, Error> { |
586 | writer.seek(SeekFrom::Start(0))?; | |
587 | let head = EncryptedDataBlobHeader { | |
588 | head: DataBlobHeader { magic: ENCR_COMPR_BLOB_MAGIC_1_0, crc: [0; 4] }, | |
589 | iv: [0u8; 16], | |
590 | tag: [0u8; 16], | |
591 | }; | |
592 | unsafe { | |
593 | writer.write_le_value(head)?; | |
594 | } | |
595 | ||
596 | let csum_writer = ChecksumWriter::new(writer, None); | |
597 | let crypt_writer = CryptWriter::new(csum_writer, config)?; | |
598 | let compr = zstd::stream::write::Encoder::new(crypt_writer, 1)?; | |
599 | Ok(Self { state: BlobWriterState::EncryptedCompressed { compr }}) | |
600 | } | |
601 | ||
a762ce54 DM |
602 | pub fn finish(self) -> Result<W, Error> { |
603 | match self.state { | |
706638f8 | 604 | BlobWriterState::Uncompressed { csum_writer } => { |
a762ce54 | 605 | // write CRC |
706638f8 | 606 | let (mut writer, crc, _) = csum_writer.finish()?; |
18be4ec2 | 607 | let head = DataBlobHeader { magic: UNCOMPRESSED_BLOB_MAGIC_1_0, crc: crc.to_le_bytes() }; |
1f26fdef | 608 | |
a762ce54 DM |
609 | writer.seek(SeekFrom::Start(0))?; |
610 | unsafe { | |
611 | writer.write_le_value(head)?; | |
612 | } | |
1f26fdef | 613 | |
a762ce54 DM |
614 | return Ok(writer) |
615 | } | |
706638f8 DM |
616 | BlobWriterState::Compressed { compr } => { |
617 | let csum_writer = compr.finish()?; | |
618 | let (mut writer, crc, _) = csum_writer.finish()?; | |
a762ce54 | 619 | |
a762ce54 | 620 | let head = DataBlobHeader { magic: COMPRESSED_BLOB_MAGIC_1_0, crc: crc.to_le_bytes() }; |
1f26fdef | 621 | |
a762ce54 DM |
622 | writer.seek(SeekFrom::Start(0))?; |
623 | unsafe { | |
624 | writer.write_le_value(head)?; | |
625 | } | |
626 | ||
f796351c DM |
627 | return Ok(writer) |
628 | } | |
706638f8 DM |
629 | BlobWriterState::Signed { csum_writer } => { |
630 | let (mut writer, crc, tag) = csum_writer.finish()?; | |
f796351c | 631 | |
706638f8 | 632 | let head = AuthenticatedDataBlobHeader { |
f796351c | 633 | head: DataBlobHeader { magic: AUTHENTICATED_BLOB_MAGIC_1_0, crc: crc.to_le_bytes() }, |
706638f8 | 634 | tag: tag.unwrap(), |
f796351c | 635 | }; |
f796351c DM |
636 | |
637 | writer.seek(SeekFrom::Start(0))?; | |
638 | unsafe { | |
639 | writer.write_le_value(head)?; | |
640 | } | |
641 | ||
7776becf DM |
642 | return Ok(writer) |
643 | } | |
644 | BlobWriterState::SignedCompressed { compr } => { | |
645 | let csum_writer = compr.finish()?; | |
646 | let (mut writer, crc, tag) = csum_writer.finish()?; | |
647 | ||
648 | let head = AuthenticatedDataBlobHeader { | |
649 | head: DataBlobHeader { magic: AUTH_COMPR_BLOB_MAGIC_1_0, crc: crc.to_le_bytes() }, | |
650 | tag: tag.unwrap(), | |
651 | }; | |
652 | ||
653 | writer.seek(SeekFrom::Start(0))?; | |
654 | unsafe { | |
655 | writer.write_le_value(head)?; | |
656 | } | |
657 | ||
a762ce54 DM |
658 | return Ok(writer) |
659 | } | |
f4942e9f DM |
660 | BlobWriterState::Encrypted { crypt_writer } => { |
661 | let (csum_writer, iv, tag) = crypt_writer.finish()?; | |
662 | let (mut writer, crc, _) = csum_writer.finish()?; | |
663 | ||
664 | let head = EncryptedDataBlobHeader { | |
665 | head: DataBlobHeader { magic: ENCRYPTED_BLOB_MAGIC_1_0, crc: crc.to_le_bytes() }, | |
666 | iv, tag, | |
667 | }; | |
a32bd8a5 | 668 | writer.seek(SeekFrom::Start(0))?; |
f4942e9f DM |
669 | unsafe { |
670 | writer.write_le_value(head)?; | |
671 | } | |
672 | return Ok(writer) | |
673 | } | |
5622a3fc DM |
674 | BlobWriterState::EncryptedCompressed { compr } => { |
675 | let crypt_writer = compr.finish()?; | |
676 | let (csum_writer, iv, tag) = crypt_writer.finish()?; | |
677 | let (mut writer, crc, _) = csum_writer.finish()?; | |
678 | ||
679 | let head = EncryptedDataBlobHeader { | |
680 | head: DataBlobHeader { magic: ENCR_COMPR_BLOB_MAGIC_1_0, crc: crc.to_le_bytes() }, | |
681 | iv, tag, | |
682 | }; | |
a32bd8a5 | 683 | writer.seek(SeekFrom::Start(0))?; |
5622a3fc DM |
684 | unsafe { |
685 | writer.write_le_value(head)?; | |
686 | } | |
687 | return Ok(writer) | |
688 | } | |
a762ce54 | 689 | } |
1f26fdef DM |
690 | } |
691 | } | |
692 | ||
f796351c | 693 | impl <'a, W: Write + Seek> Write for DataBlobWriter<'a, W> { |
1f26fdef DM |
694 | |
695 | fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> { | |
a762ce54 | 696 | match self.state { |
706638f8 DM |
697 | BlobWriterState::Uncompressed { ref mut csum_writer } => { |
698 | csum_writer.write(buf) | |
a762ce54 | 699 | } |
706638f8 | 700 | BlobWriterState::Compressed { ref mut compr } => { |
a762ce54 DM |
701 | compr.write(buf) |
702 | } | |
706638f8 DM |
703 | BlobWriterState::Signed { ref mut csum_writer } => { |
704 | csum_writer.write(buf) | |
705 | } | |
7776becf DM |
706 | BlobWriterState::SignedCompressed { ref mut compr } => { |
707 | compr.write(buf) | |
708 | } | |
f4942e9f DM |
709 | BlobWriterState::Encrypted { ref mut crypt_writer } => { |
710 | crypt_writer.write(buf) | |
711 | } | |
5622a3fc DM |
712 | BlobWriterState::EncryptedCompressed { ref mut compr } => { |
713 | compr.write(buf) | |
714 | } | |
a762ce54 | 715 | } |
1f26fdef DM |
716 | } |
717 | ||
718 | fn flush(&mut self) -> Result<(), std::io::Error> { | |
a762ce54 | 719 | match self.state { |
706638f8 DM |
720 | BlobWriterState::Uncompressed { ref mut csum_writer } => { |
721 | csum_writer.flush() | |
a762ce54 | 722 | } |
706638f8 | 723 | BlobWriterState::Compressed { ref mut compr } => { |
a762ce54 DM |
724 | compr.flush() |
725 | } | |
706638f8 DM |
726 | BlobWriterState::Signed { ref mut csum_writer } => { |
727 | csum_writer.flush() | |
f796351c | 728 | } |
7776becf DM |
729 | BlobWriterState::SignedCompressed { ref mut compr } => { |
730 | compr.flush() | |
731 | } | |
f4942e9f DM |
732 | BlobWriterState::Encrypted { ref mut crypt_writer } => { |
733 | crypt_writer.flush() | |
734 | } | |
5622a3fc DM |
735 | BlobWriterState::EncryptedCompressed { ref mut compr } => { |
736 | compr.flush() | |
737 | } | |
a762ce54 | 738 | } |
1f26fdef DM |
739 | } |
740 | } | |
741 | ||
09785b27 DM |
742 | struct ChecksumReader<'a, R> { |
743 | reader: R, | |
744 | hasher: crc32fast::Hasher, | |
745 | signer: Option<openssl::sign::Signer<'a>>, | |
746 | } | |
747 | ||
748 | impl <'a, R: Read> ChecksumReader<'a, R> { | |
749 | ||
750 | fn new(reader: R, signer: Option<openssl::sign::Signer<'a>>) -> Self { | |
751 | let hasher = crc32fast::Hasher::new(); | |
752 | Self { reader, hasher, signer } | |
753 | } | |
754 | ||
755 | pub fn finish(mut self) -> Result<(R, u32, Option<[u8; 32]>), Error> { | |
756 | let crc = self.hasher.finalize(); | |
757 | ||
758 | if let Some(ref mut signer) = self.signer { | |
759 | let mut tag = [0u8; 32]; | |
760 | signer.sign(&mut tag)?; | |
761 | Ok((self.reader, crc, Some(tag))) | |
762 | } else { | |
763 | Ok((self.reader, crc, None)) | |
764 | } | |
765 | } | |
766 | } | |
767 | ||
768 | impl <'a, R: Read> Read for ChecksumReader<'a, R> { | |
769 | ||
770 | fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> { | |
771 | let count = self.reader.read(buf)?; | |
772 | if count > 0 { | |
773 | self.hasher.update(&buf[..count]); | |
774 | if let Some(ref mut signer) = self.signer { | |
775 | signer.update(&buf[..count]) | |
776 | .map_err(|err| { | |
777 | std::io::Error::new( | |
778 | std::io::ErrorKind::Other, | |
779 | format!("hmac update failed - {}", err)) | |
780 | })?; | |
781 | } | |
782 | } | |
783 | Ok(count) | |
784 | } | |
785 | } | |
786 | ||
787 | enum BlobReaderState<'a, R: Read> { | |
788 | Uncompressed { expected_crc: u32, csum_reader: ChecksumReader<'a, R> }, | |
789 | Compressed { expected_crc: u32, decompr: zstd::stream::read::Decoder<BufReader<ChecksumReader<'a, R>>> }, | |
4bfa147e | 790 | Signed { expected_crc: u32, expected_hmac: [u8; 32], csum_reader: ChecksumReader<'a, R> }, |
e9a385a7 | 791 | SignedCompressed { expected_crc: u32, expected_hmac: [u8; 32], decompr: zstd::stream::read::Decoder<BufReader<ChecksumReader<'a, R>>> }, |
2aa0bfff | 792 | Encrypted { expected_crc: u32, decrypt_reader: CryptReader<BufReader<ChecksumReader<'a, R>>> }, |
548c9489 | 793 | EncryptedCompressed { expected_crc: u32, decompr: zstd::stream::read::Decoder<BufReader<CryptReader<BufReader<ChecksumReader<'a, R>>>>> }, |
09785b27 DM |
794 | } |
795 | ||
796 | /// Read data blobs | |
797 | pub struct DataBlobReader<'a, R: Read> { | |
798 | state: BlobReaderState<'a, R>, | |
1f26fdef DM |
799 | } |
800 | ||
09785b27 | 801 | impl <'a, R: Read> DataBlobReader<'a, R> { |
1f26fdef | 802 | |
4bfa147e | 803 | pub fn new(mut reader: R, config: Option<&'a CryptConfig>) -> Result<Self, Error> { |
1f26fdef DM |
804 | |
805 | let head: DataBlobHeader = unsafe { reader.read_le_value()? }; | |
09785b27 DM |
806 | match head.magic { |
807 | UNCOMPRESSED_BLOB_MAGIC_1_0 => { | |
808 | let expected_crc = u32::from_le_bytes(head.crc); | |
809 | let csum_reader = ChecksumReader::new(reader, None); | |
810 | Ok(Self { state: BlobReaderState::Uncompressed { expected_crc, csum_reader }}) | |
811 | } | |
812 | COMPRESSED_BLOB_MAGIC_1_0 => { | |
813 | let expected_crc = u32::from_le_bytes(head.crc); | |
814 | let csum_reader = ChecksumReader::new(reader, None); | |
815 | ||
816 | let decompr = zstd::stream::read::Decoder::new(csum_reader)?; | |
817 | Ok(Self { state: BlobReaderState::Compressed { expected_crc, decompr }}) | |
818 | } | |
4bfa147e DM |
819 | AUTHENTICATED_BLOB_MAGIC_1_0 => { |
820 | let expected_crc = u32::from_le_bytes(head.crc); | |
821 | let mut expected_hmac = [0u8; 32]; | |
822 | reader.read_exact(&mut expected_hmac)?; | |
823 | let signer = config.map(|c| c.data_signer()); | |
824 | let csum_reader = ChecksumReader::new(reader, signer); | |
825 | Ok(Self { state: BlobReaderState::Signed { expected_crc, expected_hmac, csum_reader }}) | |
826 | } | |
e9a385a7 DM |
827 | AUTH_COMPR_BLOB_MAGIC_1_0 => { |
828 | let expected_crc = u32::from_le_bytes(head.crc); | |
829 | let mut expected_hmac = [0u8; 32]; | |
830 | reader.read_exact(&mut expected_hmac)?; | |
831 | let signer = config.map(|c| c.data_signer()); | |
832 | let csum_reader = ChecksumReader::new(reader, signer); | |
833 | ||
834 | let decompr = zstd::stream::read::Decoder::new(csum_reader)?; | |
835 | Ok(Self { state: BlobReaderState::SignedCompressed { expected_crc, expected_hmac, decompr }}) | |
836 | } | |
2aa0bfff DM |
837 | ENCRYPTED_BLOB_MAGIC_1_0 => { |
838 | let expected_crc = u32::from_le_bytes(head.crc); | |
839 | let mut iv = [0u8; 16]; | |
840 | let mut expected_tag = [0u8; 16]; | |
841 | reader.read_exact(&mut iv)?; | |
842 | reader.read_exact(&mut expected_tag)?; | |
843 | let csum_reader = ChecksumReader::new(reader, None); | |
60822163 | 844 | let decrypt_reader = CryptReader::new(BufReader::with_capacity(64*1024, csum_reader), iv, expected_tag, config.unwrap())?; |
2aa0bfff DM |
845 | Ok(Self { state: BlobReaderState::Encrypted { expected_crc, decrypt_reader }}) |
846 | } | |
548c9489 DM |
847 | ENCR_COMPR_BLOB_MAGIC_1_0 => { |
848 | let expected_crc = u32::from_le_bytes(head.crc); | |
849 | let mut iv = [0u8; 16]; | |
850 | let mut expected_tag = [0u8; 16]; | |
851 | reader.read_exact(&mut iv)?; | |
852 | reader.read_exact(&mut expected_tag)?; | |
853 | let csum_reader = ChecksumReader::new(reader, None); | |
60822163 | 854 | let decrypt_reader = CryptReader::new(BufReader::with_capacity(64*1024, csum_reader), iv, expected_tag, config.unwrap())?; |
548c9489 DM |
855 | let decompr = zstd::stream::read::Decoder::new(decrypt_reader)?; |
856 | Ok(Self { state: BlobReaderState::EncryptedCompressed { expected_crc, decompr }}) | |
857 | } | |
09785b27 | 858 | _ => bail!("got wrong magic number {:?}", head.magic) |
1f26fdef | 859 | } |
09785b27 DM |
860 | } |
861 | ||
862 | pub fn finish(self) -> Result<R, Error> { | |
4bfa147e DM |
863 | match self.state { |
864 | BlobReaderState::Uncompressed { csum_reader, expected_crc } => { | |
865 | let (reader, crc, _) = csum_reader.finish()?; | |
866 | if crc != expected_crc { | |
867 | bail!("blob crc check failed"); | |
868 | } | |
869 | Ok(reader) | |
870 | } | |
871 | BlobReaderState::Compressed { expected_crc, decompr } => { | |
872 | let csum_reader = decompr.finish().into_inner(); | |
873 | let (reader, crc, _) = csum_reader.finish()?; | |
874 | if crc != expected_crc { | |
875 | bail!("blob crc check failed"); | |
876 | } | |
877 | Ok(reader) | |
878 | } | |
879 | BlobReaderState::Signed { csum_reader, expected_crc, expected_hmac } => { | |
880 | let (reader, crc, hmac) = csum_reader.finish()?; | |
881 | if crc != expected_crc { | |
882 | bail!("blob crc check failed"); | |
883 | } | |
884 | if let Some(hmac) = hmac { | |
885 | if hmac != expected_hmac { | |
886 | bail!("blob signature check failed"); | |
887 | } | |
888 | } | |
889 | Ok(reader) | |
890 | } | |
e9a385a7 DM |
891 | BlobReaderState::SignedCompressed { expected_crc, expected_hmac, decompr } => { |
892 | let csum_reader = decompr.finish().into_inner(); | |
893 | let (reader, crc, hmac) = csum_reader.finish()?; | |
894 | if crc != expected_crc { | |
895 | bail!("blob crc check failed"); | |
896 | } | |
897 | if let Some(hmac) = hmac { | |
898 | if hmac != expected_hmac { | |
899 | bail!("blob signature check failed"); | |
900 | } | |
901 | } | |
902 | Ok(reader) | |
903 | } | |
2aa0bfff DM |
904 | BlobReaderState::Encrypted { expected_crc, decrypt_reader } => { |
905 | let csum_reader = decrypt_reader.finish()?.into_inner(); | |
906 | let (reader, crc, _) = csum_reader.finish()?; | |
907 | if crc != expected_crc { | |
908 | bail!("blob crc check failed"); | |
909 | } | |
910 | Ok(reader) | |
911 | } | |
548c9489 DM |
912 | BlobReaderState::EncryptedCompressed { expected_crc, decompr } => { |
913 | let decrypt_reader = decompr.finish().into_inner(); | |
914 | let csum_reader = decrypt_reader.finish()?.into_inner(); | |
915 | let (reader, crc, _) = csum_reader.finish()?; | |
916 | if crc != expected_crc { | |
917 | bail!("blob crc check failed"); | |
918 | } | |
919 | Ok(reader) | |
920 | } | |
4bfa147e | 921 | } |
1f26fdef DM |
922 | } |
923 | } | |
924 | ||
09785b27 | 925 | impl <'a, R: BufRead> Read for DataBlobReader<'a, R> { |
1f26fdef DM |
926 | |
927 | fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> { | |
09785b27 DM |
928 | match &mut self.state { |
929 | BlobReaderState::Uncompressed { csum_reader, .. } => { | |
930 | csum_reader.read(buf) | |
931 | } | |
932 | BlobReaderState::Compressed { decompr, .. } => { | |
933 | decompr.read(buf) | |
1f26fdef | 934 | } |
4bfa147e DM |
935 | BlobReaderState::Signed { csum_reader, .. } => { |
936 | csum_reader.read(buf) | |
937 | } | |
e9a385a7 DM |
938 | BlobReaderState::SignedCompressed { decompr, .. } => { |
939 | decompr.read(buf) | |
940 | } | |
2aa0bfff DM |
941 | BlobReaderState::Encrypted { decrypt_reader, .. } => { |
942 | decrypt_reader.read(buf) | |
943 | } | |
548c9489 DM |
944 | BlobReaderState::EncryptedCompressed { decompr, .. } => { |
945 | decompr.read(buf) | |
946 | } | |
1f26fdef | 947 | } |
1f26fdef DM |
948 | } |
949 | } |