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 } s' , 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 } s' , bytes .fromhex (image ['hash' ]))
340+
341+ except FileNotFoundError :
342+ raise click .UsageError (f"Manifest file { self .path } not found" ) 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 } , format={ self .format } , len={ 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 base_addr={}, load_addr={}, align={}, slot_size={}, \
354424 max_sectors={}, overwrite_only={}, endian={} format={}, \
355- payloadlen=0x{:x}, vid={}, cid={}>" .format (
425+ payloadlen=0x{:x}, vid={}, cid={}, 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 } ' is too long ({ len (sw_type )} characters) for sw_type. Its " \
@@ -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