Rustam Ismayilov | f3a5702 | 2023-11-27 21:19:43 +0100 | [diff] [blame] | 1 | # Copyright 2023-2024 Arm Limited |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 2 | # |
| 3 | # SPDX-License-Identifier: Apache-2.0 |
| 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | # you may not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
| 16 | |
| 17 | """ |
| 18 | Parse and print header, TLV area and trailer information of a signed image. |
| 19 | """ |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 20 | import os.path |
Rustam Ismayilov | 316a139 | 2023-12-01 17:32:06 +0100 | [diff] [blame] | 21 | import struct |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 22 | import sys |
| 23 | |
Rustam Ismayilov | 316a139 | 2023-12-01 17:32:06 +0100 | [diff] [blame] | 24 | import click |
| 25 | import yaml |
| 26 | |
| 27 | from imgtool import image |
| 28 | |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 29 | HEADER_ITEMS = ("magic", "load_addr", "hdr_size", "protected_tlv_size", |
| 30 | "img_size", "flags", "version") |
| 31 | TLV_TYPES = dict((value, key) for key, value in image.TLV_VALUES.items()) |
| 32 | BOOT_MAGIC = bytes([ |
Rustam Ismayilov | f3a5702 | 2023-11-27 21:19:43 +0100 | [diff] [blame] | 33 | 0x77, 0xc2, 0x95, 0xf3, |
| 34 | 0x60, 0xd2, 0xef, 0x7f, |
| 35 | 0x35, 0x52, 0x50, 0x0f, |
| 36 | 0x2c, 0xb6, 0x79, 0x80, ]) |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 37 | BOOT_MAGIC_2 = bytes([ |
Rustam Ismayilov | f3a5702 | 2023-11-27 21:19:43 +0100 | [diff] [blame] | 38 | 0x2d, 0xe1, 0x5d, 0x29, |
| 39 | 0x41, 0x0b, 0x8d, 0x77, |
| 40 | 0x67, 0x9c, 0x11, 0x0f, |
| 41 | 0x1f, 0x8a, ]) |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 42 | BOOT_MAGIC_SIZE = len(BOOT_MAGIC) |
| 43 | _LINE_LENGTH = 60 |
Rustam Ismayilov | 316a139 | 2023-12-01 17:32:06 +0100 | [diff] [blame] | 44 | STATUS = { |
| 45 | '0x1': 'SET', |
| 46 | '0x2': 'BAD', |
| 47 | '0x3': 'UNSET', |
| 48 | '0x4': 'ANY', |
| 49 | } |
| 50 | |
| 51 | |
| 52 | def parse_enc(key_field_len): |
| 53 | if key_field_len is not None: |
| 54 | return "(len: {}, if BOOT_SWAP_SAVE_ENCTLV is unset)".format(hex(key_field_len)) |
| 55 | else: |
| 56 | return "Image not encrypted" |
| 57 | |
| 58 | |
| 59 | def parse_size(size_hex): |
| 60 | if size_hex == '0xffffffff': |
| 61 | return "unknown" |
| 62 | return size_hex + " octal: " + str(int(size_hex, 0)) |
| 63 | |
| 64 | |
| 65 | def parse_status(status_hex): |
| 66 | return f"{STATUS[status_hex]} ({status_hex})" if status_hex in STATUS else f"INVALID ({status_hex})" |
| 67 | |
| 68 | |
| 69 | def parse_boot_magic(trailer_magic): |
| 70 | magic = "" |
| 71 | for i in range(BOOT_MAGIC_SIZE): |
| 72 | magic += "{0:#04x} ".format(trailer_magic[i]) |
| 73 | if i == (BOOT_MAGIC_SIZE / 2 - 1): |
| 74 | magic += ("\n" + " ") |
| 75 | return magic |
| 76 | |
| 77 | |
| 78 | def print_in_frame(header_text, content): |
| 79 | sepc = " " |
| 80 | header = "#### " + header_text + sepc |
| 81 | post_header = "#" * (_LINE_LENGTH - len(header)) |
| 82 | print(header + post_header) |
| 83 | |
| 84 | print("|", sepc * (_LINE_LENGTH - 2), "|", sep="") |
| 85 | offset = (_LINE_LENGTH - len(content)) // 2 |
| 86 | pre = "|" + (sepc * (offset - 1)) |
| 87 | post = sepc * (_LINE_LENGTH - len(pre) - len(content) - 1) + "|" |
| 88 | print(pre, content, post, sep="") |
| 89 | print("|", sepc * (_LINE_LENGTH - 2), "|", sep="") |
| 90 | print("#" * _LINE_LENGTH) |
| 91 | |
| 92 | |
| 93 | def print_in_row(row_text): |
| 94 | row_text = "#### " + row_text + " " |
| 95 | fill = "#" * (_LINE_LENGTH - len(row_text)) |
| 96 | print(row_text + fill) |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 97 | |
| 98 | |
| 99 | def print_tlv_records(tlv_list): |
| 100 | indent = _LINE_LENGTH // 8 |
| 101 | for tlv in tlv_list: |
| 102 | print(" " * indent, "-" * 45) |
| 103 | tlv_type, tlv_length, tlv_data = tlv.keys() |
| 104 | |
Rustam Ismayilov | f3a5702 | 2023-11-27 21:19:43 +0100 | [diff] [blame] | 105 | if tlv[tlv_type] in TLV_TYPES: |
| 106 | print(" " * indent, "{}: {} ({})".format( |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 107 | tlv_type, TLV_TYPES[tlv[tlv_type]], hex(tlv[tlv_type]))) |
Rustam Ismayilov | f3a5702 | 2023-11-27 21:19:43 +0100 | [diff] [blame] | 108 | else: |
| 109 | print(" " * indent, "{}: {} ({})".format( |
| 110 | tlv_type, "UNKNOWN", hex(tlv[tlv_type]))) |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 111 | print(" " * indent, "{}: ".format(tlv_length), hex(tlv[tlv_length])) |
| 112 | print(" " * indent, "{}: ".format(tlv_data), end="") |
| 113 | |
| 114 | for j, data in enumerate(tlv[tlv_data]): |
Rustam Ismayilov | f3a5702 | 2023-11-27 21:19:43 +0100 | [diff] [blame] | 115 | print("{0:#04x}".format(data), end=" ") |
| 116 | if ((j + 1) % 8 == 0) and ((j + 1) != len(tlv[tlv_data])): |
| 117 | print("\n", end=" " * (indent + 7)) |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 118 | print() |
| 119 | |
| 120 | |
| 121 | def dump_imginfo(imgfile, outfile=None, silent=False): |
Rustam Ismayilov | f3a5702 | 2023-11-27 21:19:43 +0100 | [diff] [blame] | 122 | """Parse a signed image binary and print/save the available information.""" |
| 123 | trailer_magic = None |
Rustam Ismayilov | 316a139 | 2023-12-01 17:32:06 +0100 | [diff] [blame] | 124 | # set to INVALID by default |
| 125 | swap_size = 0x99 |
| 126 | swap_info = 0x99 |
| 127 | copy_done = 0x99 |
| 128 | image_ok = 0x99 |
Rustam Ismayilov | f3a5702 | 2023-11-27 21:19:43 +0100 | [diff] [blame] | 129 | trailer = {} |
| 130 | key_field_len = None |
| 131 | |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 132 | try: |
| 133 | with open(imgfile, "rb") as f: |
| 134 | b = f.read() |
| 135 | except FileNotFoundError: |
| 136 | raise click.UsageError("Image file not found ({})".format(imgfile)) |
| 137 | |
| 138 | # Parsing the image header |
| 139 | _header = struct.unpack('IIHHIIBBHI', b[:28]) |
| 140 | # Image version consists of the last 4 item ('BBHI') |
| 141 | _version = _header[-4:] |
| 142 | header = {} |
| 143 | for i, key in enumerate(HEADER_ITEMS): |
| 144 | if key == "version": |
| 145 | header[key] = "{}.{}.{}+{}".format(*_version) |
| 146 | else: |
| 147 | header[key] = _header[i] |
| 148 | |
| 149 | # Parsing the TLV area |
| 150 | tlv_area = {"tlv_hdr_prot": {}, |
Rustam Ismayilov | 316a139 | 2023-12-01 17:32:06 +0100 | [diff] [blame] | 151 | "tlvs_prot": [], |
| 152 | "tlv_hdr": {}, |
| 153 | "tlvs": []} |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 154 | tlv_off = header["hdr_size"] + header["img_size"] |
| 155 | protected_tlv_size = header["protected_tlv_size"] |
| 156 | |
| 157 | if protected_tlv_size != 0: |
| 158 | _tlv_prot_head = struct.unpack( |
Rustam Ismayilov | 316a139 | 2023-12-01 17:32:06 +0100 | [diff] [blame] | 159 | 'HH', |
| 160 | b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)]) |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 161 | tlv_area["tlv_hdr_prot"]["magic"] = _tlv_prot_head[0] |
| 162 | tlv_area["tlv_hdr_prot"]["tlv_tot"] = _tlv_prot_head[1] |
| 163 | tlv_end = tlv_off + tlv_area["tlv_hdr_prot"]["tlv_tot"] |
| 164 | tlv_off += image.TLV_INFO_SIZE |
| 165 | |
| 166 | # Iterating through the protected TLV area |
| 167 | while tlv_off < tlv_end: |
| 168 | tlv_type, tlv_len = struct.unpack( |
Rustam Ismayilov | 316a139 | 2023-12-01 17:32:06 +0100 | [diff] [blame] | 169 | 'HH', |
| 170 | b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)]) |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 171 | tlv_off += image.TLV_INFO_SIZE |
| 172 | tlv_data = b[tlv_off:(tlv_off + tlv_len)] |
| 173 | tlv_area["tlvs_prot"].append( |
| 174 | {"type": tlv_type, "len": tlv_len, "data": tlv_data}) |
| 175 | tlv_off += tlv_len |
| 176 | |
| 177 | _tlv_head = struct.unpack('HH', b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)]) |
| 178 | tlv_area["tlv_hdr"]["magic"] = _tlv_head[0] |
| 179 | tlv_area["tlv_hdr"]["tlv_tot"] = _tlv_head[1] |
| 180 | |
| 181 | tlv_end = tlv_off + tlv_area["tlv_hdr"]["tlv_tot"] |
| 182 | tlv_off += image.TLV_INFO_SIZE |
| 183 | |
| 184 | # Iterating through the TLV area |
| 185 | while tlv_off < tlv_end: |
| 186 | tlv_type, tlv_len = struct.unpack( |
Rustam Ismayilov | 316a139 | 2023-12-01 17:32:06 +0100 | [diff] [blame] | 187 | 'HH', |
| 188 | b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)]) |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 189 | tlv_off += image.TLV_INFO_SIZE |
| 190 | tlv_data = b[tlv_off:(tlv_off + tlv_len)] |
| 191 | tlv_area["tlvs"].append( |
| 192 | {"type": tlv_type, "len": tlv_len, "data": tlv_data}) |
| 193 | tlv_off += tlv_len |
| 194 | |
| 195 | _img_pad_size = len(b) - tlv_end |
| 196 | |
| 197 | if _img_pad_size: |
| 198 | # Parsing the image trailer |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 199 | trailer_off = -BOOT_MAGIC_SIZE |
| 200 | trailer_magic = b[trailer_off:] |
| 201 | trailer["magic"] = trailer_magic |
| 202 | max_align = None |
| 203 | if trailer_magic == BOOT_MAGIC: |
| 204 | # The maximum supported write alignment is the default 8 Bytes |
| 205 | max_align = 8 |
| 206 | elif trailer_magic[-len(BOOT_MAGIC_2):] == BOOT_MAGIC_2: |
| 207 | # The alignment value is encoded in the magic field |
Rustam Ismayilov | f3a5702 | 2023-11-27 21:19:43 +0100 | [diff] [blame] | 208 | max_align = int.from_bytes(trailer_magic[:2], "little") |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 209 | else: |
| 210 | # Invalid magic: the rest of the image trailer cannot be processed. |
| 211 | print("Warning: the trailer magic value is invalid!") |
| 212 | |
| 213 | if max_align is not None: |
| 214 | if max_align > BOOT_MAGIC_SIZE: |
| 215 | trailer_off -= max_align - BOOT_MAGIC_SIZE |
| 216 | # Parsing rest of the trailer fields |
| 217 | trailer_off -= max_align |
| 218 | image_ok = b[trailer_off] |
| 219 | trailer["image_ok"] = image_ok |
| 220 | |
| 221 | trailer_off -= max_align |
| 222 | copy_done = b[trailer_off] |
| 223 | trailer["copy_done"] = copy_done |
| 224 | |
| 225 | trailer_off -= max_align |
| 226 | swap_info = b[trailer_off] |
| 227 | trailer["swap_info"] = swap_info |
| 228 | |
| 229 | trailer_off -= max_align |
| 230 | swap_size = int.from_bytes(b[trailer_off:(trailer_off + 4)], |
| 231 | "little") |
| 232 | trailer["swap_size"] = swap_size |
| 233 | |
| 234 | # Encryption key 0/1 |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 235 | if ((header["flags"] & image.IMAGE_F["ENCRYPTED_AES128"]) or |
Rustam Ismayilov | 316a139 | 2023-12-01 17:32:06 +0100 | [diff] [blame] | 236 | (header["flags"] & image.IMAGE_F["ENCRYPTED_AES256"])): |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 237 | # The image is encrypted |
| 238 | # Estimated value of key_field_len is correct if |
| 239 | # BOOT_SWAP_SAVE_ENCTLV is unset |
| 240 | key_field_len = image.align_up(16, max_align) * 2 |
| 241 | |
| 242 | # Generating output yaml file |
| 243 | if outfile is not None: |
| 244 | imgdata = {"header": header, |
| 245 | "tlv_area": tlv_area, |
| 246 | "trailer": trailer} |
| 247 | with open(outfile, "w") as outf: |
| 248 | # sort_keys - from pyyaml 5.1 |
| 249 | yaml.dump(imgdata, outf, sort_keys=False) |
| 250 | |
Rustam Ismayilov | 316a139 | 2023-12-01 17:32:06 +0100 | [diff] [blame] | 251 | ############################################################################### |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 252 | |
| 253 | if silent: |
| 254 | sys.exit(0) |
| 255 | |
| 256 | print("Printing content of signed image:", os.path.basename(imgfile), "\n") |
| 257 | |
| 258 | # Image header |
Rustam Ismayilov | 316a139 | 2023-12-01 17:32:06 +0100 | [diff] [blame] | 259 | section_name = "Image header (offset: 0x0)" |
| 260 | print_in_row(section_name) |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 261 | for key, value in header.items(): |
| 262 | if key == "flags": |
| 263 | if not value: |
| 264 | flag_string = hex(value) |
| 265 | else: |
| 266 | flag_string = "" |
| 267 | for flag in image.IMAGE_F.keys(): |
Rustam Ismayilov | f3a5702 | 2023-11-27 21:19:43 +0100 | [diff] [blame] | 268 | if value & image.IMAGE_F[flag]: |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 269 | if flag_string: |
| 270 | flag_string += ("\n" + (" " * 20)) |
| 271 | flag_string += "{} ({})".format( |
Rustam Ismayilov | 316a139 | 2023-12-01 17:32:06 +0100 | [diff] [blame] | 272 | flag, hex(image.IMAGE_F[flag])) |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 273 | value = flag_string |
| 274 | |
Rustam Ismayilov | 316a139 | 2023-12-01 17:32:06 +0100 | [diff] [blame] | 275 | if not isinstance(value, str): |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 276 | value = hex(value) |
Rustam Ismayilov | f3a5702 | 2023-11-27 21:19:43 +0100 | [diff] [blame] | 277 | print(key, ":", " " * (19 - len(key)), value, sep="") |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 278 | print("#" * _LINE_LENGTH) |
| 279 | |
| 280 | # Image payload |
| 281 | _sectionoff = header["hdr_size"] |
Rustam Ismayilov | 316a139 | 2023-12-01 17:32:06 +0100 | [diff] [blame] | 282 | frame_header_text = "Payload (offset: {})".format(hex(_sectionoff)) |
| 283 | frame_content = "FW image (size: {} Bytes)".format(hex(header["img_size"])) |
| 284 | print_in_frame(frame_header_text, frame_content) |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 285 | |
| 286 | # TLV area |
| 287 | _sectionoff += header["img_size"] |
| 288 | if protected_tlv_size != 0: |
| 289 | # Protected TLV area |
Rustam Ismayilov | 316a139 | 2023-12-01 17:32:06 +0100 | [diff] [blame] | 290 | section_name = "Protected TLV area (offset: {})".format(hex(_sectionoff)) |
| 291 | print_in_row(section_name) |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 292 | print("magic: ", hex(tlv_area["tlv_hdr_prot"]["magic"])) |
| 293 | print("area size:", hex(tlv_area["tlv_hdr_prot"]["tlv_tot"])) |
| 294 | print_tlv_records(tlv_area["tlvs_prot"]) |
| 295 | print("#" * _LINE_LENGTH) |
| 296 | |
| 297 | _sectionoff += protected_tlv_size |
Rustam Ismayilov | 316a139 | 2023-12-01 17:32:06 +0100 | [diff] [blame] | 298 | section_name = "TLV area (offset: {})".format(hex(_sectionoff)) |
| 299 | print_in_row(section_name) |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 300 | print("magic: ", hex(tlv_area["tlv_hdr"]["magic"])) |
| 301 | print("area size:", hex(tlv_area["tlv_hdr"]["tlv_tot"])) |
| 302 | print_tlv_records(tlv_area["tlvs"]) |
| 303 | print("#" * _LINE_LENGTH) |
| 304 | |
| 305 | if _img_pad_size: |
| 306 | _sectionoff += tlv_area["tlv_hdr"]["tlv_tot"] |
| 307 | _erased_val = b[_sectionoff] |
Rustam Ismayilov | 316a139 | 2023-12-01 17:32:06 +0100 | [diff] [blame] | 308 | frame_header_text = "Image padding (offset: {})".format(hex(_sectionoff)) |
| 309 | frame_content = "padding ({})".format(hex(_erased_val)) |
| 310 | print_in_frame(frame_header_text, frame_content) |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 311 | |
| 312 | # Image trailer |
Rustam Ismayilov | 316a139 | 2023-12-01 17:32:06 +0100 | [diff] [blame] | 313 | section_name = "Image trailer (offset: unknown)" |
| 314 | print_in_row(section_name) |
| 315 | notice = "(Note: some fields may not be used, depending on the update strategy)\n" |
| 316 | notice = '\n'.join(notice[i:i + _LINE_LENGTH] for i in range(0, len(notice), _LINE_LENGTH)) |
| 317 | print(notice) |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 318 | print("swap status: (len: unknown)") |
Rustam Ismayilov | 316a139 | 2023-12-01 17:32:06 +0100 | [diff] [blame] | 319 | print("enc. keys: ", parse_enc(key_field_len)) |
| 320 | print("swap size: ", parse_size(hex(swap_size))) |
| 321 | print("swap_info: ", parse_status(hex(swap_info))) |
| 322 | print("copy_done: ", parse_status(hex(copy_done))) |
| 323 | print("image_ok: ", parse_status(hex(image_ok))) |
| 324 | print("boot magic: ", parse_boot_magic(trailer_magic)) |
David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame] | 325 | print() |
| 326 | |
Rustam Ismayilov | 316a139 | 2023-12-01 17:32:06 +0100 | [diff] [blame] | 327 | footer = "End of Image " |
| 328 | print_in_row(footer) |