2019-12-31 15:23:41 +01:00
use std ::path ::Path ;
2019-10-12 17:58:08 +02:00
2021-07-07 11:34:45 +02:00
use anyhow ::{ bail , format_err , Error } ;
use serde ::{ Deserialize , Serialize } ;
2022-04-14 13:27:53 +02:00
use serde_json ::{ json , Value } ;
2019-10-12 17:58:08 +02:00
2022-04-14 15:05:58 +02:00
use pbs_api_types ::{ BackupType , CryptMode , Fingerprint } ;
2022-04-14 13:27:53 +02:00
use pbs_tools ::crypt_config ::CryptConfig ;
2021-09-07 09:22:14 +02:00
2019-10-12 17:58:08 +02:00
pub const MANIFEST_BLOB_NAME : & str = " index.json.blob " ;
2020-10-16 09:31:12 +02:00
pub const MANIFEST_LOCK_NAME : & str = " .index.json.lck " ;
2020-05-30 14:04:15 +02:00
pub const CLIENT_LOG_BLOB_NAME : & str = " client.log.blob " ;
2020-12-16 14:41:05 +01:00
pub const ENCRYPTED_KEY_BLOB_NAME : & str = " rsa-encrypted.key.blob " ;
2019-10-12 17:58:08 +02:00
2022-04-14 13:27:53 +02:00
fn crypt_mode_none ( ) -> CryptMode {
CryptMode ::None
}
fn empty_value ( ) -> Value {
json! ( { } )
}
2020-07-09 13:25:38 +02:00
2020-07-09 09:20:49 +02:00
#[ derive(Serialize, Deserialize) ]
2022-04-14 13:27:53 +02:00
#[ serde(rename_all = " kebab-case " ) ]
2019-12-31 15:23:41 +01:00
pub struct FileInfo {
pub filename : String ,
2022-04-14 13:27:53 +02:00
#[ serde(default = " crypt_mode_none " ) ] // to be compatible with < 0.8.0 backups
2020-07-07 15:20:20 +02:00
pub crypt_mode : CryptMode ,
2019-12-31 15:23:41 +01:00
pub size : u64 ,
2021-11-24 13:02:22 +01:00
#[ serde(with = " hex::serde " ) ]
2019-12-31 15:23:41 +01:00
pub csum : [ u8 ; 32 ] ,
2019-10-12 17:58:08 +02:00
}
2020-08-10 13:25:07 +02:00
impl FileInfo {
/// Return expected CryptMode of referenced chunks
///
/// Encrypted Indices should only reference encrypted chunks, while signed or plain indices
/// should only reference plain chunks.
2022-04-14 13:27:53 +02:00
pub fn chunk_crypt_mode ( & self ) -> CryptMode {
2020-08-10 13:25:07 +02:00
match self . crypt_mode {
CryptMode ::Encrypt = > CryptMode ::Encrypt ,
CryptMode ::SignOnly | CryptMode ::None = > CryptMode ::None ,
}
}
}
2020-07-09 09:20:49 +02:00
#[ derive(Serialize, Deserialize) ]
2022-04-14 13:27:53 +02:00
#[ serde(rename_all = " kebab-case " ) ]
2019-10-12 17:58:08 +02:00
pub struct BackupManifest {
2022-04-14 15:05:58 +02:00
backup_type : BackupType ,
2020-07-09 09:20:49 +02:00
backup_id : String ,
backup_time : i64 ,
2019-10-12 17:58:08 +02:00
files : Vec < FileInfo > ,
2022-04-14 13:27:53 +02:00
#[ serde(default = " empty_value " ) ] // to be compatible with < 0.8.0 backups
2020-07-09 09:20:49 +02:00
pub unprotected : Value ,
2020-08-10 13:25:09 +02:00
pub signature : Option < String > ,
2019-10-12 17:58:08 +02:00
}
2022-07-27 15:26:50 +02:00
#[ derive(PartialEq, Eq) ]
2019-12-31 15:23:41 +01:00
pub enum ArchiveType {
FixedIndex ,
DynamicIndex ,
Blob ,
}
2021-07-09 14:26:42 +02:00
impl ArchiveType {
pub fn from_path ( archive_name : impl AsRef < Path > ) -> Result < Self , Error > {
let archive_name = archive_name . as_ref ( ) ;
let archive_type = match archive_name . extension ( ) . and_then ( | ext | ext . to_str ( ) ) {
Some ( " didx " ) = > ArchiveType ::DynamicIndex ,
Some ( " fidx " ) = > ArchiveType ::FixedIndex ,
Some ( " blob " ) = > ArchiveType ::Blob ,
_ = > bail! ( " unknown archive type: {:?} " , archive_name ) ,
} ;
Ok ( archive_type )
}
}
2019-10-12 17:58:08 +02:00
impl BackupManifest {
2022-04-19 10:38:46 +02:00
pub fn new ( snapshot : pbs_api_types ::BackupDir ) -> Self {
2020-07-09 09:20:49 +02:00
Self {
2022-04-19 10:38:46 +02:00
backup_type : snapshot . group . ty ,
2022-05-15 16:01:09 +02:00
backup_id : snapshot . group . id ,
2022-04-19 10:38:46 +02:00
backup_time : snapshot . time ,
2020-07-09 09:20:49 +02:00
files : Vec ::new ( ) ,
unprotected : json ! ( { } ) ,
2020-08-10 13:25:09 +02:00
signature : None ,
2020-07-09 09:20:49 +02:00
}
2019-10-12 17:58:08 +02:00
}
2022-04-14 13:27:53 +02:00
pub fn add_file (
& mut self ,
filename : String ,
size : u64 ,
csum : [ u8 ; 32 ] ,
crypt_mode : CryptMode ,
) -> Result < ( ) , Error > {
2021-07-09 14:26:42 +02:00
let _archive_type = ArchiveType ::from_path ( & filename ) ? ; // check type
2022-04-14 13:27:53 +02:00
self . files . push ( FileInfo {
filename ,
size ,
csum ,
crypt_mode ,
} ) ;
2019-12-31 15:23:41 +01:00
Ok ( ( ) )
}
pub fn files ( & self ) -> & [ FileInfo ] {
& self . files [ .. ]
2019-10-12 17:58:08 +02:00
}
2020-07-23 10:39:18 +02:00
pub fn lookup_file_info ( & self , name : & str ) -> Result < & FileInfo , Error > {
2019-10-13 10:09:12 +02:00
let info = self . files . iter ( ) . find ( | item | item . filename = = name ) ;
match info {
None = > bail! ( " manifest does not contain file '{}' " , name ) ,
Some ( info ) = > Ok ( info ) ,
}
}
pub fn verify_file ( & self , name : & str , csum : & [ u8 ; 32 ] , size : u64 ) -> Result < ( ) , Error > {
let info = self . lookup_file_info ( name ) ? ;
if size ! = info . size {
2020-06-03 19:09:58 +02:00
bail! ( " wrong size for file '{}' ({} != {}) " , name , info . size , size ) ;
2019-10-13 10:09:12 +02:00
}
if csum ! = & info . csum {
bail! ( " wrong checksum for file '{}' " , name ) ;
}
Ok ( ( ) )
}
2020-08-25 18:52:31 +02:00
// Generate canonical json
2020-07-09 12:08:00 +02:00
fn to_canonical_json ( value : & Value ) -> Result < Vec < u8 > , Error > {
2022-06-21 14:27:00 +02:00
proxmox_serde ::json ::to_canonical_json ( value )
2020-07-09 09:20:49 +02:00
}
/// Compute manifest signature
///
/// By generating a HMAC SHA256 over the canonical json
/// representation, The 'unpreotected' property is excluded.
pub fn signature ( & self , crypt_config : & CryptConfig ) -> Result < [ u8 ; 32 ] , Error > {
2022-12-05 11:27:40 +01:00
Self ::json_signature ( & serde_json ::to_value ( self ) ? , crypt_config )
2020-07-09 09:48:30 +02:00
}
fn json_signature ( data : & Value , crypt_config : & CryptConfig ) -> Result < [ u8 ; 32 ] , Error > {
let mut signed_data = data . clone ( ) ;
2020-07-09 09:20:49 +02:00
signed_data . as_object_mut ( ) . unwrap ( ) . remove ( " unprotected " ) ; // exclude
2020-07-10 10:31:42 +02:00
signed_data . as_object_mut ( ) . unwrap ( ) . remove ( " signature " ) ; // exclude
2020-07-09 09:20:49 +02:00
2020-07-09 12:08:00 +02:00
let canonical = Self ::to_canonical_json ( & signed_data ) ? ;
2020-07-08 16:07:14 +02:00
2020-07-09 12:08:00 +02:00
let sig = crypt_config . compute_auth_tag ( & canonical ) ;
2020-07-09 09:20:49 +02:00
Ok ( sig )
2020-07-08 16:07:14 +02:00
}
2020-07-09 09:20:49 +02:00
/// Converts the Manifest into json string, and add a signature if there is a crypt_config.
2020-07-09 11:28:05 +02:00
pub fn to_string ( & self , crypt_config : Option < & CryptConfig > ) -> Result < String , Error > {
2022-12-05 11:27:40 +01:00
let mut manifest = serde_json ::to_value ( self ) ? ;
2020-07-08 16:07:14 +02:00
if let Some ( crypt_config ) = crypt_config {
2020-07-09 09:20:49 +02:00
let sig = self . signature ( crypt_config ) ? ;
2022-12-05 11:27:40 +01:00
manifest [ " signature " ] = hex ::encode ( sig ) . into ( ) ;
2021-09-07 09:22:14 +02:00
let fingerprint = & Fingerprint ::new ( crypt_config . fingerprint ( ) ) ;
2020-11-20 17:38:35 +01:00
manifest [ " unprotected " ] [ " key-fingerprint " ] = serde_json ::to_value ( fingerprint ) ? ;
2020-07-08 16:07:14 +02:00
}
2021-01-15 14:38:27 +01:00
let manifest = serde_json ::to_string_pretty ( & manifest ) . unwrap ( ) ;
2020-07-09 09:20:49 +02:00
Ok ( manifest )
2019-10-12 17:58:08 +02:00
}
2020-11-20 17:38:35 +01:00
pub fn fingerprint ( & self ) -> Result < Option < Fingerprint > , Error > {
match & self . unprotected [ " key-fingerprint " ] {
Value ::Null = > Ok ( None ) ,
2022-04-14 13:27:53 +02:00
value = > Ok ( Some ( Deserialize ::deserialize ( value ) ? ) ) ,
2020-11-20 17:38:35 +01:00
}
}
/// Checks if a BackupManifest and a CryptConfig share a valid fingerprint combination.
///
/// An unsigned manifest is valid with any or no CryptConfig.
/// A signed manifest is only valid with a matching CryptConfig.
pub fn check_fingerprint ( & self , crypt_config : Option < & CryptConfig > ) -> Result < ( ) , Error > {
if let Some ( fingerprint ) = self . fingerprint ( ) ? {
match crypt_config {
None = > bail! (
" missing key - manifest was created with key {} " ,
fingerprint ,
) ,
Some ( crypt_config ) = > {
2021-09-07 09:22:14 +02:00
let config_fp = Fingerprint ::new ( crypt_config . fingerprint ( ) ) ;
2020-11-20 17:38:35 +01:00
if config_fp ! = fingerprint {
bail! (
" wrong key - manifest's key {} does not match provided key {} " ,
fingerprint ,
config_fp
) ;
}
}
}
} ;
Ok ( ( ) )
}
2020-07-09 09:20:49 +02:00
/// Try to read the manifest. This verifies the signature if there is a crypt_config.
2022-04-14 13:27:53 +02:00
pub fn from_data (
data : & [ u8 ] ,
crypt_config : Option < & CryptConfig > ,
) -> Result < BackupManifest , Error > {
2020-07-09 09:20:49 +02:00
let json : Value = serde_json ::from_slice ( data ) ? ;
let signature = json [ " signature " ] . as_str ( ) . map ( String ::from ) ;
2021-12-30 12:57:37 +01:00
if let Some ( crypt_config ) = crypt_config {
2020-07-09 09:20:49 +02:00
if let Some ( signature ) = signature {
2022-12-05 11:27:40 +01:00
let expected_signature = hex ::encode ( Self ::json_signature ( & json , crypt_config ) ? ) ;
2020-11-20 17:38:36 +01:00
let fingerprint = & json [ " unprotected " ] [ " key-fingerprint " ] ;
if fingerprint ! = & Value ::Null {
2022-04-12 12:39:50 +02:00
let fingerprint = Fingerprint ::deserialize ( fingerprint ) ? ;
2021-09-07 09:22:14 +02:00
let config_fp = Fingerprint ::new ( crypt_config . fingerprint ( ) ) ;
2020-11-20 17:38:36 +01:00
if config_fp ! = fingerprint {
bail! (
" wrong key - unable to verify signature since manifest's key {} does not match provided key {} " ,
fingerprint ,
config_fp
) ;
}
}
2020-07-09 09:20:49 +02:00
if signature ! = expected_signature {
bail! ( " wrong signature in manifest " ) ;
}
} else {
// not signed: warn/fail?
}
}
2020-07-09 09:48:30 +02:00
let manifest : BackupManifest = serde_json ::from_value ( json ) ? ;
2020-07-09 09:20:49 +02:00
Ok ( manifest )
}
2019-10-12 17:58:08 +02:00
}
2020-07-09 09:20:49 +02:00
2020-01-05 16:20:26 +01:00
impl TryFrom < super ::DataBlob > for BackupManifest {
type Error = Error ;
fn try_from ( blob : super ::DataBlob ) -> Result < Self , Error > {
2020-08-03 14:10:43 +02:00
// no expected digest available
2022-04-14 13:27:53 +02:00
let data = blob
. decode ( None , None )
2020-01-05 16:20:26 +01:00
. map_err ( | err | format_err! ( " decode backup manifest blob failed - {} " , err ) ) ? ;
let json : Value = serde_json ::from_slice ( & data [ .. ] )
. map_err ( | err | format_err! ( " unable to parse backup manifest json - {} " , err ) ) ? ;
2020-07-09 09:48:30 +02:00
let manifest : BackupManifest = serde_json ::from_value ( json ) ? ;
Ok ( manifest )
2020-01-05 16:20:26 +01:00
}
}
2019-10-12 17:58:08 +02:00
2020-07-09 09:20:49 +02:00
#[ test ]
fn test_manifest_signature ( ) -> Result < ( ) , Error > {
2022-12-12 14:19:52 +01:00
use pbs_key_config ::KeyDerivationConfig ;
2020-07-09 09:20:49 +02:00
let pw = b " test " ;
let kdf = KeyDerivationConfig ::Scrypt {
n : 65536 ,
r : 8 ,
p : 1 ,
salt : Vec ::new ( ) ,
} ;
let testkey = kdf . derive_key ( pw ) ? ;
let crypt_config = CryptConfig ::new ( testkey ) ? ;
2022-04-19 10:38:46 +02:00
let mut manifest = BackupManifest ::new ( " host/elsa/2020-06-26T13:56:05Z " . parse ( ) ? ) ;
2020-07-09 09:20:49 +02:00
manifest . add_file ( " test1.img.fidx " . into ( ) , 200 , [ 1 u8 ; 32 ] , CryptMode ::Encrypt ) ? ;
manifest . add_file ( " abc.blob " . into ( ) , 200 , [ 2 u8 ; 32 ] , CryptMode ::None ) ? ;
manifest . unprotected [ " note " ] = " This is not protected by the signature. " . into ( ) ;
2020-07-09 11:28:05 +02:00
let text = manifest . to_string ( Some ( & crypt_config ) ) ? ;
2020-07-09 09:20:49 +02:00
let manifest : Value = serde_json ::from_str ( & text ) ? ;
let signature = manifest [ " signature " ] . as_str ( ) . unwrap ( ) . to_string ( ) ;
2022-04-14 13:27:53 +02:00
assert_eq! (
signature ,
" d7b446fb7db081662081d4b40fedd858a1d6307a5aff4ecff7d5bf4fd35679e9 "
) ;
2020-07-09 09:20:49 +02:00
2020-07-09 09:48:30 +02:00
let manifest : BackupManifest = serde_json ::from_value ( manifest ) ? ;
2022-12-05 11:27:40 +01:00
let expected_signature = hex ::encode ( manifest . signature ( & crypt_config ) ? ) ;
2020-07-09 09:20:49 +02:00
assert_eq! ( signature , expected_signature ) ;
Ok ( ( ) )
}