David Vincze | ca56135 | 2023-01-27 15:39:12 +0100 | [diff] [blame^] | 1 | # Copyright 2023 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 | """ |
| 18 | Parse and print header, TLV area and trailer information of a signed image. |
| 19 | """ |
| 20 | from imgtool import image |
| 21 | import click |
| 22 | import struct |
| 23 | import yaml |
| 24 | import os.path |
| 25 | import sys |
| 26 | |
| 27 | HEADER_ITEMS = ("magic", "load_addr", "hdr_size", "protected_tlv_size", |
| 28 | "img_size", "flags", "version") |
| 29 | TLV_TYPES = dict((value, key) for key, value in image.TLV_VALUES.items()) |
| 30 | BOOT_MAGIC = bytes([ |
| 31 | 0x77, 0xc2, 0x95, 0xf3, |
| 32 | 0x60, 0xd2, 0xef, 0x7f, |
| 33 | 0x35, 0x52, 0x50, 0x0f, |
| 34 | 0x2c, 0xb6, 0x79, 0x80, ]) |
| 35 | BOOT_MAGIC_2 = bytes([ |
| 36 | 0x2d, 0xe1, 0x5d, 0x29, |
| 37 | 0x41, 0x0b, 0x8d, 0x77, |
| 38 | 0x67, 0x9c, 0x11, 0x0f, |
| 39 | 0x1f, 0x8a, ]) |
| 40 | BOOT_MAGIC_SIZE = len(BOOT_MAGIC) |
| 41 | _LINE_LENGTH = 60 |
| 42 | |
| 43 | |
| 44 | def print_tlv_records(tlv_list): |
| 45 | indent = _LINE_LENGTH // 8 |
| 46 | for tlv in tlv_list: |
| 47 | print(" " * indent, "-" * 45) |
| 48 | tlv_type, tlv_length, tlv_data = tlv.keys() |
| 49 | |
| 50 | print(" " * indent, "{}: {} ({})".format( |
| 51 | tlv_type, TLV_TYPES[tlv[tlv_type]], hex(tlv[tlv_type]))) |
| 52 | print(" " * indent, "{}: ".format(tlv_length), hex(tlv[tlv_length])) |
| 53 | print(" " * indent, "{}: ".format(tlv_data), end="") |
| 54 | |
| 55 | for j, data in enumerate(tlv[tlv_data]): |
| 56 | print("{0:#04x}".format(data), end=" ") |
| 57 | if ((j+1) % 8 == 0) and ((j+1) != len(tlv[tlv_data])): |
| 58 | print("\n", end=" " * (indent+7)) |
| 59 | print() |
| 60 | |
| 61 | |
| 62 | def dump_imginfo(imgfile, outfile=None, silent=False): |
| 63 | '''Parse a signed image binary and print/save the available information.''' |
| 64 | try: |
| 65 | with open(imgfile, "rb") as f: |
| 66 | b = f.read() |
| 67 | except FileNotFoundError: |
| 68 | raise click.UsageError("Image file not found ({})".format(imgfile)) |
| 69 | |
| 70 | # Parsing the image header |
| 71 | _header = struct.unpack('IIHHIIBBHI', b[:28]) |
| 72 | # Image version consists of the last 4 item ('BBHI') |
| 73 | _version = _header[-4:] |
| 74 | header = {} |
| 75 | for i, key in enumerate(HEADER_ITEMS): |
| 76 | if key == "version": |
| 77 | header[key] = "{}.{}.{}+{}".format(*_version) |
| 78 | else: |
| 79 | header[key] = _header[i] |
| 80 | |
| 81 | # Parsing the TLV area |
| 82 | tlv_area = {"tlv_hdr_prot": {}, |
| 83 | "tlvs_prot": [], |
| 84 | "tlv_hdr": {}, |
| 85 | "tlvs": []} |
| 86 | tlv_off = header["hdr_size"] + header["img_size"] |
| 87 | protected_tlv_size = header["protected_tlv_size"] |
| 88 | |
| 89 | if protected_tlv_size != 0: |
| 90 | _tlv_prot_head = struct.unpack( |
| 91 | 'HH', |
| 92 | b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)]) |
| 93 | tlv_area["tlv_hdr_prot"]["magic"] = _tlv_prot_head[0] |
| 94 | tlv_area["tlv_hdr_prot"]["tlv_tot"] = _tlv_prot_head[1] |
| 95 | tlv_end = tlv_off + tlv_area["tlv_hdr_prot"]["tlv_tot"] |
| 96 | tlv_off += image.TLV_INFO_SIZE |
| 97 | |
| 98 | # Iterating through the protected TLV area |
| 99 | while tlv_off < tlv_end: |
| 100 | tlv_type, tlv_len = struct.unpack( |
| 101 | 'HH', |
| 102 | b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)]) |
| 103 | tlv_off += image.TLV_INFO_SIZE |
| 104 | tlv_data = b[tlv_off:(tlv_off + tlv_len)] |
| 105 | tlv_area["tlvs_prot"].append( |
| 106 | {"type": tlv_type, "len": tlv_len, "data": tlv_data}) |
| 107 | tlv_off += tlv_len |
| 108 | |
| 109 | _tlv_head = struct.unpack('HH', b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)]) |
| 110 | tlv_area["tlv_hdr"]["magic"] = _tlv_head[0] |
| 111 | tlv_area["tlv_hdr"]["tlv_tot"] = _tlv_head[1] |
| 112 | |
| 113 | tlv_end = tlv_off + tlv_area["tlv_hdr"]["tlv_tot"] |
| 114 | tlv_off += image.TLV_INFO_SIZE |
| 115 | |
| 116 | # Iterating through the TLV area |
| 117 | while tlv_off < tlv_end: |
| 118 | tlv_type, tlv_len = struct.unpack( |
| 119 | 'HH', |
| 120 | b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)]) |
| 121 | tlv_off += image.TLV_INFO_SIZE |
| 122 | tlv_data = b[tlv_off:(tlv_off + tlv_len)] |
| 123 | tlv_area["tlvs"].append( |
| 124 | {"type": tlv_type, "len": tlv_len, "data": tlv_data}) |
| 125 | tlv_off += tlv_len |
| 126 | |
| 127 | _img_pad_size = len(b) - tlv_end |
| 128 | |
| 129 | if _img_pad_size: |
| 130 | # Parsing the image trailer |
| 131 | trailer = {} |
| 132 | trailer_off = -BOOT_MAGIC_SIZE |
| 133 | trailer_magic = b[trailer_off:] |
| 134 | trailer["magic"] = trailer_magic |
| 135 | max_align = None |
| 136 | if trailer_magic == BOOT_MAGIC: |
| 137 | # The maximum supported write alignment is the default 8 Bytes |
| 138 | max_align = 8 |
| 139 | elif trailer_magic[-len(BOOT_MAGIC_2):] == BOOT_MAGIC_2: |
| 140 | # The alignment value is encoded in the magic field |
| 141 | max_align = int(trailer_magic[:2], 0) |
| 142 | else: |
| 143 | # Invalid magic: the rest of the image trailer cannot be processed. |
| 144 | print("Warning: the trailer magic value is invalid!") |
| 145 | |
| 146 | if max_align is not None: |
| 147 | if max_align > BOOT_MAGIC_SIZE: |
| 148 | trailer_off -= max_align - BOOT_MAGIC_SIZE |
| 149 | # Parsing rest of the trailer fields |
| 150 | trailer_off -= max_align |
| 151 | image_ok = b[trailer_off] |
| 152 | trailer["image_ok"] = image_ok |
| 153 | |
| 154 | trailer_off -= max_align |
| 155 | copy_done = b[trailer_off] |
| 156 | trailer["copy_done"] = copy_done |
| 157 | |
| 158 | trailer_off -= max_align |
| 159 | swap_info = b[trailer_off] |
| 160 | trailer["swap_info"] = swap_info |
| 161 | |
| 162 | trailer_off -= max_align |
| 163 | swap_size = int.from_bytes(b[trailer_off:(trailer_off + 4)], |
| 164 | "little") |
| 165 | trailer["swap_size"] = swap_size |
| 166 | |
| 167 | # Encryption key 0/1 |
| 168 | key_field_len = None |
| 169 | if ((header["flags"] & image.IMAGE_F["ENCRYPTED_AES128"]) or |
| 170 | (header["flags"] & image.IMAGE_F["ENCRYPTED_AES256"])): |
| 171 | # The image is encrypted |
| 172 | # Estimated value of key_field_len is correct if |
| 173 | # BOOT_SWAP_SAVE_ENCTLV is unset |
| 174 | key_field_len = image.align_up(16, max_align) * 2 |
| 175 | |
| 176 | # Generating output yaml file |
| 177 | if outfile is not None: |
| 178 | imgdata = {"header": header, |
| 179 | "tlv_area": tlv_area, |
| 180 | "trailer": trailer} |
| 181 | with open(outfile, "w") as outf: |
| 182 | # sort_keys - from pyyaml 5.1 |
| 183 | yaml.dump(imgdata, outf, sort_keys=False) |
| 184 | |
| 185 | ############################################################################### |
| 186 | |
| 187 | if silent: |
| 188 | sys.exit(0) |
| 189 | |
| 190 | print("Printing content of signed image:", os.path.basename(imgfile), "\n") |
| 191 | |
| 192 | # Image header |
| 193 | str1 = "#### Image header (offset: 0x0) " |
| 194 | str2 = "#" * (_LINE_LENGTH - len(str1)) |
| 195 | print(str1 + str2) |
| 196 | for key, value in header.items(): |
| 197 | if key == "flags": |
| 198 | if not value: |
| 199 | flag_string = hex(value) |
| 200 | else: |
| 201 | flag_string = "" |
| 202 | for flag in image.IMAGE_F.keys(): |
| 203 | if (value & image.IMAGE_F[flag]): |
| 204 | if flag_string: |
| 205 | flag_string += ("\n" + (" " * 20)) |
| 206 | flag_string += "{} ({})".format( |
| 207 | flag, hex(image.IMAGE_F[flag])) |
| 208 | value = flag_string |
| 209 | |
| 210 | if type(value) != str: |
| 211 | value = hex(value) |
| 212 | print(key, ":", " " * (19-len(key)), value, sep="") |
| 213 | print("#" * _LINE_LENGTH) |
| 214 | |
| 215 | # Image payload |
| 216 | _sectionoff = header["hdr_size"] |
| 217 | sepc = " " |
| 218 | str1 = "#### Payload (offset: {}) ".format(hex(_sectionoff)) |
| 219 | str2 = "#" * (_LINE_LENGTH - len(str1)) |
| 220 | print(str1 + str2) |
| 221 | print("|", sepc * (_LINE_LENGTH - 2), "|", sep="") |
| 222 | str1 = "FW image (size: {} Bytes)".format(hex(header["img_size"])) |
| 223 | numc = (_LINE_LENGTH - len(str1)) // 2 |
| 224 | str2 = "|" + (sepc * (numc - 1)) |
| 225 | str3 = sepc * (_LINE_LENGTH - len(str2) - len(str1) - 1) + "|" |
| 226 | print(str2, str1, str3, sep="") |
| 227 | print("|", sepc * (_LINE_LENGTH - 2), "|", sep="") |
| 228 | print("#" * _LINE_LENGTH) |
| 229 | |
| 230 | # TLV area |
| 231 | _sectionoff += header["img_size"] |
| 232 | if protected_tlv_size != 0: |
| 233 | # Protected TLV area |
| 234 | str1 = "#### Protected TLV area (offset: {}) ".format(hex(_sectionoff)) |
| 235 | str2 = "#" * (_LINE_LENGTH - len(str1)) |
| 236 | print(str1 + str2) |
| 237 | print("magic: ", hex(tlv_area["tlv_hdr_prot"]["magic"])) |
| 238 | print("area size:", hex(tlv_area["tlv_hdr_prot"]["tlv_tot"])) |
| 239 | print_tlv_records(tlv_area["tlvs_prot"]) |
| 240 | print("#" * _LINE_LENGTH) |
| 241 | |
| 242 | _sectionoff += protected_tlv_size |
| 243 | str1 = "#### TLV area (offset: {}) ".format(hex(_sectionoff)) |
| 244 | str2 = "#" * (_LINE_LENGTH - len(str1)) |
| 245 | print(str1 + str2) |
| 246 | print("magic: ", hex(tlv_area["tlv_hdr"]["magic"])) |
| 247 | print("area size:", hex(tlv_area["tlv_hdr"]["tlv_tot"])) |
| 248 | print_tlv_records(tlv_area["tlvs"]) |
| 249 | print("#" * _LINE_LENGTH) |
| 250 | |
| 251 | if _img_pad_size: |
| 252 | _sectionoff += tlv_area["tlv_hdr"]["tlv_tot"] |
| 253 | _erased_val = b[_sectionoff] |
| 254 | str1 = "#### Image padding (offset: {}) ".format(hex(_sectionoff)) |
| 255 | str2 = "#" * (_LINE_LENGTH - len(str1)) |
| 256 | print(str1 + str2) |
| 257 | print("|", sepc * (_LINE_LENGTH - 2), "|", sep="") |
| 258 | str1 = "padding ({})".format(hex(_erased_val)) |
| 259 | numc = (_LINE_LENGTH - len(str1)) // 2 |
| 260 | str2 = "|" + (sepc * (numc - 1)) |
| 261 | str3 = sepc * (_LINE_LENGTH - len(str2) - len(str1) - 1) + "|" |
| 262 | print(str2, str1, str3, sep="") |
| 263 | print("|", sepc * (_LINE_LENGTH - 2), "|", sep="") |
| 264 | print("#" * _LINE_LENGTH) |
| 265 | |
| 266 | # Image trailer |
| 267 | str1 = "#### Image trailer (offset: unknown) " |
| 268 | str2 = "#" * (_LINE_LENGTH - len(str1)) |
| 269 | print(str1 + str2) |
| 270 | print("(Note: some field may not be used, \n" |
| 271 | " depending on the update strategy)\n") |
| 272 | |
| 273 | print("swap status: (len: unknown)") |
| 274 | if key_field_len is not None: |
| 275 | print("enc. keys: (len: {}, if BOOT_SWAP_SAVE_ENCTLV is unset)" |
| 276 | .format(hex(key_field_len))) |
| 277 | print("swap size: ", hex(swap_size)) |
| 278 | print("swap_info: ", hex(swap_info)) |
| 279 | print("copy_done: ", hex(copy_done)) |
| 280 | print("image_ok: ", hex(image_ok)) |
| 281 | print("boot magic: ", end="") |
| 282 | for i in range(BOOT_MAGIC_SIZE): |
| 283 | print("{0:#04x}".format(trailer_magic[i]), end=" ") |
| 284 | if (i == (BOOT_MAGIC_SIZE/2 - 1)): |
| 285 | print("\n", end=" ") |
| 286 | print() |
| 287 | |
| 288 | str1 = "#### End of Image " |
| 289 | str2 = "#" * (_LINE_LENGTH - len(str1)) |
| 290 | print(str1 + str2) |