2020-04-17 14:11:25 +02:00
use anyhow ::{ bail , format_err , Error } ;
2019-10-12 17:58:08 +02:00
use std ::convert ::TryFrom ;
2019-12-31 15:23:41 +01:00
use std ::path ::Path ;
2019-10-12 17:58:08 +02:00
use serde_json ::{ json , Value } ;
2020-07-09 09:20:49 +02:00
use ::serde ::{ Deserialize , Serialize } ;
2019-10-12 17:58:08 +02:00
2020-07-08 16:07:14 +02:00
use crate ::backup ::{ BackupDir , CryptMode , CryptConfig } ;
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 " ;
2019-10-12 17:58:08 +02:00
2020-07-09 09:20:49 +02:00
mod hex_csum {
use serde ::{ self , Deserialize , Serializer , Deserializer } ;
pub fn serialize < S > (
csum : & [ u8 ; 32 ] ,
serializer : S ,
) -> Result < S ::Ok , S ::Error >
where
S : Serializer ,
{
let s = proxmox ::tools ::digest_to_hex ( csum ) ;
serializer . serialize_str ( & s )
}
pub fn deserialize < ' de , D > (
deserializer : D ,
) -> Result < [ u8 ; 32 ] , D ::Error >
where
D : Deserializer < ' de > ,
{
let s = String ::deserialize ( deserializer ) ? ;
proxmox ::tools ::hex_to_digest ( & s ) . map_err ( serde ::de ::Error ::custom )
}
}
2020-07-09 13:25:38 +02:00
fn crypt_mode_none ( ) -> CryptMode { CryptMode ::None }
fn empty_value ( ) -> Value { json! ( { } ) }
2020-07-09 09:20:49 +02:00
#[ derive(Serialize, Deserialize) ]
#[ serde(rename_all= " kebab-case " ) ]
2019-12-31 15:23:41 +01:00
pub struct FileInfo {
pub filename : String ,
2020-07-09 13:25:38 +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 ,
2020-07-09 09:20:49 +02:00
#[ serde(with = " hex_csum " ) ]
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.
pub fn chunk_crypt_mode ( & self ) -> CryptMode {
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) ]
#[ serde(rename_all= " kebab-case " ) ]
2019-10-12 17:58:08 +02:00
pub struct BackupManifest {
2020-07-09 09:20:49 +02:00
backup_type : String ,
backup_id : String ,
backup_time : i64 ,
2019-10-12 17:58:08 +02:00
files : Vec < FileInfo > ,
2020-07-09 13:25:38 +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
}
2019-12-31 15:23:41 +01:00
#[ derive(PartialEq) ]
pub enum ArchiveType {
FixedIndex ,
DynamicIndex ,
Blob ,
}
pub fn archive_type < P : AsRef < Path > > (
archive_name : P ,
) -> Result < ArchiveType , 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 {
pub fn new ( snapshot : BackupDir ) -> Self {
2020-07-09 09:20:49 +02:00
Self {
backup_type : snapshot . group ( ) . backup_type ( ) . into ( ) ,
backup_id : snapshot . group ( ) . backup_id ( ) . into ( ) ,
2020-09-12 15:10:47 +02:00
backup_time : snapshot . backup_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
}
2020-07-07 15:20:20 +02:00
pub fn add_file ( & mut self , filename : String , size : u64 , csum : [ u8 ; 32 ] , crypt_mode : CryptMode ) -> Result < ( ) , Error > {
2019-12-31 15:23:41 +01:00
let _archive_type = archive_type ( & filename ) ? ; // check type
2020-07-07 15:20:20 +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 > {
let mut data = Vec ::new ( ) ;
Self ::write_canonical_json ( value , & mut data ) ? ;
Ok ( data )
}
fn write_canonical_json ( value : & Value , output : & mut Vec < u8 > ) -> Result < ( ) , Error > {
2020-07-09 09:20:49 +02:00
match value {
Value ::Null = > bail! ( " got unexpected null value " ) ,
2020-07-09 12:08:00 +02:00
Value ::String ( _ ) | Value ::Number ( _ ) | Value ::Bool ( _ ) = > {
serde_json ::to_writer ( output , & value ) ? ;
2020-07-09 09:20:49 +02:00
}
Value ::Array ( list ) = > {
2020-07-09 12:08:00 +02:00
output . push ( b '[' ) ;
let mut iter = list . iter ( ) ;
if let Some ( item ) = iter . next ( ) {
Self ::write_canonical_json ( item , output ) ? ;
for item in iter {
output . push ( b ',' ) ;
Self ::write_canonical_json ( item , output ) ? ;
}
2020-07-09 09:20:49 +02:00
}
2020-07-09 12:08:00 +02:00
output . push ( b ']' ) ;
2020-07-09 09:20:49 +02:00
}
Value ::Object ( map ) = > {
2020-07-09 12:08:00 +02:00
output . push ( b '{' ) ;
let mut keys : Vec < & str > = map . keys ( ) . map ( String ::as_str ) . collect ( ) ;
2020-07-09 09:20:49 +02:00
keys . sort ( ) ;
2020-07-09 12:08:00 +02:00
let mut iter = keys . into_iter ( ) ;
if let Some ( key ) = iter . next ( ) {
2020-07-27 10:31:34 +02:00
serde_json ::to_writer ( & mut * output , & key ) ? ;
2020-07-09 12:08:00 +02:00
output . push ( b ':' ) ;
Self ::write_canonical_json ( & map [ key ] , output ) ? ;
for key in iter {
output . push ( b ',' ) ;
2020-07-27 10:31:34 +02:00
serde_json ::to_writer ( & mut * output , & key ) ? ;
2020-07-09 12:08:00 +02:00
output . push ( b ':' ) ;
Self ::write_canonical_json ( & map [ key ] , output ) ? ;
}
2020-07-09 09:20:49 +02:00
}
2020-07-09 12:08:00 +02:00
output . push ( b '}' ) ;
2020-07-09 09:20:49 +02:00
}
2020-07-08 16:07:14 +02:00
}
2020-07-09 09:20:49 +02:00
Ok ( ( ) )
}
/// 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 > {
2020-07-09 09:48:30 +02:00
Self ::json_signature ( & serde_json ::to_value ( & self ) ? , crypt_config )
}
fn json_signature ( data : & Value , crypt_config : & CryptConfig ) -> Result < [ u8 ; 32 ] , Error > {
2020-07-09 09:20:49 +02:00
2020-07-09 09:48:30 +02:00
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 > {
2020-07-09 09:20:49 +02: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 ) ? ;
2020-07-08 16:07:14 +02:00
manifest [ " signature " ] = proxmox ::tools ::digest_to_hex ( & sig ) . into ( ) ;
}
2020-07-09 09:20:49 +02:00
let manifest = serde_json ::to_string_pretty ( & manifest ) . unwrap ( ) . into ( ) ;
Ok ( manifest )
2019-10-12 17:58:08 +02:00
}
2020-07-09 09:20:49 +02:00
/// Try to read the manifest. This verifies the signature if there is a crypt_config.
pub fn from_data ( data : & [ u8 ] , crypt_config : Option < & CryptConfig > ) -> Result < BackupManifest , Error > {
let json : Value = serde_json ::from_slice ( data ) ? ;
let signature = json [ " signature " ] . as_str ( ) . map ( String ::from ) ;
if let Some ( ref crypt_config ) = crypt_config {
if let Some ( signature ) = signature {
2020-07-09 09:48:30 +02:00
let expected_signature = proxmox ::tools ::digest_to_hex ( & Self ::json_signature ( & json , crypt_config ) ? ) ;
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-07-09 09:48:30 +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
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 > {
use crate ::backup ::{ KeyDerivationConfig } ;
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 ) ? ;
let snapshot : BackupDir = " host/elsa/2020-06-26T13:56:05Z " . parse ( ) ? ;
let mut manifest = BackupManifest ::new ( snapshot ) ;
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 ( ) ;
assert_eq! ( signature , " d7b446fb7db081662081d4b40fedd858a1d6307a5aff4ecff7d5bf4fd35679e9 " ) ;
2020-07-09 09:48:30 +02:00
let manifest : BackupManifest = serde_json ::from_value ( manifest ) ? ;
2020-07-09 09:20:49 +02:00
let expected_signature = proxmox ::tools ::digest_to_hex ( & manifest . signature ( & crypt_config ) ? ) ;
assert_eq! ( signature , expected_signature ) ;
Ok ( ( ) )
}