1 use std
::convert
::TryInto
;
4 use anyhow
::{bail, Error}
;
5 use openssl
::symm
::{decrypt_aead, Mode}
;
7 use proxmox_io
::{ReadExt, WriteExt}
;
9 use pbs_tools
::crypt_config
::CryptConfig
;
10 use pbs_api_types
::CryptMode
;
12 use super::file_formats
::*;
14 const MAX_BLOB_SIZE
: usize = 128*1024*1024;
16 /// Encoded data chunk with digest and positional information
17 pub struct ChunkInfo
{
24 /// Data blob binary storage format
26 /// Data blobs store arbitrary binary data (< 128MB), and can be
27 /// compressed and encrypted (or just signed). A simply binary format
28 /// is used to store them on disk or transfer them over the network.
30 /// Please use index files to store large data files (".fidx" of
34 raw_data
: Vec
<u8>, // tagged, compressed, encryped data
39 /// accessor to raw_data field
40 pub fn raw_data(&self) -> &[u8] {
44 /// Returns raw_data size
45 pub fn raw_size(&self) -> u64 {
46 self.raw_data
.len() as u64
49 /// Consume self and returns raw_data
50 pub fn into_inner(self) -> Vec
<u8> {
54 /// accessor to chunk type (magic number)
55 pub fn magic(&self) -> &[u8; 8] {
56 self.raw_data
[0..8].try_into().unwrap()
59 /// accessor to crc32 checksum
60 pub fn crc(&self) -> u32 {
61 let crc_o
= proxmox_lang
::offsetof!(DataBlobHeader
, crc
);
62 u32::from_le_bytes(self.raw_data
[crc_o
..crc_o
+4].try_into().unwrap())
65 // set the CRC checksum field
66 pub fn set_crc(&mut self, crc
: u32) {
67 let crc_o
= proxmox_lang
::offsetof!(DataBlobHeader
, crc
);
68 self.raw_data
[crc_o
..crc_o
+4].copy_from_slice(&crc
.to_le_bytes());
71 /// compute the CRC32 checksum
72 pub fn compute_crc(&self) -> u32 {
73 let mut hasher
= crc32fast
::Hasher
::new();
74 let start
= header_size(self.magic()); // start after HEAD
75 hasher
.update(&self.raw_data
[start
..]);
79 // verify the CRC32 checksum
80 pub fn verify_crc(&self) -> Result
<(), Error
> {
81 let expected_crc
= self.compute_crc();
82 if expected_crc
!= self.crc() {
83 bail
!("Data blob has wrong CRC checksum.");
88 /// Create a DataBlob, optionally compressed and/or encrypted
91 config
: Option
<&CryptConfig
>,
93 ) -> Result
<Self, Error
> {
95 if data
.len() > MAX_BLOB_SIZE
{
96 bail
!("data blob too large ({} bytes).", data
.len());
99 let mut blob
= if let Some(config
) = config
{
102 let (_compress
, data
, magic
) = if compress
{
103 compr_data
= zstd
::block
::compress(data
, 1)?
;
104 // Note: We only use compression if result is shorter
105 if compr_data
.len() < data
.len() {
106 (true, &compr_data
[..], ENCR_COMPR_BLOB_MAGIC_1_0
)
108 (false, data
, ENCRYPTED_BLOB_MAGIC_1_0
)
111 (false, data
, ENCRYPTED_BLOB_MAGIC_1_0
)
114 let header_len
= std
::mem
::size_of
::<EncryptedDataBlobHeader
>();
115 let mut raw_data
= Vec
::with_capacity(data
.len() + header_len
);
117 let dummy_head
= EncryptedDataBlobHeader
{
118 head
: DataBlobHeader { magic: [0u8; 8], crc: [0; 4] }
,
123 raw_data
.write_le_value(dummy_head
)?
;
126 let (iv
, tag
) = Self::encrypt_to(&config
, data
, &mut raw_data
)?
;
128 let head
= EncryptedDataBlobHeader
{
129 head
: DataBlobHeader { magic, crc: [0; 4] }
, iv
, tag
,
133 (&mut raw_data
[0..header_len
]).write_le_value(head
)?
;
136 DataBlob { raw_data }
139 let max_data_len
= data
.len() + std
::mem
::size_of
::<DataBlobHeader
>();
141 let mut comp_data
= Vec
::with_capacity(max_data_len
);
143 let head
= DataBlobHeader
{
144 magic
: COMPRESSED_BLOB_MAGIC_1_0
,
148 comp_data
.write_le_value(head
)?
;
151 zstd
::stream
::copy_encode(data
, &mut comp_data
, 1)?
;
153 if comp_data
.len() < max_data_len
{
154 let mut blob
= DataBlob { raw_data: comp_data }
;
155 blob
.set_crc(blob
.compute_crc());
160 let mut raw_data
= Vec
::with_capacity(max_data_len
);
162 let head
= DataBlobHeader
{
163 magic
: UNCOMPRESSED_BLOB_MAGIC_1_0
,
167 raw_data
.write_le_value(head
)?
;
169 raw_data
.extend_from_slice(data
);
171 DataBlob { raw_data }
174 blob
.set_crc(blob
.compute_crc());
179 /// Get the encryption mode for this blob.
180 pub fn crypt_mode(&self) -> Result
<CryptMode
, Error
> {
181 let magic
= self.magic();
183 Ok(if magic
== &UNCOMPRESSED_BLOB_MAGIC_1_0
|| magic
== &COMPRESSED_BLOB_MAGIC_1_0
{
185 } else if magic
== &ENCR_COMPR_BLOB_MAGIC_1_0
|| magic
== &ENCRYPTED_BLOB_MAGIC_1_0
{
188 bail
!("Invalid blob magic number.");
193 pub fn decode(&self, config
: Option
<&CryptConfig
>, digest
: Option
<&[u8; 32]>) -> Result
<Vec
<u8>, Error
> {
195 let magic
= self.magic();
197 if magic
== &UNCOMPRESSED_BLOB_MAGIC_1_0
{
198 let data_start
= std
::mem
::size_of
::<DataBlobHeader
>();
199 let data
= self.raw_data
[data_start
..].to_vec();
200 if let Some(digest
) = digest
{
201 Self::verify_digest(&data
, None
, digest
)?
;
204 } else if magic
== &COMPRESSED_BLOB_MAGIC_1_0
{
205 let data_start
= std
::mem
::size_of
::<DataBlobHeader
>();
206 let mut reader
= &self.raw_data
[data_start
..];
207 let data
= zstd
::stream
::decode_all(&mut reader
)?
;
208 // zstd::block::decompress is abou 10% slower
209 // let data = zstd::block::decompress(&self.raw_data[data_start..], MAX_BLOB_SIZE)?;
210 if let Some(digest
) = digest
{
211 Self::verify_digest(&data
, None
, digest
)?
;
214 } else if magic
== &ENCR_COMPR_BLOB_MAGIC_1_0
|| magic
== &ENCRYPTED_BLOB_MAGIC_1_0
{
215 let header_len
= std
::mem
::size_of
::<EncryptedDataBlobHeader
>();
217 (&self.raw_data
[..header_len
]).read_le_value
::<EncryptedDataBlobHeader
>()?
220 if let Some(config
) = config
{
221 let data
= if magic
== &ENCR_COMPR_BLOB_MAGIC_1_0
{
222 Self::decode_compressed_chunk(config
, &self.raw_data
[header_len
..], &head
.iv
, &head
.tag
)?
224 Self::decode_uncompressed_chunk(config
, &self.raw_data
[header_len
..], &head
.iv
, &head
.tag
)?
226 if let Some(digest
) = digest
{
227 Self::verify_digest(&data
, Some(config
), digest
)?
;
231 bail
!("unable to decrypt blob - missing CryptConfig");
234 bail
!("Invalid blob magic number.");
238 /// Load blob from ``reader``, verify CRC
239 pub fn load_from_reader(reader
: &mut dyn std
::io
::Read
) -> Result
<Self, Error
> {
241 let mut data
= Vec
::with_capacity(1024*1024);
242 reader
.read_to_end(&mut data
)?
;
244 let blob
= Self::from_raw(data
)?
;
251 /// Create Instance from raw data
252 pub fn from_raw(data
: Vec
<u8>) -> Result
<Self, Error
> {
254 if data
.len() < std
::mem
::size_of
::<DataBlobHeader
>() {
255 bail
!("blob too small ({} bytes).", data
.len());
258 let magic
= &data
[0..8];
260 if magic
== ENCR_COMPR_BLOB_MAGIC_1_0
|| magic
== ENCRYPTED_BLOB_MAGIC_1_0
{
262 if data
.len() < std
::mem
::size_of
::<EncryptedDataBlobHeader
>() {
263 bail
!("encrypted blob too small ({} bytes).", data
.len());
266 let blob
= DataBlob { raw_data: data }
;
269 } else if magic
== COMPRESSED_BLOB_MAGIC_1_0
|| magic
== UNCOMPRESSED_BLOB_MAGIC_1_0
{
271 let blob
= DataBlob { raw_data: data }
;
275 bail
!("unable to parse raw blob - wrong magic");
279 /// Returns if chunk is encrypted
280 pub fn is_encrypted(&self) -> bool
{
281 let magic
= self.magic();
282 magic
== &ENCR_COMPR_BLOB_MAGIC_1_0
|| magic
== &ENCRYPTED_BLOB_MAGIC_1_0
285 /// Verify digest and data length for unencrypted chunks.
287 /// To do that, we need to decompress data first. Please note that
288 /// this is not possible for encrypted chunks. This function simply return Ok
289 /// for encrypted chunks.
290 /// Note: This does not call verify_crc, because this is usually done in load
291 pub fn verify_unencrypted(
293 expected_chunk_size
: usize,
294 expected_digest
: &[u8; 32],
295 ) -> Result
<(), Error
> {
297 let magic
= self.magic();
299 if magic
== &ENCR_COMPR_BLOB_MAGIC_1_0
|| magic
== &ENCRYPTED_BLOB_MAGIC_1_0
{
304 let data
= self.decode(None
, Some(expected_digest
))?
;
306 if expected_chunk_size
!= data
.len() {
307 bail
!("detected chunk with wrong length ({} != {})", expected_chunk_size
, data
.len());
315 config
: Option
<&CryptConfig
>,
316 expected_digest
: &[u8; 32],
317 ) -> Result
<(), Error
> {
319 let digest
= match config
{
320 Some(config
) => config
.compute_digest(data
),
321 None
=> openssl
::sha
::sha256(data
),
323 if &digest
!= expected_digest
{
324 bail
!("detected chunk with wrong digest.");
330 /// Benchmark encryption speed
331 pub fn encrypt_benchmark
<W
: Write
>(
332 config
: &CryptConfig
,
335 ) -> Result
<(), Error
> {
336 let _
= Self::encrypt_to(config
, data
, output
)?
;
340 // Encrypt data using a random 16 byte IV.
342 // Writes encrypted data to ``output``, Return the used IV and computed MAC.
343 fn encrypt_to
<W
: Write
>(
344 config
: &CryptConfig
,
347 ) -> Result
<([u8;16], [u8;16]), Error
> {
349 let mut iv
= [0u8; 16];
350 proxmox
::sys
::linux
::fill_with_random_data(&mut iv
)?
;
352 let mut tag
= [0u8; 16];
354 let mut c
= config
.data_crypter(&iv
, Mode
::Encrypt
)?
;
356 const BUFFER_SIZE
: usize = 32*1024;
358 let mut encr_buf
= [0u8; BUFFER_SIZE
];
359 let max_encoder_input
= BUFFER_SIZE
- config
.cipher().block_size();
363 let mut end
= start
+ max_encoder_input
;
364 if end
> data
.len() { end = data.len(); }
366 let count
= c
.update(&data
[start
..end
], &mut encr_buf
)?
;
367 output
.write_all(&encr_buf
[..count
])?
;
374 let rest
= c
.finalize(&mut encr_buf
)?
;
375 if rest
> 0 { output.write_all(&encr_buf[..rest])?; }
379 c
.get_tag(&mut tag
)?
;
384 // Decompress and decrypt data, verify MAC.
385 fn decode_compressed_chunk(
386 config
: &CryptConfig
,
390 ) -> Result
<Vec
<u8>, Error
> {
392 let dec
= Vec
::with_capacity(1024*1024);
394 let mut decompressor
= zstd
::stream
::write
::Decoder
::new(dec
)?
;
396 let mut c
= config
.data_crypter(iv
, Mode
::Decrypt
)?
;
398 const BUFFER_SIZE
: usize = 32*1024;
400 let mut decr_buf
= [0u8; BUFFER_SIZE
];
401 let max_decoder_input
= BUFFER_SIZE
- config
.cipher().block_size();
405 let mut end
= start
+ max_decoder_input
;
406 if end
> data
.len() { end = data.len(); }
408 let count
= c
.update(&data
[start
..end
], &mut decr_buf
)?
;
409 decompressor
.write_all(&decr_buf
[0..count
])?
;
417 let rest
= c
.finalize(&mut decr_buf
)?
;
418 if rest
> 0 { decompressor.write_all(&decr_buf[..rest])?; }
420 decompressor
.flush()?
;
422 Ok(decompressor
.into_inner())
425 // Decrypt data, verify tag.
426 fn decode_uncompressed_chunk(
427 config
: &CryptConfig
,
431 ) -> Result
<Vec
<u8>, Error
> {
433 let decr_data
= decrypt_aead(
447 /// Builder for chunk DataBlobs
449 /// Main purpose is to centralize digest computation. Digest
450 /// computation differ for encryped chunk, and this interface ensures that
451 /// we always compute the correct one.
452 pub struct DataChunkBuilder
<'a
, 'b
> {
453 config
: Option
<&'b CryptConfig
>,
455 digest_computed
: bool
,
460 impl <'a
, 'b
> DataChunkBuilder
<'a
, 'b
> {
462 /// Create a new builder instance.
463 pub fn new(orig_data
: &'a
[u8]) -> Self {
467 digest_computed
: false,
473 /// Set compression flag.
475 /// If true, chunk data is compressed using zstd (level 1).
476 pub fn compress(mut self, value
: bool
) -> Self {
477 self.compress
= value
;
481 /// Set encryption Configuration
483 /// If set, chunks are encrypted
484 pub fn crypt_config(mut self, value
: &'b CryptConfig
) -> Self {
485 if self.digest_computed
{
486 panic
!("unable to set crypt_config after compute_digest().");
488 self.config
= Some(value
);
492 fn compute_digest(&mut self) {
493 if !self.digest_computed
{
494 if let Some(ref config
) = self.config
{
495 self.digest
= config
.compute_digest(self.orig_data
);
497 self.digest
= openssl
::sha
::sha256(self.orig_data
);
499 self.digest_computed
= true;
503 /// Returns the chunk Digest
505 /// Note: For encrypted chunks, this needs to be called after
506 /// ``crypt_config``.
507 pub fn digest(&mut self) -> &[u8; 32] {
508 if !self.digest_computed
{
509 self.compute_digest();
514 /// Consume self and build the ``DataBlob``.
516 /// Returns the blob and the computet digest.
517 pub fn build(mut self) -> Result
<(DataBlob
, [u8; 32]), Error
> {
518 if !self.digest_computed
{
519 self.compute_digest();
522 let chunk
= DataBlob
::encode(self.orig_data
, self.config
, self.compress
)?
;
523 Ok((chunk
, self.digest
))
526 /// Create a chunk filled with zeroes
527 pub fn build_zero_chunk(
528 crypt_config
: Option
<&CryptConfig
>,
531 ) -> Result
<(DataBlob
, [u8; 32]), Error
> {
532 let zero_bytes
= vec
![0; chunk_size
];
533 let mut chunk_builder
= DataChunkBuilder
::new(&zero_bytes
).compress(compress
);
534 if let Some(ref crypt_config
) = crypt_config
{
535 chunk_builder
= chunk_builder
.crypt_config(crypt_config
);
538 chunk_builder
.build()