1# Copyright 2023-2024 Arm Limited 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""" 18Parse and print header, TLV area and trailer information of a signed image. 19""" 20import os.path 21import struct 22import sys 23 24import click 25import yaml 26 27from imgtool import image 28 29HEADER_ITEMS = ("magic", "load_addr", "hdr_size", "protected_tlv_size", 30 "img_size", "flags", "version") 31TLV_TYPES = dict((value, key) for key, value in image.TLV_VALUES.items()) 32BOOT_MAGIC = bytes([ 33 0x77, 0xc2, 0x95, 0xf3, 34 0x60, 0xd2, 0xef, 0x7f, 35 0x35, 0x52, 0x50, 0x0f, 36 0x2c, 0xb6, 0x79, 0x80, ]) 37BOOT_MAGIC_2 = bytes([ 38 0x2d, 0xe1, 0x5d, 0x29, 39 0x41, 0x0b, 0x8d, 0x77, 40 0x67, 0x9c, 0x11, 0x0f, 41 0x1f, 0x8a, ]) 42BOOT_MAGIC_SIZE = len(BOOT_MAGIC) 43_LINE_LENGTH = 60 44STATUS = { 45 '0x1': 'SET', 46 '0x2': 'BAD', 47 '0x3': 'UNSET', 48 '0x4': 'ANY', 49} 50 51 52def 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 59def parse_size(size_hex): 60 if size_hex == '0xffffffff': 61 return "unknown" 62 return size_hex + " octal: " + str(int(size_hex, 0)) 63 64 65def parse_status(status_hex): 66 return f"{STATUS[status_hex]} ({status_hex})" if status_hex in STATUS else f"INVALID ({status_hex})" 67 68 69def 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 78def 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 93def print_in_row(row_text): 94 row_text = "#### " + row_text + " " 95 fill = "#" * (_LINE_LENGTH - len(row_text)) 96 print(row_text + fill) 97 98 99def 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 105 if tlv[tlv_type] in TLV_TYPES: 106 print(" " * indent, "{}: {} ({})".format( 107 tlv_type, TLV_TYPES[tlv[tlv_type]], hex(tlv[tlv_type]))) 108 else: 109 print(" " * indent, "{}: {} ({})".format( 110 tlv_type, "UNKNOWN", hex(tlv[tlv_type]))) 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]): 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)) 118 print() 119 120 121def dump_imginfo(imgfile, outfile=None, silent=False): 122 """Parse a signed image binary and print/save the available information.""" 123 trailer_magic = None 124 # set to INVALID by default 125 swap_size = 0x99 126 swap_info = 0x99 127 copy_done = 0x99 128 image_ok = 0x99 129 trailer = {} 130 key_field_len = None 131 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": {}, 151 "tlvs_prot": [], 152 "tlv_hdr": {}, 153 "tlvs": []} 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( 159 'HH', 160 b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)]) 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( 169 'HH', 170 b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)]) 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( 187 'HH', 188 b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)]) 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 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 208 max_align = int.from_bytes(trailer_magic[:2], "little") 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 235 if ((header["flags"] & image.IMAGE_F["ENCRYPTED_AES128"]) or 236 (header["flags"] & image.IMAGE_F["ENCRYPTED_AES256"])): 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 251 ############################################################################### 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 259 section_name = "Image header (offset: 0x0)" 260 print_in_row(section_name) 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(): 268 if value & image.IMAGE_F[flag]: 269 if flag_string: 270 flag_string += ("\n" + (" " * 20)) 271 flag_string += "{} ({})".format( 272 flag, hex(image.IMAGE_F[flag])) 273 value = flag_string 274 275 if not isinstance(value, str): 276 value = hex(value) 277 print(key, ":", " " * (19 - len(key)), value, sep="") 278 print("#" * _LINE_LENGTH) 279 280 # Image payload 281 _sectionoff = header["hdr_size"] 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) 285 286 # TLV area 287 _sectionoff += header["img_size"] 288 if protected_tlv_size != 0: 289 # Protected TLV area 290 section_name = "Protected TLV area (offset: {})".format(hex(_sectionoff)) 291 print_in_row(section_name) 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 298 section_name = "TLV area (offset: {})".format(hex(_sectionoff)) 299 print_in_row(section_name) 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] 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) 311 312 # Image trailer 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) 318 print("swap status: (len: unknown)") 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)) 325 print() 326 327 footer = "End of Image " 328 print_in_row(footer) 329