3 use anyhow
::{bail, Error}
;
4 use openssl
::symm
::{decrypt_aead, Mode}
;
6 use proxmox_io
::{ReadExt, WriteExt}
;
8 use pbs_api_types
::CryptMode
;
9 use pbs_tools
::crypt_config
::CryptConfig
;
11 use super::file_formats
::*;
13 const MAX_BLOB_SIZE
: usize = 128 * 1024 * 1024;
15 /// Encoded data chunk with digest and positional information
16 pub struct ChunkInfo
{
23 /// Data blob binary storage format
25 /// Data blobs store arbitrary binary data (< 128MB), and can be
26 /// compressed and encrypted (or just signed). A simply binary format
27 /// is used to store them on disk or transfer them over the network.
29 /// Please use index files to store large data files (".fidx" of
33 raw_data
: Vec
<u8>, // tagged, compressed, encryped data
37 /// accessor to raw_data field
38 pub fn raw_data(&self) -> &[u8] {
42 /// Returns raw_data size
43 pub fn raw_size(&self) -> u64 {
44 self.raw_data
.len() as u64
47 /// Consume self and returns raw_data
48 pub fn into_inner(self) -> Vec
<u8> {
52 /// accessor to chunk type (magic number)
53 pub fn magic(&self) -> &[u8; 8] {
54 self.raw_data
[0..8].try_into().unwrap()
57 /// accessor to crc32 checksum
58 pub fn crc(&self) -> u32 {
59 let crc_o
= proxmox_lang
::offsetof!(DataBlobHeader
, crc
);
60 u32::from_le_bytes(self.raw_data
[crc_o
..crc_o
+ 4].try_into().unwrap())
63 // set the CRC checksum field
64 pub fn set_crc(&mut self, crc
: u32) {
65 let crc_o
= proxmox_lang
::offsetof!(DataBlobHeader
, crc
);
66 self.raw_data
[crc_o
..crc_o
+ 4].copy_from_slice(&crc
.to_le_bytes());
69 /// compute the CRC32 checksum
70 pub fn compute_crc(&self) -> u32 {
71 let mut hasher
= crc32fast
::Hasher
::new();
72 let start
= header_size(self.magic()); // start after HEAD
73 hasher
.update(&self.raw_data
[start
..]);
77 // verify the CRC32 checksum
78 pub fn verify_crc(&self) -> Result
<(), Error
> {
79 let expected_crc
= self.compute_crc();
80 if expected_crc
!= self.crc() {
81 bail
!("Data blob has wrong CRC checksum.");
86 /// Create a DataBlob, optionally compressed and/or encrypted
89 config
: Option
<&CryptConfig
>,
91 ) -> Result
<Self, Error
> {
92 if data
.len() > MAX_BLOB_SIZE
{
93 bail
!("data blob too large ({} bytes).", data
.len());
96 let mut blob
= if let Some(config
) = config
{
98 let (_compress
, data
, magic
) = if compress
{
99 compr_data
= zstd
::bulk
::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
)
104 (false, data
, ENCRYPTED_BLOB_MAGIC_1_0
)
107 (false, data
, ENCRYPTED_BLOB_MAGIC_1_0
)
110 let header_len
= std
::mem
::size_of
::<EncryptedDataBlobHeader
>();
111 let mut raw_data
= Vec
::with_capacity(data
.len() + header_len
);
113 let dummy_head
= EncryptedDataBlobHeader
{
114 head
: DataBlobHeader
{
122 raw_data
.write_le_value(dummy_head
)?
;
125 let (iv
, tag
) = Self::encrypt_to(config
, data
, &mut raw_data
)?
;
127 let head
= EncryptedDataBlobHeader
{
128 head
: DataBlobHeader { magic, crc: [0; 4] }
,
134 (&mut raw_data
[0..header_len
]).write_le_value(head
)?
;
137 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
{
157 blob
.set_crc(blob
.compute_crc());
162 let mut raw_data
= Vec
::with_capacity(max_data_len
);
164 let head
= DataBlobHeader
{
165 magic
: UNCOMPRESSED_BLOB_MAGIC_1_0
,
169 raw_data
.write_le_value(head
)?
;
171 raw_data
.extend_from_slice(data
);
173 DataBlob { raw_data }
176 blob
.set_crc(blob
.compute_crc());
181 /// Get the encryption mode for this blob.
182 pub fn crypt_mode(&self) -> Result
<CryptMode
, Error
> {
183 let magic
= self.magic();
186 if magic
== &UNCOMPRESSED_BLOB_MAGIC_1_0
|| magic
== &COMPRESSED_BLOB_MAGIC_1_0
{
188 } else if magic
== &ENCR_COMPR_BLOB_MAGIC_1_0
|| magic
== &ENCRYPTED_BLOB_MAGIC_1_0
{
191 bail
!("Invalid blob magic number.");
199 config
: Option
<&CryptConfig
>,
200 digest
: Option
<&[u8; 32]>,
201 ) -> Result
<Vec
<u8>, Error
> {
202 let magic
= self.magic();
204 if magic
== &UNCOMPRESSED_BLOB_MAGIC_1_0
{
205 let data_start
= std
::mem
::size_of
::<DataBlobHeader
>();
206 let data
= self.raw_data
[data_start
..].to_vec();
207 if let Some(digest
) = digest
{
208 Self::verify_digest(&data
, None
, digest
)?
;
211 } else if magic
== &COMPRESSED_BLOB_MAGIC_1_0
{
212 let data_start
= std
::mem
::size_of
::<DataBlobHeader
>();
213 let mut reader
= &self.raw_data
[data_start
..];
214 let data
= zstd
::stream
::decode_all(&mut reader
)?
;
215 // zstd::block::decompress is abou 10% slower
216 // let data = zstd::block::decompress(&self.raw_data[data_start..], MAX_BLOB_SIZE)?;
217 if let Some(digest
) = digest
{
218 Self::verify_digest(&data
, None
, digest
)?
;
221 } else if magic
== &ENCR_COMPR_BLOB_MAGIC_1_0
|| magic
== &ENCRYPTED_BLOB_MAGIC_1_0
{
222 let header_len
= std
::mem
::size_of
::<EncryptedDataBlobHeader
>();
224 (&self.raw_data
[..header_len
]).read_le_value
::<EncryptedDataBlobHeader
>()?
227 if let Some(config
) = config
{
228 let data
= if magic
== &ENCR_COMPR_BLOB_MAGIC_1_0
{
229 Self::decode_compressed_chunk(
231 &self.raw_data
[header_len
..],
236 Self::decode_uncompressed_chunk(
238 &self.raw_data
[header_len
..],
243 if let Some(digest
) = digest
{
244 Self::verify_digest(&data
, Some(config
), digest
)?
;
248 bail
!("unable to decrypt blob - missing CryptConfig");
251 bail
!("Invalid blob magic number.");
255 /// Load blob from ``reader``, verify CRC
256 pub fn load_from_reader(reader
: &mut dyn std
::io
::Read
) -> Result
<Self, Error
> {
257 let mut data
= Vec
::with_capacity(1024 * 1024);
258 reader
.read_to_end(&mut data
)?
;
260 let blob
= Self::from_raw(data
)?
;
267 /// Create Instance from raw data
268 pub fn from_raw(data
: Vec
<u8>) -> Result
<Self, Error
> {
269 if data
.len() < std
::mem
::size_of
::<DataBlobHeader
>() {
270 bail
!("blob too small ({} bytes).", data
.len());
273 let magic
= &data
[0..8];
275 if magic
== ENCR_COMPR_BLOB_MAGIC_1_0
|| magic
== ENCRYPTED_BLOB_MAGIC_1_0
{
276 if data
.len() < std
::mem
::size_of
::<EncryptedDataBlobHeader
>() {
277 bail
!("encrypted blob too small ({} bytes).", data
.len());
280 let blob
= DataBlob { raw_data: data }
;
283 } else if magic
== COMPRESSED_BLOB_MAGIC_1_0
|| magic
== UNCOMPRESSED_BLOB_MAGIC_1_0
{
284 let blob
= DataBlob { raw_data: data }
;
288 bail
!("unable to parse raw blob - wrong magic");
292 /// Returns if chunk is encrypted
293 pub fn is_encrypted(&self) -> bool
{
294 let magic
= self.magic();
295 magic
== &ENCR_COMPR_BLOB_MAGIC_1_0
|| magic
== &ENCRYPTED_BLOB_MAGIC_1_0
298 /// Returns if chunk is compressed
299 pub fn is_compressed(&self) -> bool
{
300 let magic
= self.magic();
301 magic
== &ENCR_COMPR_BLOB_MAGIC_1_0
|| magic
== &COMPRESSED_BLOB_MAGIC_1_0
304 /// Verify digest and data length for unencrypted chunks.
306 /// To do that, we need to decompress data first. Please note that
307 /// this is not possible for encrypted chunks. This function simply return Ok
308 /// for encrypted chunks.
309 /// Note: This does not call verify_crc, because this is usually done in load
310 pub fn verify_unencrypted(
312 expected_chunk_size
: usize,
313 expected_digest
: &[u8; 32],
314 ) -> Result
<(), Error
> {
315 let magic
= self.magic();
317 if magic
== &ENCR_COMPR_BLOB_MAGIC_1_0
|| magic
== &ENCRYPTED_BLOB_MAGIC_1_0
{
322 let data
= self.decode(None
, Some(expected_digest
))?
;
324 if expected_chunk_size
!= data
.len() {
326 "detected chunk with wrong length ({} != {})",
337 config
: Option
<&CryptConfig
>,
338 expected_digest
: &[u8; 32],
339 ) -> Result
<(), Error
> {
340 let digest
= match config
{
341 Some(config
) => config
.compute_digest(data
),
342 None
=> openssl
::sha
::sha256(data
),
344 if &digest
!= expected_digest
{
345 bail
!("detected chunk with wrong digest.");
351 /// Benchmark encryption speed
352 pub fn encrypt_benchmark
<W
: Write
>(
353 config
: &CryptConfig
,
356 ) -> Result
<(), Error
> {
357 let _
= Self::encrypt_to(config
, data
, output
)?
;
361 // Encrypt data using a random 16 byte IV.
363 // Writes encrypted data to ``output``, Return the used IV and computed MAC.
364 fn encrypt_to
<W
: Write
>(
365 config
: &CryptConfig
,
368 ) -> Result
<([u8; 16], [u8; 16]), Error
> {
369 let mut iv
= [0u8; 16];
370 proxmox_sys
::linux
::fill_with_random_data(&mut iv
)?
;
372 let mut tag
= [0u8; 16];
374 let mut c
= config
.data_crypter(&iv
, Mode
::Encrypt
)?
;
376 const BUFFER_SIZE
: usize = 32 * 1024;
378 let mut encr_buf
= [0u8; BUFFER_SIZE
];
379 let max_encoder_input
= BUFFER_SIZE
- config
.cipher().block_size();
383 let mut end
= start
+ max_encoder_input
;
384 if end
> data
.len() {
388 let count
= c
.update(&data
[start
..end
], &mut encr_buf
)?
;
389 output
.write_all(&encr_buf
[..count
])?
;
396 let rest
= c
.finalize(&mut encr_buf
)?
;
398 output
.write_all(&encr_buf
[..rest
])?
;
403 c
.get_tag(&mut tag
)?
;
408 // Decompress and decrypt data, verify MAC.
409 fn decode_compressed_chunk(
410 config
: &CryptConfig
,
414 ) -> Result
<Vec
<u8>, Error
> {
415 let dec
= Vec
::with_capacity(1024 * 1024);
417 let mut decompressor
= zstd
::stream
::write
::Decoder
::new(dec
)?
;
419 let mut c
= config
.data_crypter(iv
, Mode
::Decrypt
)?
;
421 const BUFFER_SIZE
: usize = 32 * 1024;
423 let mut decr_buf
= [0u8; BUFFER_SIZE
];
424 let max_decoder_input
= BUFFER_SIZE
- config
.cipher().block_size();
428 let mut end
= start
+ max_decoder_input
;
429 if end
> data
.len() {
433 let count
= c
.update(&data
[start
..end
], &mut decr_buf
)?
;
434 decompressor
.write_all(&decr_buf
[0..count
])?
;
442 let rest
= c
.finalize(&mut decr_buf
)?
;
444 decompressor
.write_all(&decr_buf
[..rest
])?
;
447 decompressor
.flush()?
;
449 Ok(decompressor
.into_inner())
452 // Decrypt data, verify tag.
453 fn decode_uncompressed_chunk(
454 config
: &CryptConfig
,
458 ) -> Result
<Vec
<u8>, Error
> {
459 let decr_data
= decrypt_aead(
472 /// Builder for chunk DataBlobs
474 /// Main purpose is to centralize digest computation. Digest
475 /// computation differ for encryped chunk, and this interface ensures that
476 /// we always compute the correct one.
477 pub struct DataChunkBuilder
<'a
, 'b
> {
478 config
: Option
<&'b CryptConfig
>,
480 digest_computed
: bool
,
485 impl<'a
, 'b
> DataChunkBuilder
<'a
, 'b
> {
486 /// Create a new builder instance.
487 pub fn new(orig_data
: &'a
[u8]) -> Self {
491 digest_computed
: false,
497 /// Set compression flag.
499 /// If true, chunk data is compressed using zstd (level 1).
500 pub fn compress(mut self, value
: bool
) -> Self {
501 self.compress
= value
;
505 /// Set encryption Configuration
507 /// If set, chunks are encrypted
508 pub fn crypt_config(mut self, value
: &'b CryptConfig
) -> Self {
509 if self.digest_computed
{
510 panic
!("unable to set crypt_config after compute_digest().");
512 self.config
= Some(value
);
516 fn compute_digest(&mut self) {
517 if !self.digest_computed
{
518 if let Some(config
) = self.config
{
519 self.digest
= config
.compute_digest(self.orig_data
);
521 self.digest
= openssl
::sha
::sha256(self.orig_data
);
523 self.digest_computed
= true;
527 /// Returns the chunk Digest
529 /// Note: For encrypted chunks, this needs to be called after
530 /// ``crypt_config``.
531 pub fn digest(&mut self) -> &[u8; 32] {
532 if !self.digest_computed
{
533 self.compute_digest();
538 /// Consume self and build the ``DataBlob``.
540 /// Returns the blob and the computet digest.
541 pub fn build(mut self) -> Result
<(DataBlob
, [u8; 32]), Error
> {
542 if !self.digest_computed
{
543 self.compute_digest();
546 let chunk
= DataBlob
::encode(self.orig_data
, self.config
, self.compress
)?
;
547 Ok((chunk
, self.digest
))
550 /// Create a chunk filled with zeroes
551 pub fn build_zero_chunk(
552 crypt_config
: Option
<&CryptConfig
>,
555 ) -> Result
<(DataBlob
, [u8; 32]), Error
> {
556 let zero_bytes
= vec
![0; chunk_size
];
557 let mut chunk_builder
= DataChunkBuilder
::new(&zero_bytes
).compress(compress
);
558 if let Some(crypt_config
) = crypt_config
{
559 chunk_builder
= chunk_builder
.crypt_config(crypt_config
);
562 chunk_builder
.build()