3939from  cryptography .hazmat .primitives .kdf .hkdf  import  HKDF 
4040from  cryptography .hazmat .primitives .serialization  import  Encoding , PublicFormat 
4141from  intelhex  import  IntelHex 
42+ from  yaml  import  safe_load  as  yaml_safe_load 
4243
4344from  . import  keys 
4445from  . import  version  as  versmod 
9394        'COMP_DEC_SIZE'  : 0x73 ,
9495        'UUID_VID' : 0x74 ,
9596        'UUID_CID' : 0x75 ,
97+         'MANIFEST' : 0x76 ,
9698}
9799
98100TLV_SIZE  =  4 
@@ -282,6 +284,73 @@ def parse_uuid(namespace, value):
282284
283285    return  uuid_bytes 
284286
287+ class  Manifest :
288+     def  __init__ (self , endian , path ):
289+         self .path  =  path 
290+         self .format  =  1 
291+         self .data  =  None 
292+         self .config  =  None 
293+         self .endian  =  endian 
294+         self .load ()
295+ 
296+     def  load (self ):
297+         try :
298+             with  open (self .path ) as  f :
299+                 self .config  =  yaml_safe_load (f )
300+             format  =  self .config .get ('format' , 0 )
301+             if  isinstance (format , str ) and  format .isdigit ():
302+                 format  =  int (format )
303+             if  format  !=  self .format :
304+                 raise  click .UsageError (f"Unsupported manifest format: { format }  )
305+ 
306+             # Encode manifest format 
307+             e  =  STRUCT_ENDIAN_DICT [self .endian ]
308+             self .data  =  struct .pack (e  +  'I' , format )
309+ 
310+             # Encode number of images/hashes 
311+             n_images  =  len (self .config .get ('images' , []))
312+             self .data  +=  struct .pack (e  +  'I' , n_images )
313+ 
314+             # Encode each image hash 
315+             exp_hash_len  =  None 
316+             for  image  in  self .config .get ('images' , []):
317+                 if  'path'  not  in image  and  'hash'  not  in image :
318+                     raise  click .UsageError (
319+                         "Manifest image entry must contain either 'path' or 'hash'" )
320+ 
321+                 # Encode hash, based on the signed image path 
322+                 if  'path'  in  image :
323+                     (result , version , digest , _ ) =  Image .verify (image ['path' ], None )
324+                     if  result  !=  VerifyResult .OK :
325+                         raise  click .UsageError (f"Failed to verify image: { image ['path' ]}  )
326+ 
327+                     if  exp_hash_len  is  None :
328+                         exp_hash_len  =  len (digest )
329+                     elif  exp_hash_len  !=  len (digest ):
330+                         raise  click .UsageError ("All image hashes must have the same length" )
331+                     self .data  +=  struct .pack (e  +  f'{ exp_hash_len }  , digest )
332+ 
333+                 # Encode RAW image hash 
334+                 if  'hash'  in  image :
335+                     if  exp_hash_len  is  None :
336+                         exp_hash_len  =  len (bytes .fromhex (image ['hash' ]))
337+                     elif  exp_hash_len  !=  len (bytes .fromhex (image ['hash' ])):
338+                         raise  click .UsageError ("All image hashes must have the same length" )
339+                     self .data  +=  struct .pack (e  +  f'{ exp_hash_len }  , bytes .fromhex (image ['hash' ]))
340+ 
341+         except  FileNotFoundError :
342+             raise  click .UsageError (f"Manifest file { self .path }  ) from  None 
343+ 
344+     def  encode (self ):
345+         if  self .data  is  None :
346+             raise  click .UsageError ("Manifest data is empty" )
347+         return  self .data 
348+ 
349+     def  __len__ (self ):
350+         return  len (self .data ) if  self .data  is  not None  else  0 
351+ 
352+     def  __repr__ (self ):
353+         return  f"<Manifest path={ self .path } { self .format } { len (self )}  
285354
286355class  Image :
287356
@@ -291,7 +360,7 @@ def __init__(self, version=None, header_size=IMAGE_HEADER_SIZE,
291360                 overwrite_only = False , endian = "little" , load_addr = 0 ,
292361                 rom_fixed = None , erased_val = None , save_enctlv = False ,
293362                 security_counter = None , max_align = None ,
294-                  non_bootable = False , vid = None , cid = None ):
363+                  non_bootable = False , vid = None , cid = None ,  manifest = None ):
295364
296365        if  load_addr  and  rom_fixed :
297366            raise  click .UsageError ("Can not set rom_fixed and load_addr at the same time" )
@@ -323,6 +392,7 @@ def __init__(self, version=None, header_size=IMAGE_HEADER_SIZE,
323392        self .non_bootable  =  non_bootable 
324393        self .vid  =  vid 
325394        self .cid  =  cid 
395+         self .manifest  =  Manifest (endian = endian , path = manifest ) if  manifest  is  not None  else  None 
326396
327397        if  self .max_align  ==  DEFAULT_MAX_ALIGN :
328398            self .boot_magic  =  bytes ([
@@ -352,7 +422,7 @@ def __repr__(self):
352422        return  "<Image version={}, header_size={}, security_counter={}, \  
353423\ 
354424\ 
355- format (
425+ , manifest={} >"format (
356426                    self .version ,
357427                    self .header_size ,
358428                    self .security_counter ,
@@ -366,7 +436,8 @@ def __repr__(self):
366436                    self .__class__ .__name__ ,
367437                    len (self .payload ),
368438                    self .vid ,
369-                     self .cid )
439+                     self .cid ,
440+                     self .manifest )
370441
371442    def  load (self , path ):
372443        """Load an image from a given file""" 
@@ -556,6 +627,11 @@ def create(self, key, public_key_format, enckey, dependencies=None,
556627            #                                   = 4 + 16 = 20 Bytes 
557628            protected_tlv_size  +=  TLV_SIZE  +  16 
558629
630+         if  self .manifest  is  not None :
631+             # Size of the MANIFEST TLV: header ('HH') + payload (len(manifest)) 
632+             #                                   = 4 + len(manifest) Bytes 
633+             protected_tlv_size  +=  TLV_SIZE  +  len (self .manifest .encode ())
634+ 
559635        if  sw_type  is  not None :
560636            if  len (sw_type ) >  MAX_SW_TYPE_LENGTH :
561637                msg  =  f"'{ sw_type } { len (sw_type )}   \
@@ -671,6 +747,10 @@ def create(self, key, public_key_format, enckey, dependencies=None,
671747                payload  =  struct .pack (e  +  '16s' , cid )
672748                prot_tlv .add ('UUID_CID' , payload )
673749
750+             if  self .manifest  is  not None :
751+                 payload  =  self .manifest .encode ()
752+                 prot_tlv .add ('MANIFEST' , payload )
753+ 
674754            if  custom_tlvs  is  not None :
675755                for  tag , value  in  custom_tlvs .items ():
676756                    prot_tlv .add (tag , value )
0 commit comments