blob: 55446574c1eafcb6fbdbc188c5118ba06c31ec07 [file] [log] [blame]
Rustam Ismayilovf3a57022023-11-27 21:19:43 +01001# Copyright 2023-2024 Arm Limited
David Vinczeca561352023-01-27 15:39:12 +01002#
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"""
David Vinczeca561352023-01-27 15:39:12 +010020import os.path
Rustam Ismayilov316a1392023-12-01 17:32:06 +010021import struct
David Vinczeca561352023-01-27 15:39:12 +010022import sys
23
Rustam Ismayilov316a1392023-12-01 17:32:06 +010024import click
25import yaml
26
27from imgtool import image
28
David Vinczeca561352023-01-27 15:39:12 +010029HEADER_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([
Rustam Ismayilovf3a57022023-11-27 21:19:43 +010033 0x77, 0xc2, 0x95, 0xf3,
34 0x60, 0xd2, 0xef, 0x7f,
35 0x35, 0x52, 0x50, 0x0f,
36 0x2c, 0xb6, 0x79, 0x80, ])
David Vinczeca561352023-01-27 15:39:12 +010037BOOT_MAGIC_2 = bytes([
Rustam Ismayilovf3a57022023-11-27 21:19:43 +010038 0x2d, 0xe1, 0x5d, 0x29,
39 0x41, 0x0b, 0x8d, 0x77,
40 0x67, 0x9c, 0x11, 0x0f,
41 0x1f, 0x8a, ])
David Vinczeca561352023-01-27 15:39:12 +010042BOOT_MAGIC_SIZE = len(BOOT_MAGIC)
43_LINE_LENGTH = 60
Rustam Ismayilov316a1392023-12-01 17:32:06 +010044STATUS = {
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)
David Vinczeca561352023-01-27 15:39:12 +010097
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
Rustam Ismayilovf3a57022023-11-27 21:19:43 +0100105 if tlv[tlv_type] in TLV_TYPES:
106 print(" " * indent, "{}: {} ({})".format(
David Vinczeca561352023-01-27 15:39:12 +0100107 tlv_type, TLV_TYPES[tlv[tlv_type]], hex(tlv[tlv_type])))
Rustam Ismayilovf3a57022023-11-27 21:19:43 +0100108 else:
109 print(" " * indent, "{}: {} ({})".format(
110 tlv_type, "UNKNOWN", hex(tlv[tlv_type])))
David Vinczeca561352023-01-27 15:39:12 +0100111 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 Ismayilovf3a57022023-11-27 21:19:43 +0100115 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 Vinczeca561352023-01-27 15:39:12 +0100118 print()
119
120
121def dump_imginfo(imgfile, outfile=None, silent=False):
Rustam Ismayilovf3a57022023-11-27 21:19:43 +0100122 """Parse a signed image binary and print/save the available information."""
123 trailer_magic = None
Rustam Ismayilov316a1392023-12-01 17:32:06 +0100124 # set to INVALID by default
125 swap_size = 0x99
126 swap_info = 0x99
127 copy_done = 0x99
128 image_ok = 0x99
Rustam Ismayilovf3a57022023-11-27 21:19:43 +0100129 trailer = {}
130 key_field_len = None
131
David Vinczeca561352023-01-27 15:39:12 +0100132 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 Ismayilov316a1392023-12-01 17:32:06 +0100151 "tlvs_prot": [],
152 "tlv_hdr": {},
153 "tlvs": []}
David Vinczeca561352023-01-27 15:39:12 +0100154 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 Ismayilov316a1392023-12-01 17:32:06 +0100159 'HH',
160 b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)])
David Vinczeca561352023-01-27 15:39:12 +0100161 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 Ismayilov316a1392023-12-01 17:32:06 +0100169 'HH',
170 b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)])
David Vinczeca561352023-01-27 15:39:12 +0100171 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 Ismayilov316a1392023-12-01 17:32:06 +0100187 'HH',
188 b[tlv_off:(tlv_off + image.TLV_INFO_SIZE)])
David Vinczeca561352023-01-27 15:39:12 +0100189 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 Vinczeca561352023-01-27 15:39:12 +0100199 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 Ismayilovf3a57022023-11-27 21:19:43 +0100208 max_align = int.from_bytes(trailer_magic[:2], "little")
David Vinczeca561352023-01-27 15:39:12 +0100209 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 Vinczeca561352023-01-27 15:39:12 +0100235 if ((header["flags"] & image.IMAGE_F["ENCRYPTED_AES128"]) or
Rustam Ismayilov316a1392023-12-01 17:32:06 +0100236 (header["flags"] & image.IMAGE_F["ENCRYPTED_AES256"])):
David Vinczeca561352023-01-27 15:39:12 +0100237 # 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 Ismayilov316a1392023-12-01 17:32:06 +0100251 ###############################################################################
David Vinczeca561352023-01-27 15:39:12 +0100252
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 Ismayilov316a1392023-12-01 17:32:06 +0100259 section_name = "Image header (offset: 0x0)"
260 print_in_row(section_name)
David Vinczeca561352023-01-27 15:39:12 +0100261 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 Ismayilovf3a57022023-11-27 21:19:43 +0100268 if value & image.IMAGE_F[flag]:
David Vinczeca561352023-01-27 15:39:12 +0100269 if flag_string:
270 flag_string += ("\n" + (" " * 20))
271 flag_string += "{} ({})".format(
Rustam Ismayilov316a1392023-12-01 17:32:06 +0100272 flag, hex(image.IMAGE_F[flag]))
David Vinczeca561352023-01-27 15:39:12 +0100273 value = flag_string
274
Rustam Ismayilov316a1392023-12-01 17:32:06 +0100275 if not isinstance(value, str):
David Vinczeca561352023-01-27 15:39:12 +0100276 value = hex(value)
Rustam Ismayilovf3a57022023-11-27 21:19:43 +0100277 print(key, ":", " " * (19 - len(key)), value, sep="")
David Vinczeca561352023-01-27 15:39:12 +0100278 print("#" * _LINE_LENGTH)
279
280 # Image payload
281 _sectionoff = header["hdr_size"]
Rustam Ismayilov316a1392023-12-01 17:32:06 +0100282 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 Vinczeca561352023-01-27 15:39:12 +0100285
286 # TLV area
287 _sectionoff += header["img_size"]
288 if protected_tlv_size != 0:
289 # Protected TLV area
Rustam Ismayilov316a1392023-12-01 17:32:06 +0100290 section_name = "Protected TLV area (offset: {})".format(hex(_sectionoff))
291 print_in_row(section_name)
David Vinczeca561352023-01-27 15:39:12 +0100292 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 Ismayilov316a1392023-12-01 17:32:06 +0100298 section_name = "TLV area (offset: {})".format(hex(_sectionoff))
299 print_in_row(section_name)
David Vinczeca561352023-01-27 15:39:12 +0100300 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 Ismayilov316a1392023-12-01 17:32:06 +0100308 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 Vinczeca561352023-01-27 15:39:12 +0100311
312 # Image trailer
Rustam Ismayilov316a1392023-12-01 17:32:06 +0100313 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 Vinczeca561352023-01-27 15:39:12 +0100318 print("swap status: (len: unknown)")
Rustam Ismayilov316a1392023-12-01 17:32:06 +0100319 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 Vinczeca561352023-01-27 15:39:12 +0100325 print()
326
Rustam Ismayilov316a1392023-12-01 17:32:06 +0100327 footer = "End of Image "
328 print_in_row(footer)