Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

imgtool: Bugfixes and improvements for various imgtool commands #1973

Merged
merged 6 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions scripts/imgtool/boot_record.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2019-2021, Arm Limited.
# Copyright (c) 2019-2024, Arm Limited.
# Copyright (c) 2020, Linaro Limited
#
# SPDX-License-Identifier: Apache-2.0
Expand All @@ -22,6 +22,7 @@
except ImportError:
from cbor import dumps


class SwComponent(int, Enum):
"""
Software component property IDs specified by
Expand All @@ -36,17 +37,16 @@ class SwComponent(int, Enum):

def create_sw_component_data(sw_type, sw_version, sw_measurement_description,
sw_measurement_value, sw_signer_id):

# List of software component properties (Key ID + value)
properties = {
SwComponent.TYPE: sw_type,
SwComponent.VERSION: sw_version,
SwComponent.SIGNER_ID: sw_signer_id,
SwComponent.MEASUREMENT_DESCRIPTION: sw_measurement_description,
}
properties = {SwComponent.TYPE: sw_type,
SwComponent.VERSION: sw_version,
SwComponent.SIGNER_ID: sw_signer_id,
SwComponent.MEASUREMENT_DESCRIPTION: sw_measurement_description,
SwComponent.MEASUREMENT_VALUE: sw_measurement_value,
}

# Note: The measurement value must be the last item of the property
# list because later it will be modified by the bootloader.
properties[SwComponent.MEASUREMENT_VALUE] = sw_measurement_value

last_key = list(properties.keys())[-1]
assert SwComponent.MEASUREMENT_VALUE == last_key, 'Measurement value is not the last item of the property list'
return dumps(properties)
216 changes: 127 additions & 89 deletions scripts/imgtool/dumpinfo.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2023 Arm Limited
# Copyright 2023-2024 Arm Limited
#
# SPDX-License-Identifier: Apache-2.0
#
Expand All @@ -17,28 +17,83 @@
"""
Parse and print header, TLV area and trailer information of a signed image.
"""
from imgtool import image
import click
import struct
import yaml
import os.path
import struct
import sys

import click
import yaml

from imgtool import image

HEADER_ITEMS = ("magic", "load_addr", "hdr_size", "protected_tlv_size",
"img_size", "flags", "version")
TLV_TYPES = dict((value, key) for key, value in image.TLV_VALUES.items())
BOOT_MAGIC = bytes([
0x77, 0xc2, 0x95, 0xf3,
0x60, 0xd2, 0xef, 0x7f,
0x35, 0x52, 0x50, 0x0f,
0x2c, 0xb6, 0x79, 0x80, ])
0x77, 0xc2, 0x95, 0xf3,
0x60, 0xd2, 0xef, 0x7f,
0x35, 0x52, 0x50, 0x0f,
0x2c, 0xb6, 0x79, 0x80, ])
BOOT_MAGIC_2 = bytes([
0x2d, 0xe1, 0x5d, 0x29,
0x41, 0x0b, 0x8d, 0x77,
0x67, 0x9c, 0x11, 0x0f,
0x1f, 0x8a, ])
0x2d, 0xe1, 0x5d, 0x29,
0x41, 0x0b, 0x8d, 0x77,
0x67, 0x9c, 0x11, 0x0f,
0x1f, 0x8a, ])
BOOT_MAGIC_SIZE = len(BOOT_MAGIC)
_LINE_LENGTH = 60
STATUS = {
'0x1': 'SET',
'0x2': 'BAD',
'0x3': 'UNSET',
'0x4': 'ANY',
}


def parse_enc(key_field_len):
if key_field_len is not None:
return "(len: {}, if BOOT_SWAP_SAVE_ENCTLV is unset)".format(hex(key_field_len))
else:
return "Image not encrypted"


def parse_size(size_hex):
if size_hex == '0xffffffff':
return "unknown"
return size_hex + " octal: " + str(int(size_hex, 0))


def parse_status(status_hex):
return f"{STATUS[status_hex]} ({status_hex})" if status_hex in STATUS else f"INVALID ({status_hex})"


def parse_boot_magic(trailer_magic):
magic = ""
for i in range(BOOT_MAGIC_SIZE):
magic += "{0:#04x} ".format(trailer_magic[i])
if i == (BOOT_MAGIC_SIZE / 2 - 1):
magic += ("\n" + " ")
return magic


def print_in_frame(header_text, content):
sepc = " "
header = "#### " + header_text + sepc
post_header = "#" * (_LINE_LENGTH - len(header))
print(header + post_header)

print("|", sepc * (_LINE_LENGTH - 2), "|", sep="")
offset = (_LINE_LENGTH - len(content)) // 2
pre = "|" + (sepc * (offset - 1))
post = sepc * (_LINE_LENGTH - len(pre) - len(content) - 1) + "|"
print(pre, content, post, sep="")
print("|", sepc * (_LINE_LENGTH - 2), "|", sep="")
print("#" * _LINE_LENGTH)


def print_in_row(row_text):
row_text = "#### " + row_text + " "
fill = "#" * (_LINE_LENGTH - len(row_text))
print(row_text + fill)


def print_tlv_records(tlv_list):
Expand All @@ -47,20 +102,33 @@ def print_tlv_records(tlv_list):
print(" " * indent, "-" * 45)
tlv_type, tlv_length, tlv_data = tlv.keys()

print(" " * indent, "{}: {} ({})".format(
if tlv[tlv_type] in TLV_TYPES:
print(" " * indent, "{}: {} ({})".format(
tlv_type, TLV_TYPES[tlv[tlv_type]], hex(tlv[tlv_type])))
else:
print(" " * indent, "{}: {} ({})".format(
tlv_type, "UNKNOWN", hex(tlv[tlv_type])))
print(" " * indent, "{}: ".format(tlv_length), hex(tlv[tlv_length]))
print(" " * indent, "{}: ".format(tlv_data), end="")

for j, data in enumerate(tlv[tlv_data]):
print("{0:#04x}".format(data), end=" ")
if ((j+1) % 8 == 0) and ((j+1) != len(tlv[tlv_data])):
print("\n", end=" " * (indent+7))
print("{0:#04x}".format(data), end=" ")
if ((j + 1) % 8 == 0) and ((j + 1) != len(tlv[tlv_data])):
print("\n", end=" " * (indent + 7))
print()


def dump_imginfo(imgfile, outfile=None, silent=False):
'''Parse a signed image binary and print/save the available information.'''
"""Parse a signed image binary and print/save the available information."""
trailer_magic = None
# set to INVALID by default
swap_size = 0x99
swap_info = 0x99
copy_done = 0x99
image_ok = 0x99
trailer = {}
key_field_len = None

try:
with open(imgfile, "rb") as f:
b = f.read()
Expand All @@ -80,16 +148,16 @@ def dump_imginfo(imgfile, outfile=None, silent=False):

# Parsing the TLV area
tlv_area = {"tlv_hdr_prot": {},
"tlvs_prot": [],
"tlv_hdr": {},
"tlvs": []}
"tlvs_prot": [],
"tlv_hdr": {},
"tlvs": []}
tlv_off = header["hdr_size"] + header["img_size"]
protected_tlv_size = header["protected_tlv_size"]

if protected_tlv_size != 0:
_tlv_prot_head = struct.unpack(
'HH',
b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)])
'HH',
b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)])
tlv_area["tlv_hdr_prot"]["magic"] = _tlv_prot_head[0]
tlv_area["tlv_hdr_prot"]["tlv_tot"] = _tlv_prot_head[1]
tlv_end = tlv_off + tlv_area["tlv_hdr_prot"]["tlv_tot"]
Expand All @@ -98,8 +166,8 @@ def dump_imginfo(imgfile, outfile=None, silent=False):
# Iterating through the protected TLV area
while tlv_off < tlv_end:
tlv_type, tlv_len = struct.unpack(
'HH',
b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)])
'HH',
b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)])
tlv_off += image.TLV_INFO_SIZE
tlv_data = b[tlv_off:(tlv_off + tlv_len)]
tlv_area["tlvs_prot"].append(
Expand All @@ -116,8 +184,8 @@ def dump_imginfo(imgfile, outfile=None, silent=False):
# Iterating through the TLV area
while tlv_off < tlv_end:
tlv_type, tlv_len = struct.unpack(
'HH',
b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)])
'HH',
b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)])
tlv_off += image.TLV_INFO_SIZE
tlv_data = b[tlv_off:(tlv_off + tlv_len)]
tlv_area["tlvs"].append(
Expand All @@ -128,7 +196,6 @@ def dump_imginfo(imgfile, outfile=None, silent=False):

if _img_pad_size:
# Parsing the image trailer
trailer = {}
trailer_off = -BOOT_MAGIC_SIZE
trailer_magic = b[trailer_off:]
trailer["magic"] = trailer_magic
Expand All @@ -138,7 +205,7 @@ def dump_imginfo(imgfile, outfile=None, silent=False):
max_align = 8
elif trailer_magic[-len(BOOT_MAGIC_2):] == BOOT_MAGIC_2:
# The alignment value is encoded in the magic field
max_align = int(trailer_magic[:2], 0)
max_align = int.from_bytes(trailer_magic[:2], "little")
else:
# Invalid magic: the rest of the image trailer cannot be processed.
print("Warning: the trailer magic value is invalid!")
Expand All @@ -165,9 +232,8 @@ def dump_imginfo(imgfile, outfile=None, silent=False):
trailer["swap_size"] = swap_size

# Encryption key 0/1
key_field_len = None
if ((header["flags"] & image.IMAGE_F["ENCRYPTED_AES128"]) or
(header["flags"] & image.IMAGE_F["ENCRYPTED_AES256"])):
(header["flags"] & image.IMAGE_F["ENCRYPTED_AES256"])):
# The image is encrypted
# Estimated value of key_field_len is correct if
# BOOT_SWAP_SAVE_ENCTLV is unset
Expand All @@ -182,67 +248,55 @@ def dump_imginfo(imgfile, outfile=None, silent=False):
# sort_keys - from pyyaml 5.1
yaml.dump(imgdata, outf, sort_keys=False)

###############################################################################
###############################################################################

if silent:
sys.exit(0)

print("Printing content of signed image:", os.path.basename(imgfile), "\n")

# Image header
str1 = "#### Image header (offset: 0x0) "
str2 = "#" * (_LINE_LENGTH - len(str1))
print(str1 + str2)
section_name = "Image header (offset: 0x0)"
print_in_row(section_name)
for key, value in header.items():
if key == "flags":
if not value:
flag_string = hex(value)
else:
flag_string = ""
for flag in image.IMAGE_F.keys():
if (value & image.IMAGE_F[flag]):
if value & image.IMAGE_F[flag]:
if flag_string:
flag_string += ("\n" + (" " * 20))
flag_string += "{} ({})".format(
flag, hex(image.IMAGE_F[flag]))
flag, hex(image.IMAGE_F[flag]))
value = flag_string

if type(value) != str:
if not isinstance(value, str):
value = hex(value)
print(key, ":", " " * (19-len(key)), value, sep="")
print(key, ":", " " * (19 - len(key)), value, sep="")
print("#" * _LINE_LENGTH)

# Image payload
_sectionoff = header["hdr_size"]
sepc = " "
str1 = "#### Payload (offset: {}) ".format(hex(_sectionoff))
str2 = "#" * (_LINE_LENGTH - len(str1))
print(str1 + str2)
print("|", sepc * (_LINE_LENGTH - 2), "|", sep="")
str1 = "FW image (size: {} Bytes)".format(hex(header["img_size"]))
numc = (_LINE_LENGTH - len(str1)) // 2
str2 = "|" + (sepc * (numc - 1))
str3 = sepc * (_LINE_LENGTH - len(str2) - len(str1) - 1) + "|"
print(str2, str1, str3, sep="")
print("|", sepc * (_LINE_LENGTH - 2), "|", sep="")
print("#" * _LINE_LENGTH)
frame_header_text = "Payload (offset: {})".format(hex(_sectionoff))
frame_content = "FW image (size: {} Bytes)".format(hex(header["img_size"]))
print_in_frame(frame_header_text, frame_content)

# TLV area
_sectionoff += header["img_size"]
if protected_tlv_size != 0:
# Protected TLV area
str1 = "#### Protected TLV area (offset: {}) ".format(hex(_sectionoff))
str2 = "#" * (_LINE_LENGTH - len(str1))
print(str1 + str2)
section_name = "Protected TLV area (offset: {})".format(hex(_sectionoff))
print_in_row(section_name)
print("magic: ", hex(tlv_area["tlv_hdr_prot"]["magic"]))
print("area size:", hex(tlv_area["tlv_hdr_prot"]["tlv_tot"]))
print_tlv_records(tlv_area["tlvs_prot"])
print("#" * _LINE_LENGTH)

_sectionoff += protected_tlv_size
str1 = "#### TLV area (offset: {}) ".format(hex(_sectionoff))
str2 = "#" * (_LINE_LENGTH - len(str1))
print(str1 + str2)
section_name = "TLV area (offset: {})".format(hex(_sectionoff))
print_in_row(section_name)
print("magic: ", hex(tlv_area["tlv_hdr"]["magic"]))
print("area size:", hex(tlv_area["tlv_hdr"]["tlv_tot"]))
print_tlv_records(tlv_area["tlvs"])
Expand All @@ -251,40 +305,24 @@ def dump_imginfo(imgfile, outfile=None, silent=False):
if _img_pad_size:
_sectionoff += tlv_area["tlv_hdr"]["tlv_tot"]
_erased_val = b[_sectionoff]
str1 = "#### Image padding (offset: {}) ".format(hex(_sectionoff))
str2 = "#" * (_LINE_LENGTH - len(str1))
print(str1 + str2)
print("|", sepc * (_LINE_LENGTH - 2), "|", sep="")
str1 = "padding ({})".format(hex(_erased_val))
numc = (_LINE_LENGTH - len(str1)) // 2
str2 = "|" + (sepc * (numc - 1))
str3 = sepc * (_LINE_LENGTH - len(str2) - len(str1) - 1) + "|"
print(str2, str1, str3, sep="")
print("|", sepc * (_LINE_LENGTH - 2), "|", sep="")
print("#" * _LINE_LENGTH)
frame_header_text = "Image padding (offset: {})".format(hex(_sectionoff))
frame_content = "padding ({})".format(hex(_erased_val))
print_in_frame(frame_header_text, frame_content)

# Image trailer
str1 = "#### Image trailer (offset: unknown) "
str2 = "#" * (_LINE_LENGTH - len(str1))
print(str1 + str2)
print("(Note: some field may not be used, \n"
" depending on the update strategy)\n")

section_name = "Image trailer (offset: unknown)"
print_in_row(section_name)
notice = "(Note: some fields may not be used, depending on the update strategy)\n"
notice = '\n'.join(notice[i:i + _LINE_LENGTH] for i in range(0, len(notice), _LINE_LENGTH))
print(notice)
print("swap status: (len: unknown)")
if key_field_len is not None:
print("enc. keys: (len: {}, if BOOT_SWAP_SAVE_ENCTLV is unset)"
.format(hex(key_field_len)))
print("swap size: ", hex(swap_size))
print("swap_info: ", hex(swap_info))
print("copy_done: ", hex(copy_done))
print("image_ok: ", hex(image_ok))
print("boot magic: ", end="")
for i in range(BOOT_MAGIC_SIZE):
print("{0:#04x}".format(trailer_magic[i]), end=" ")
if (i == (BOOT_MAGIC_SIZE/2 - 1)):
print("\n", end=" ")
print("enc. keys: ", parse_enc(key_field_len))
print("swap size: ", parse_size(hex(swap_size)))
print("swap_info: ", parse_status(hex(swap_info)))
print("copy_done: ", parse_status(hex(copy_done)))
print("image_ok: ", parse_status(hex(image_ok)))
print("boot magic: ", parse_boot_magic(trailer_magic))
print()

str1 = "#### End of Image "
str2 = "#" * (_LINE_LENGTH - len(str1))
print(str1 + str2)
footer = "End of Image "
print_in_row(footer)
Loading
Loading