2 use std
::convert
::TryInto
;
5 use proxmox
::tools
::io
::{ReadExt, WriteExt}
;
7 const MAX_BLOB_SIZE
: usize = 128*1024*1024;
9 use super::file_formats
::*;
10 use super::CryptConfig
;
12 /// Encoded data chunk with digest and positional information
13 pub struct ChunkInfo
{
20 /// Data blob binary storage format
22 /// Data blobs store arbitrary binary data (< 128MB), and can be
23 /// compressed and encrypted (or just signed). A simply binary format
24 /// is used to store them on disk or transfer them over the network.
26 /// Please use index files to store large data files (".fidx" of
30 raw_data
: Vec
<u8>, // tagged, compressed, encryped data
35 /// accessor to raw_data field
36 pub fn raw_data(&self) -> &[u8] {
40 /// Consume self and returns raw_data
41 pub fn into_inner(self) -> Vec
<u8> {
45 /// accessor to chunk type (magic number)
46 pub fn magic(&self) -> &[u8; 8] {
47 self.raw_data
[0..8].try_into().unwrap()
50 /// accessor to crc32 checksum
51 pub fn crc(&self) -> u32 {
52 let crc_o
= proxmox
::tools
::offsetof!(DataBlobHeader
, crc
);
53 u32::from_le_bytes(self.raw_data
[crc_o
..crc_o
+4].try_into().unwrap())
56 // set the CRC checksum field
57 pub fn set_crc(&mut self, crc
: u32) {
58 let crc_o
= proxmox
::tools
::offsetof!(DataBlobHeader
, crc
);
59 self.raw_data
[crc_o
..crc_o
+4].copy_from_slice(&crc
.to_le_bytes());
62 /// compute the CRC32 checksum
63 pub fn compute_crc(&self) -> u32 {
64 let mut hasher
= crc32fast
::Hasher
::new();
65 let start
= header_size(self.magic()); // start after HEAD
66 hasher
.update(&self.raw_data
[start
..]);
70 /// verify the CRC32 checksum
71 pub fn verify_crc(&self) -> Result
<(), Error
> {
72 let expected_crc
= self.compute_crc();
73 if expected_crc
!= self.crc() {
74 bail
!("Data blob has wrong CRC checksum.");
79 /// Create a DataBlob, optionally compressed and/or encrypted
82 config
: Option
<Arc
<CryptConfig
>>,
84 ) -> Result
<Self, Error
> {
86 if data
.len() > MAX_BLOB_SIZE
{
87 bail
!("data blob too large ({} bytes).", data
.len());
90 let mut blob
= if let Some(config
) = config
{
93 let (_compress
, data
, magic
) = if compress
{
94 compr_data
= zstd
::block
::compress(data
, 1)?
;
95 // Note: We only use compression if result is shorter
96 if compr_data
.len() < data
.len() {
97 (true, &compr_data
[..], ENCR_COMPR_BLOB_MAGIC_1_0
)
99 (false, data
, ENCRYPTED_BLOB_MAGIC_1_0
)
102 (false, data
, ENCRYPTED_BLOB_MAGIC_1_0
)
105 let header_len
= std
::mem
::size_of
::<EncryptedDataBlobHeader
>();
106 let mut raw_data
= Vec
::with_capacity(data
.len() + header_len
);
108 let dummy_head
= EncryptedDataBlobHeader
{
109 head
: DataBlobHeader { magic: [0u8; 8], crc: [0; 4] }
,
114 raw_data
.write_le_value(dummy_head
)?
;
117 let (iv
, tag
) = config
.encrypt_to(data
, &mut raw_data
)?
;
119 let head
= EncryptedDataBlobHeader
{
120 head
: DataBlobHeader { magic, crc: [0; 4] }
, iv
, tag
,
124 (&mut raw_data
[0..header_len
]).write_le_value(head
)?
;
127 DataBlob { raw_data }
130 let max_data_len
= data
.len() + std
::mem
::size_of
::<DataBlobHeader
>();
132 let mut comp_data
= Vec
::with_capacity(max_data_len
);
134 let head
= DataBlobHeader
{
135 magic
: COMPRESSED_BLOB_MAGIC_1_0
,
139 comp_data
.write_le_value(head
)?
;
142 zstd
::stream
::copy_encode(data
, &mut comp_data
, 1)?
;
144 if comp_data
.len() < max_data_len
{
145 let mut blob
= DataBlob { raw_data: comp_data }
;
146 blob
.set_crc(blob
.compute_crc());
151 let mut raw_data
= Vec
::with_capacity(max_data_len
);
153 let head
= DataBlobHeader
{
154 magic
: UNCOMPRESSED_BLOB_MAGIC_1_0
,
158 raw_data
.write_le_value(head
)?
;
160 raw_data
.extend_from_slice(data
);
162 DataBlob { raw_data }
165 blob
.set_crc(blob
.compute_crc());
171 pub fn decode(self, config
: Option
<Arc
<CryptConfig
>>) -> Result
<Vec
<u8>, Error
> {
173 let magic
= self.magic();
175 if magic
== &UNCOMPRESSED_BLOB_MAGIC_1_0
{
176 let data_start
= std
::mem
::size_of
::<DataBlobHeader
>();
177 return Ok(self.raw_data
[data_start
..].to_vec());
178 } else if magic
== &COMPRESSED_BLOB_MAGIC_1_0
{
179 let data_start
= std
::mem
::size_of
::<DataBlobHeader
>();
180 let data
= zstd
::block
::decompress(&self.raw_data
[data_start
..], MAX_BLOB_SIZE
)?
;
182 } else if magic
== &ENCR_COMPR_BLOB_MAGIC_1_0
|| magic
== &ENCRYPTED_BLOB_MAGIC_1_0
{
183 let header_len
= std
::mem
::size_of
::<EncryptedDataBlobHeader
>();
185 (&self.raw_data
[..header_len
]).read_le_value
::<EncryptedDataBlobHeader
>()?
188 if let Some(config
) = config
{
189 let data
= if magic
== &ENCR_COMPR_BLOB_MAGIC_1_0
{
190 config
.decode_compressed_chunk(&self.raw_data
[header_len
..], &head
.iv
, &head
.tag
)?
192 config
.decode_uncompressed_chunk(&self.raw_data
[header_len
..], &head
.iv
, &head
.tag
)?
196 bail
!("unable to decrypt blob - missing CryptConfig");
198 } else if magic
== &AUTH_COMPR_BLOB_MAGIC_1_0
|| magic
== &AUTHENTICATED_BLOB_MAGIC_1_0
{
199 let header_len
= std
::mem
::size_of
::<AuthenticatedDataBlobHeader
>();
201 (&self.raw_data
[..header_len
]).read_le_value
::<AuthenticatedDataBlobHeader
>()?
204 let data_start
= std
::mem
::size_of
::<AuthenticatedDataBlobHeader
>();
206 // Note: only verify if we have a crypt config
207 if let Some(config
) = config
{
208 let signature
= config
.compute_auth_tag(&self.raw_data
[data_start
..]);
209 if signature
!= head
.tag
{
210 bail
!("verifying blob signature failed");
214 if magic
== &AUTH_COMPR_BLOB_MAGIC_1_0
{
215 let data
= zstd
::block
::decompress(&self.raw_data
[data_start
..], 16*1024*1024)?
;
218 return Ok(self.raw_data
[data_start
..].to_vec());
221 bail
!("Invalid blob magic number.");
225 /// Create a signed DataBlob, optionally compressed
226 pub fn create_signed(
228 config
: Arc
<CryptConfig
>,
230 ) -> Result
<Self, Error
> {
232 if data
.len() > MAX_BLOB_SIZE
{
233 bail
!("data blob too large ({} bytes).", data
.len());
237 let (_compress
, data
, magic
) = if compress
{
238 compr_data
= zstd
::block
::compress(data
, 1)?
;
239 // Note: We only use compression if result is shorter
240 if compr_data
.len() < data
.len() {
241 (true, &compr_data
[..], AUTH_COMPR_BLOB_MAGIC_1_0
)
243 (false, data
, AUTHENTICATED_BLOB_MAGIC_1_0
)
246 (false, data
, AUTHENTICATED_BLOB_MAGIC_1_0
)
249 let header_len
= std
::mem
::size_of
::<AuthenticatedDataBlobHeader
>();
250 let mut raw_data
= Vec
::with_capacity(data
.len() + header_len
);
252 let head
= AuthenticatedDataBlobHeader
{
253 head
: DataBlobHeader { magic, crc: [0; 4] }
,
254 tag
: config
.compute_auth_tag(data
),
257 raw_data
.write_le_value(head
)?
;
259 raw_data
.extend_from_slice(data
);
261 let mut blob
= DataBlob { raw_data }
;
262 blob
.set_crc(blob
.compute_crc());
267 /// Load blob from ``reader``
268 pub fn load(reader
: &mut dyn std
::io
::Read
) -> Result
<Self, Error
> {
270 let mut data
= Vec
::with_capacity(1024*1024);
271 reader
.read_to_end(&mut data
)?
;
276 /// Create Instance from raw data
277 pub fn from_raw(data
: Vec
<u8>) -> Result
<Self, Error
> {
279 if data
.len() < std
::mem
::size_of
::<DataBlobHeader
>() {
280 bail
!("blob too small ({} bytes).", data
.len());
283 let magic
= &data
[0..8];
285 if magic
== ENCR_COMPR_BLOB_MAGIC_1_0
|| magic
== ENCRYPTED_BLOB_MAGIC_1_0
{
287 if data
.len() < std
::mem
::size_of
::<EncryptedDataBlobHeader
>() {
288 bail
!("encrypted blob too small ({} bytes).", data
.len());
291 let blob
= DataBlob { raw_data: data }
;
294 } else if magic
== COMPRESSED_BLOB_MAGIC_1_0
|| magic
== UNCOMPRESSED_BLOB_MAGIC_1_0
{
296 let blob
= DataBlob { raw_data: data }
;
299 } else if magic
== AUTH_COMPR_BLOB_MAGIC_1_0
|| magic
== AUTHENTICATED_BLOB_MAGIC_1_0
{
300 if data
.len() < std
::mem
::size_of
::<AuthenticatedDataBlobHeader
>() {
301 bail
!("authenticated blob too small ({} bytes).", data
.len());
304 let blob
= DataBlob { raw_data: data }
;
308 bail
!("unable to parse raw blob - wrong magic");
312 /// Verify digest and data length for unencrypted chunks.
314 /// To do that, we need to decompress data first. Please note that
315 /// this is noth possible for encrypted chunks.
316 pub fn verify_unencrypted(
318 expected_chunk_size
: usize,
319 expected_digest
: &[u8; 32],
320 ) -> Result
<(), Error
> {
322 let magic
= self.magic();
324 let verify_raw_data
= |data
: &[u8]| {
325 if expected_chunk_size
!= data
.len() {
326 bail
!("detected chunk with wrong length ({} != {})", expected_chunk_size
, data
.len());
328 let digest
= openssl
::sha
::sha256(data
);
329 if &digest
!= expected_digest
{
330 bail
!("detected chunk with wrong digest.");
335 if magic
== &COMPRESSED_BLOB_MAGIC_1_0
{
336 let data
= zstd
::block
::decompress(&self.raw_data
[12..], 16*1024*1024)?
;
337 verify_raw_data(&data
)?
;
338 } else if magic
== &UNCOMPRESSED_BLOB_MAGIC_1_0
{
339 verify_raw_data(&self.raw_data
[12..])?
;
346 /// Builder for chunk DataBlobs
348 /// Main purpose is to centralize digest computation. Digest
349 /// computation differ for encryped chunk, and this interface ensures that
350 /// we always compute the correct one.
351 pub struct DataChunkBuilder
<'a
> {
352 config
: Option
<Arc
<CryptConfig
>>,
354 digest_computed
: bool
,
359 impl <'a
> DataChunkBuilder
<'a
> {
361 /// Create a new builder instance.
362 pub fn new(orig_data
: &'a
[u8]) -> Self {
366 digest_computed
: false,
372 /// Set compression flag.
374 /// If true, chunk data is compressed using zstd (level 1).
375 pub fn compress(mut self, value
: bool
) -> Self {
376 self.compress
= value
;
380 /// Set encryption Configuration
382 /// If set, chunks are encrypted.
383 pub fn crypt_config(mut self, value
: Arc
<CryptConfig
>) -> Self {
384 if self.digest_computed
{
385 panic
!("unable to set crypt_config after compute_digest().");
387 self.config
= Some(value
);
391 fn compute_digest(&mut self) {
392 if !self.digest_computed
{
393 if let Some(ref config
) = self.config
{
394 self.digest
= config
.compute_digest(self.orig_data
);
396 self.digest
= openssl
::sha
::sha256(self.orig_data
);
398 self.digest_computed
= true;
402 /// Returns the chunk Digest
404 /// Note: For encrypted chunks, this needs to be called after
405 /// ``crypt_config``.
406 pub fn digest(&mut self) -> &[u8; 32] {
407 if !self.digest_computed
{
408 self.compute_digest();
413 /// Consume self and build the ``DataBlob``.
415 /// Returns the blob and the computet digest.
416 pub fn build(mut self) -> Result
<(DataBlob
, [u8; 32]), Error
> {
417 if !self.digest_computed
{
418 self.compute_digest();
421 let chunk
= DataBlob
::encode(
427 Ok((chunk
, self.digest
))