Skip to content

Commit 97dcc66

Browse files
committed
imgtool: Add a possibility to attach manifest TLV
Add a simple logic that allows to attach a manifest TLV to an image. Signed-off-by: Tomasz Chyrowicz <[email protected]>
1 parent 98b6d9a commit 97dcc66

File tree

2 files changed

+89
-6
lines changed

2 files changed

+89
-6
lines changed

scripts/imgtool/image.py

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import re
3434
import struct
3535
import uuid
36+
import yaml
3637
from enum import Enum
3738

3839
import click
@@ -100,6 +101,7 @@
100101
'COMP_DEC_SIZE' : 0x73,
101102
'UUID_VID': 0x74,
102103
'UUID_CID': 0x75,
104+
'MANIFEST': 0x76,
103105
}
104106

105107
TLV_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

283352
class 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)

scripts/imgtool/main.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -477,13 +477,15 @@ def convert(self, value, param, ctx):
477477
help='Unique vendor identifier, format: (<raw_uuid>|<domain_name)>')
478478
@click.option('--cid', default=None, required=False,
479479
help='Unique image class identifier, format: (<raw_uuid>|<image_class_name>)')
480+
@click.option('--manifest', default=None, required=False,
481+
help='Path to the update manifest file')
480482
def sign(key, public_key_format, align, version, pad_sig, header_size,
481483
pad_header, slot_size, pad, confirm, max_sectors, overwrite_only,
482484
endian, encrypt_keylen, encrypt, compression, infile, outfile,
483485
dependencies, load_addr, hex_addr, erased_val, save_enctlv,
484486
security_counter, boot_record, custom_tlv, rom_fixed, max_align,
485487
clear, fix_sig, fix_sig_pubkey, sig_out, user_sha, hmac_sha, is_pure,
486-
vector_to_sign, non_bootable, vid, cid):
488+
vector_to_sign, non_bootable, vid, cid, manifest):
487489

488490
if confirm:
489491
# Confirmed but non-padded images don't make much sense, because
@@ -496,7 +498,8 @@ def sign(key, public_key_format, align, version, pad_sig, header_size,
496498
endian=endian, load_addr=load_addr, rom_fixed=rom_fixed,
497499
erased_val=erased_val, save_enctlv=save_enctlv,
498500
security_counter=security_counter, max_align=max_align,
499-
non_bootable=non_bootable, vid=vid, cid=cid)
501+
non_bootable=non_bootable, vid=vid, cid=cid,
502+
manifest=manifest)
500503
compression_tlvs = {}
501504
img.load(infile)
502505
key = load_key(key) if key else None
@@ -568,7 +571,7 @@ def sign(key, public_key_format, align, version, pad_sig, header_size,
568571
load_addr=load_addr, rom_fixed=rom_fixed,
569572
erased_val=erased_val, save_enctlv=save_enctlv,
570573
security_counter=security_counter, max_align=max_align,
571-
vid=vid, cid=cid)
574+
vid=vid, cid=cid, manifest=manifest)
572575
compression_filters = [
573576
{"id": lzma.FILTER_LZMA2, "preset": comp_default_preset,
574577
"dict_size": comp_default_dictsize, "lp": comp_default_lp,

0 commit comments

Comments
 (0)