3333import re
3434import struct
3535import uuid
36+ import yaml
3637from enum import Enum
3738
3839import click
100101 'COMP_DEC_SIZE' : 0x73 ,
101102 'UUID_VID' : 0x74 ,
102103 'UUID_CID' : 0x75 ,
104+ 'MANIFEST' : 0x76 ,
103105}
104106
105107TLV_SIZE = 4
@@ -279,6 +281,73 @@ def parse_uuid(namespace, value):
279281
280282 return uuid_bytes
281283
284+ class Manifest :
285+ def __init__ (self , endian , path ):
286+ self .path = path
287+ self .format = 1
288+ self .data = None
289+ self .config = None
290+ self .endian = endian
291+ self .load ()
292+
293+ def load (self ):
294+ try :
295+ with open (self .path , 'r' ) as f :
296+ self .config = yaml .safe_load (f )
297+ format = self .config .get ('format' , 0 )
298+ if isinstance (format , str ) and format .isdigit ():
299+ format = int (format )
300+ if format != self .format :
301+ raise click .UsageError ("Unsupported manifest format: {}" .format (format ))
302+
303+ # Encode manifest format
304+ e = STRUCT_ENDIAN_DICT [self .endian ]
305+ self .data = struct .pack (e + 'I' , format )
306+
307+ # Encode number of images/hashes
308+ n_images = len (self .config .get ('images' , []))
309+ self .data += struct .pack (e + 'I' , n_images )
310+
311+ # Encode each image hash
312+ exp_hash_len = None
313+ for image in self .config .get ('images' , []):
314+ if 'path' not in image and 'hash' not in image :
315+ raise click .UsageError ("Manifest image entry must contain either 'path' or 'hash'" )
316+
317+ # Encode hash, based on the signed image path
318+ if 'path' in image :
319+ (result , version , digest , _ ) = Image .verify (image ['path' ], None )
320+ if result != VerifyResult .OK :
321+ raise click .UsageError ("Failed to verify image: {}" .format (image ['path' ]))
322+
323+ if exp_hash_len is None :
324+ exp_hash_len = len (digest )
325+ elif exp_hash_len != len (digest ):
326+ raise click .UsageError ("All image hashes in manifest must have the same length" )
327+ self .data += struct .pack (e + f'{ exp_hash_len } s' , digest )
328+
329+ # Encode RAW image hash
330+ if 'hash' in image :
331+ if exp_hash_len is None :
332+ exp_hash_len = len (bytes .fromhex (image ['hash' ]))
333+ elif exp_hash_len != len (bytes .fromhex (image ['hash' ])):
334+ raise click .UsageError ("All image hashes in manifest must have the same length" )
335+ self .data += struct .pack (e + f'{ exp_hash_len } s' , bytes .fromhex (image ['hash' ]))
336+
337+ except FileNotFoundError :
338+ raise click .UsageError (f"Manifest file { self .path } not found" )
339+
340+ def encode (self ):
341+ if self .data is None :
342+ raise click .UsageError ("Manifest data is empty" )
343+ return self .data
344+
345+ def __len__ (self ):
346+ return len (self .data ) if self .data is not None else 0
347+
348+ def __repr__ (self ):
349+ return "<Manifest path={}, format={}, len={}>" .format (
350+ self .path , self .format , len (self ))
282351
283352class Image :
284353
@@ -288,7 +357,7 @@ def __init__(self, version=None, header_size=IMAGE_HEADER_SIZE,
288357 overwrite_only = False , endian = "little" , load_addr = 0 ,
289358 rom_fixed = None , erased_val = None , save_enctlv = False ,
290359 security_counter = None , max_align = None ,
291- non_bootable = False , vid = None , cid = None ):
360+ non_bootable = False , vid = None , cid = None , manifest = None ):
292361
293362 if load_addr and rom_fixed :
294363 raise click .UsageError ("Can not set rom_fixed and load_addr at the same time" )
@@ -319,6 +388,7 @@ def __init__(self, version=None, header_size=IMAGE_HEADER_SIZE,
319388 self .non_bootable = non_bootable
320389 self .vid = vid
321390 self .cid = cid
391+ self .manifest = Manifest (endian = endian , path = manifest ) if manifest is not None else None
322392
323393 if self .max_align == DEFAULT_MAX_ALIGN :
324394 self .boot_magic = bytes ([
@@ -348,7 +418,7 @@ def __repr__(self):
348418 return "<Image version={}, header_size={}, security_counter={}, \
349419 base_addr={}, load_addr={}, align={}, slot_size={}, \
350420 max_sectors={}, overwrite_only={}, endian={} format={}, \
351- payloadlen=0x{:x}, vid={}, cid={}>" .format (
421+ payloadlen=0x{:x}, vid={}, cid={}, manifest={} >" .format (
352422 self .version ,
353423 self .header_size ,
354424 self .security_counter ,
@@ -362,7 +432,8 @@ def __repr__(self):
362432 self .__class__ .__name__ ,
363433 len (self .payload ),
364434 self .vid ,
365- self .cid )
435+ self .cid ,
436+ self .manifest )
366437
367438 def load (self , path ):
368439 """Load an image from a given file"""
@@ -548,6 +619,11 @@ def create(self, key, public_key_format, enckey, dependencies=None,
548619 # = 4 + 16 = 20 Bytes
549620 protected_tlv_size += TLV_SIZE + 16
550621
622+ if self .manifest is not None :
623+ # Size of the MANIFEST TLV: header ('HH') + payload (len(manifest))
624+ # = 4 + len(manifest) Bytes
625+ protected_tlv_size += TLV_SIZE + len (self .manifest .encode ())
626+
551627 if sw_type is not None :
552628 if len (sw_type ) > MAX_SW_TYPE_LENGTH :
553629 msg = "'{}' is too long ({} characters) for sw_type. Its " \
@@ -666,6 +742,10 @@ def create(self, key, public_key_format, enckey, dependencies=None,
666742 payload = struct .pack (e + '16s' , cid )
667743 prot_tlv .add ('UUID_CID' , payload )
668744
745+ if self .manifest is not None :
746+ payload = self .manifest .encode ()
747+ prot_tlv .add ('MANIFEST' , payload )
748+
669749 if custom_tlvs is not None :
670750 for tag , value in custom_tlvs .items ():
671751 prot_tlv .add (tag , value )
0 commit comments