blob: 566a47e00fc4b4a37fd63cda9e0686f5497c3e89 [file] [log] [blame]
Carles Cufi37d052f2018-01-30 16:40:10 +01001# Copyright 2018 Nordic Semiconductor ASA
David Vinczeb2a1a482020-09-18 11:54:30 +02002# Copyright 2017-2020 Linaro Limited
Rustam Ismayilov36f8bf32023-11-27 21:44:54 +01003# Copyright 2019-2024 Arm Limited
David Brown1314bf32017-12-20 11:10:55 -07004#
David Brown79c4fcf2021-01-26 15:04:05 -07005# SPDX-License-Identifier: Apache-2.0
6#
David Brown1314bf32017-12-20 11:10:55 -07007# Licensed under the Apache License, Version 2.0 (the "License");
8# you may not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS,
15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18
David Brown23f91ad2017-05-16 11:38:17 -060019"""
20Image signing and management.
21"""
22
Mateusz Michalek63fa7e42024-08-09 12:16:40 +020023from . import version as versmod
24from .boot_record import create_sw_component_data
25import click
26import copy
27from enum import Enum
28import array
29from intelhex import IntelHex
David Brown23f91ad2017-05-16 11:38:17 -060030import hashlib
Mateusz Michalek63fa7e42024-08-09 12:16:40 +020031import array
Carles Cufi37d052f2018-01-30 16:40:10 +010032import os.path
Rustam Ismayilov36f8bf32023-11-27 21:44:54 +010033import struct
34from enum import Enum
35
36import click
37from cryptography.exceptions import InvalidSignature
38from cryptography.hazmat.backends import default_backend
39from cryptography.hazmat.primitives import hashes, hmac
Fabio Utzig7a3b2602019-10-22 09:56:44 -030040from cryptography.hazmat.primitives.asymmetric import ec, padding
Fabio Utzig960b4c52020-04-02 13:07:12 -030041from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
Fabio Utzig06b77b82018-08-23 16:01:16 -030042from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
Fabio Utzig7a3b2602019-10-22 09:56:44 -030043from cryptography.hazmat.primitives.kdf.hkdf import HKDF
44from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
Rustam Ismayilov36f8bf32023-11-27 21:44:54 +010045from intelhex import IntelHex
46
47from . import version as versmod, keys
48from .boot_record import create_sw_component_data
49from .keys import rsa, ecdsa, x25519
David Brown23f91ad2017-05-16 11:38:17 -060050
Dominik Ermelc894d042024-08-22 14:58:15 +000051from collections import namedtuple
52
David Brown72e7a512017-09-01 11:08:23 -060053IMAGE_MAGIC = 0x96f3b83d
David Brown23f91ad2017-05-16 11:38:17 -060054IMAGE_HEADER_SIZE = 32
Carles Cufi37d052f2018-01-30 16:40:10 +010055BIN_EXT = "bin"
56INTEL_HEX_EXT = "hex"
Fabio Utzig519285f2018-06-04 11:11:53 -030057DEFAULT_MAX_SECTORS = 128
Kristine Jassmann73c38c62021-02-03 16:56:14 +000058DEFAULT_MAX_ALIGN = 8
David Vinczeda8c9192019-03-26 17:17:41 +010059DEP_IMAGES_KEY = "images"
60DEP_VERSIONS_KEY = "versions"
David Vincze71b8f982020-03-17 19:08:12 +010061MAX_SW_TYPE_LENGTH = 12 # Bytes
David Brown23f91ad2017-05-16 11:38:17 -060062
63# Image header flags.
64IMAGE_F = {
65 'PIC': 0x0000001,
Salome Thirot0f641972021-05-14 11:19:55 +010066 'ENCRYPTED_AES128': 0x0000004,
67 'ENCRYPTED_AES256': 0x0000008,
Fabio Utzig06b77b82018-08-23 16:01:16 -030068 'NON_BOOTABLE': 0x0000010,
David Vincze1e0c5442020-04-07 14:12:33 +020069 'RAM_LOAD': 0x0000020,
Dominik Ermel50820b12020-12-14 13:16:46 +000070 'ROM_FIXED': 0x0000100,
Mateusz Michalek63fa7e42024-08-09 12:16:40 +020071 'COMPRESSED_LZMA1': 0x0000200,
72 'COMPRESSED_LZMA2': 0x0000400,
Mateusz Michalekd69933c2024-10-04 13:36:52 +020073 'COMPRESSED_ARM_THUMB': 0x0000800,
Fabio Utzig06b77b82018-08-23 16:01:16 -030074}
David Brown23f91ad2017-05-16 11:38:17 -060075
76TLV_VALUES = {
David Brown43cda332017-09-01 09:53:23 -060077 'KEYHASH': 0x01,
David Vinczedde178d2020-03-26 20:06:01 +010078 'PUBKEY': 0x02,
David Brown27648b82017-08-31 10:40:29 -060079 'SHA256': 0x10,
Roland Mikhel57041742023-02-03 14:43:13 +010080 'SHA384': 0x11,
Dominik Ermelc894d042024-08-22 14:58:15 +000081 'SHA512': 0x12,
David Brown27648b82017-08-31 10:40:29 -060082 'RSA2048': 0x20,
David Vincze4395b802023-04-27 16:11:49 +020083 'ECDSASIG': 0x22,
Fabio Utzig19fd79a2019-05-08 18:20:39 -030084 'RSA3072': 0x23,
Fabio Utzig8101d1f2019-05-09 15:03:22 -030085 'ED25519': 0x24,
Dominik Ermel30bcd462024-08-02 14:48:15 +000086 'SIG_PURE': 0x25,
Fabio Utzig06b77b82018-08-23 16:01:16 -030087 'ENCRSA2048': 0x30,
Salome Thirot0f641972021-05-14 11:19:55 +010088 'ENCKW': 0x31,
Fabio Utzig7a3b2602019-10-22 09:56:44 -030089 'ENCEC256': 0x32,
Fabio Utzig960b4c52020-04-02 13:07:12 -030090 'ENCX25519': 0x33,
David Vincze1a7a6902020-02-18 15:05:16 +010091 'DEPENDENCY': 0x40,
92 'SEC_CNT': 0x50,
David Vincze71b8f982020-03-17 19:08:12 +010093 'BOOT_RECORD': 0x60,
Mateusz Michalek63fa7e42024-08-09 12:16:40 +020094 'DECOMP_SIZE': 0x70,
95 'DECOMP_SHA': 0x71,
96 'DECOMP_SIGNATURE': 0x72,
Mateusz Michalek1da18e92025-01-02 14:48:28 +010097 'COMP_DEC_SIZE' : 0x73,
Fabio Utzig06b77b82018-08-23 16:01:16 -030098}
David Brown23f91ad2017-05-16 11:38:17 -060099
Fabio Utzig4a5477a2019-05-27 15:45:08 -0300100TLV_SIZE = 4
David Brownf5b33d82017-09-01 10:58:27 -0600101TLV_INFO_SIZE = 4
102TLV_INFO_MAGIC = 0x6907
Fabio Utzig510fddb2019-09-12 12:15:36 -0300103TLV_PROT_INFO_MAGIC = 0x6908
David Brown23f91ad2017-05-16 11:38:17 -0600104
Piotr Dymacze026c362023-02-24 12:09:33 +0100105TLV_VENDOR_RES_MIN = 0x00a0
106TLV_VENDOR_RES_MAX = 0xfffe
107
Mark Schultea66c6872018-09-26 17:24:40 -0700108STRUCT_ENDIAN_DICT = {
109 'little': '<',
110 'big': '>'
111}
112
Fabio Utzig4a5477a2019-05-27 15:45:08 -0300113VerifyResult = Enum('VerifyResult',
Rustam Ismayilov36f8bf32023-11-27 21:44:54 +0100114 ['OK', 'INVALID_MAGIC', 'INVALID_TLV_INFO_MAGIC', 'INVALID_HASH', 'INVALID_SIGNATURE',
115 'KEY_MISMATCH'])
Fabio Utzig4a5477a2019-05-27 15:45:08 -0300116
Roland Mikhel57041742023-02-03 14:43:13 +0100117
Gustavo Henrique Niheicf120ba2021-11-22 18:44:11 -0300118def align_up(num, align):
119 assert (align & (align - 1) == 0) and align != 0
120 return (num + (align - 1)) & ~(align - 1)
Fabio Utzig4a5477a2019-05-27 15:45:08 -0300121
Roland Mikhel57041742023-02-03 14:43:13 +0100122
David Brown23f91ad2017-05-16 11:38:17 -0600123class TLV():
Fabio Utzig510fddb2019-09-12 12:15:36 -0300124 def __init__(self, endian, magic=TLV_INFO_MAGIC):
125 self.magic = magic
David Brown23f91ad2017-05-16 11:38:17 -0600126 self.buf = bytearray()
Mark Schultea66c6872018-09-26 17:24:40 -0700127 self.endian = endian
David Brown23f91ad2017-05-16 11:38:17 -0600128
Fabio Utzig510fddb2019-09-12 12:15:36 -0300129 def __len__(self):
130 return TLV_INFO_SIZE + len(self.buf)
131
David Brown23f91ad2017-05-16 11:38:17 -0600132 def add(self, kind, payload):
Fabio Utzig510fddb2019-09-12 12:15:36 -0300133 """
134 Add a TLV record. Kind should be a string found in TLV_VALUES above.
135 """
Mark Schultea66c6872018-09-26 17:24:40 -0700136 e = STRUCT_ENDIAN_DICT[self.endian]
Ihor Slabkyy24d93732020-03-10 15:33:57 +0200137 if isinstance(kind, int):
Piotr Dymacze026c362023-02-24 12:09:33 +0100138 if not TLV_VENDOR_RES_MIN <= kind <= TLV_VENDOR_RES_MAX:
139 msg = "Invalid custom TLV type value '0x{:04x}', allowed " \
140 "value should be between 0x{:04x} and 0x{:04x}".format(
Roland Mikhel57041742023-02-03 14:43:13 +0100141 kind, TLV_VENDOR_RES_MIN, TLV_VENDOR_RES_MAX)
Piotr Dymacze026c362023-02-24 12:09:33 +0100142 raise click.UsageError(msg)
143 buf = struct.pack(e + 'HH', kind, len(payload))
Ihor Slabkyy24d93732020-03-10 15:33:57 +0200144 else:
145 buf = struct.pack(e + 'BBH', TLV_VALUES[kind], 0, len(payload))
David Brown23f91ad2017-05-16 11:38:17 -0600146 self.buf += buf
147 self.buf += payload
148
149 def get(self):
Fabio Utzig510fddb2019-09-12 12:15:36 -0300150 if len(self.buf) == 0:
151 return bytes()
Mark Schultea66c6872018-09-26 17:24:40 -0700152 e = STRUCT_ENDIAN_DICT[self.endian]
Fabio Utzig510fddb2019-09-12 12:15:36 -0300153 header = struct.pack(e + 'HH', self.magic, len(self))
David Brownf5b33d82017-09-01 10:58:27 -0600154 return header + bytes(self.buf)
David Brown23f91ad2017-05-16 11:38:17 -0600155
Fabio Utzig7c00acd2019-01-07 09:54:20 -0200156
Dominik Ermelc894d042024-08-22 14:58:15 +0000157SHAAndAlgT = namedtuple('SHAAndAlgT', ['sha', 'alg'])
158
159TLV_SHA_TO_SHA_AND_ALG = {
160 TLV_VALUES['SHA256'] : SHAAndAlgT('256', hashlib.sha256),
161 TLV_VALUES['SHA384'] : SHAAndAlgT('384', hashlib.sha384),
162 TLV_VALUES['SHA512'] : SHAAndAlgT('512', hashlib.sha512),
163}
164
165
166USER_SHA_TO_ALG_AND_TLV = {
167 'auto' : (hashlib.sha256, 'SHA256'),
168 '256' : (hashlib.sha256, 'SHA256'),
169 '384' : (hashlib.sha384, 'SHA384'),
170 '512' : (hashlib.sha512, 'SHA512')
171}
172
173
174def is_sha_tlv(tlv):
175 return tlv in TLV_SHA_TO_SHA_AND_ALG.keys()
176
177
178def tlv_sha_to_sha(tlv):
179 return TLV_SHA_TO_SHA_AND_ALG[tlv].sha
180
181
182# Auto selecting hash algorithm for type(key)
183ALLOWED_KEY_SHA = {
184 keys.ECDSA384P1 : ['384'],
185 keys.ECDSA384P1Public : ['384'],
186 keys.ECDSA256P1 : ['256'],
187 keys.RSA : ['256'],
Georgij Cernysiovf74b77c2024-10-28 13:53:12 +0100188 keys.RSAPublic : ['256'],
Dominik Ermelc894d042024-08-22 14:58:15 +0000189 # This two are set to 256 for compatibility, the right would be 512
190 keys.Ed25519 : ['256', '512'],
191 keys.X25519 : ['256', '512']
192}
193
Dominik Ermel1c04eac2024-09-12 19:37:40 +0000194ALLOWED_PURE_KEY_SHA = {
195 keys.Ed25519 : ['512']
196}
197
198ALLOWED_PURE_SIG_TLVS = [
199 TLV_VALUES['ED25519']
200]
201
202def key_and_user_sha_to_alg_and_tlv(key, user_sha, is_pure = False):
Dominik Ermelc894d042024-08-22 14:58:15 +0000203 """Matches key and user requested sha to sha alogrithm and TLV name.
204
205 The returned tuple will contain hash functions and TVL name.
206 The function is designed to succeed or completely fail execution,
207 as providing incorrect pair here basically prevents doing
208 any more work.
209 """
210 if key is None:
211 # If key is none, we allow whatever user has selected for sha
212 return USER_SHA_TO_ALG_AND_TLV[user_sha]
213
214 # If key is not None, then we have to filter hash to only allowed
215 allowed = None
Dominik Ermel1c04eac2024-09-12 19:37:40 +0000216 allowed_key_ssh = ALLOWED_PURE_KEY_SHA if is_pure else ALLOWED_KEY_SHA
Dominik Ermelc894d042024-08-22 14:58:15 +0000217 try:
Dominik Ermel1c04eac2024-09-12 19:37:40 +0000218 allowed = allowed_key_ssh[type(key)]
219
Dominik Ermelc894d042024-08-22 14:58:15 +0000220 except KeyError:
221 raise click.UsageError("Colud not find allowed hash algorithms for {}"
222 .format(type(key)))
Dominik Ermel1c04eac2024-09-12 19:37:40 +0000223
224 # Pure enforces auto, and user selection is ignored
225 if user_sha == 'auto' or is_pure:
Dominik Ermelc894d042024-08-22 14:58:15 +0000226 return USER_SHA_TO_ALG_AND_TLV[allowed[0]]
227
228 if user_sha in allowed:
229 return USER_SHA_TO_ALG_AND_TLV[user_sha]
230
231 raise click.UsageError("Key {} can not be used with --sha {}; allowed sha are one of {}"
232 .format(key.sig_type(), user_sha, allowed))
233
234
Rustam Ismayilov36f8bf32023-11-27 21:44:54 +0100235def get_digest(tlv_type, hash_region):
Dominik Ermelc894d042024-08-22 14:58:15 +0000236 sha = TLV_SHA_TO_SHA_AND_ALG[tlv_type].alg()
Rustam Ismayilov36f8bf32023-11-27 21:44:54 +0100237
238 sha.update(hash_region)
239 return sha.digest()
240
241
242def tlv_matches_key_type(tlv_type, key):
243 """Check if provided key matches to TLV record in the image"""
Dominik Ermelc894d042024-08-22 14:58:15 +0000244 try:
245 # We do not need the result here, and the key_and_user_sha_to_alg_and_tlv
246 # will either succeed finding match or rise exception, so on success we
247 # return True, on exception we return False.
248 _, _ = key_and_user_sha_to_alg_and_tlv(key, tlv_sha_to_sha(tlv_type))
249 return True
250 except:
251 pass
252
253 return False
Rustam Ismayilov36f8bf32023-11-27 21:44:54 +0100254
255
256class Image:
Carles Cufi37d052f2018-01-30 16:40:10 +0100257
Fabio Utzig7c00acd2019-01-07 09:54:20 -0200258 def __init__(self, version=None, header_size=IMAGE_HEADER_SIZE,
Henrik Brix Andersen0ce958e2020-03-11 14:04:11 +0100259 pad_header=False, pad=False, confirm=False, align=1,
260 slot_size=0, max_sectors=DEFAULT_MAX_SECTORS,
261 overwrite_only=False, endian="little", load_addr=0,
Dominik Ermel50820b12020-12-14 13:16:46 +0000262 rom_fixed=None, erased_val=None, save_enctlv=False,
Mateusz Wielgosdc030552024-02-26 15:02:45 -0600263 security_counter=None, max_align=None,
264 non_bootable=False):
Dominik Ermel50820b12020-12-14 13:16:46 +0000265
266 if load_addr and rom_fixed:
267 raise click.UsageError("Can not set rom_fixed and load_addr at the same time")
268
Mateusz Michalek63fa7e42024-08-09 12:16:40 +0200269 self.image_hash = None
270 self.image_size = None
271 self.signature = None
David Brown23f91ad2017-05-16 11:38:17 -0600272 self.version = version or versmod.decode_version("0")
Fabio Utzig7c00acd2019-01-07 09:54:20 -0200273 self.header_size = header_size
274 self.pad_header = pad_header
David Brown23f91ad2017-05-16 11:38:17 -0600275 self.pad = pad
Henrik Brix Andersen0ce958e2020-03-11 14:04:11 +0100276 self.confirm = confirm
Fabio Utzig263d4392018-06-05 10:37:35 -0300277 self.align = align
278 self.slot_size = slot_size
279 self.max_sectors = max_sectors
Fabio Utzigdcf0c9b2018-06-11 12:27:49 -0700280 self.overwrite_only = overwrite_only
Mark Schultea66c6872018-09-26 17:24:40 -0700281 self.endian = endian
Fabio Utzig7c00acd2019-01-07 09:54:20 -0200282 self.base_addr = None
Håkon Øye Amundsendf8c8912019-08-26 12:15:28 +0000283 self.load_addr = 0 if load_addr is None else load_addr
Dominik Ermel50820b12020-12-14 13:16:46 +0000284 self.rom_fixed = rom_fixed
Fabio Utzigcb080732020-02-07 12:01:39 -0300285 self.erased_val = 0xff if erased_val is None else int(erased_val, 0)
Fabio Utzig7c00acd2019-01-07 09:54:20 -0200286 self.payload = []
Mateusz Michalek63fa7e42024-08-09 12:16:40 +0200287 self.infile_data = []
Fabio Utzig649d80f2019-09-12 10:26:23 -0300288 self.enckey = None
Fabio Utzig9a492d52020-01-15 11:31:52 -0300289 self.save_enctlv = save_enctlv
290 self.enctlv_len = 0
Piotr Mienkowskib6d5cf32022-01-31 01:01:11 +0100291 self.max_align = max(DEFAULT_MAX_ALIGN, align) if max_align is None else int(max_align)
Mateusz Wielgosdc030552024-02-26 15:02:45 -0600292 self.non_bootable = non_bootable
David Brown23f91ad2017-05-16 11:38:17 -0600293
Gustavo Henrique Niheicf120ba2021-11-22 18:44:11 -0300294 if self.max_align == DEFAULT_MAX_ALIGN:
295 self.boot_magic = bytes([
296 0x77, 0xc2, 0x95, 0xf3,
297 0x60, 0xd2, 0xef, 0x7f,
298 0x35, 0x52, 0x50, 0x0f,
299 0x2c, 0xb6, 0x79, 0x80, ])
300 else:
Raphael Dupont16f3de52023-03-16 09:02:11 +0100301 lsb = self.max_align & 0x00ff
302 msb = (self.max_align & 0xff00) >> 8
303 align = bytes([msb, lsb]) if self.endian == "big" else bytes([lsb, msb])
304 self.boot_magic = align + bytes([0x2d, 0xe1,
Rustam Ismayilov36f8bf32023-11-27 21:44:54 +0100305 0x5d, 0x29, 0x41, 0x0b,
306 0x8d, 0x77, 0x67, 0x9c,
307 0x11, 0x0f, 0x1f, 0x8a, ])
Gustavo Henrique Niheicf120ba2021-11-22 18:44:11 -0300308
David Vincze1a7a6902020-02-18 15:05:16 +0100309 if security_counter == 'auto':
310 # Security counter has not been explicitly provided,
311 # generate it from the version number
312 self.security_counter = ((self.version.major << 24)
313 + (self.version.minor << 16)
314 + self.version.revision)
315 else:
316 self.security_counter = security_counter
317
David Brown23f91ad2017-05-16 11:38:17 -0600318 def __repr__(self):
David Vincze1a7a6902020-02-18 15:05:16 +0100319 return "<Image version={}, header_size={}, security_counter={}, \
320 base_addr={}, load_addr={}, align={}, slot_size={}, \
321 max_sectors={}, overwrite_only={}, endian={} format={}, \
322 payloadlen=0x{:x}>".format(
Fabio Utzig263d4392018-06-05 10:37:35 -0300323 self.version,
324 self.header_size,
David Vincze1a7a6902020-02-18 15:05:16 +0100325 self.security_counter,
Fabio Utzig263d4392018-06-05 10:37:35 -0300326 self.base_addr if self.base_addr is not None else "N/A",
Håkon Øye Amundsendf8c8912019-08-26 12:15:28 +0000327 self.load_addr,
Fabio Utzig263d4392018-06-05 10:37:35 -0300328 self.align,
329 self.slot_size,
330 self.max_sectors,
Fabio Utzigdcf0c9b2018-06-11 12:27:49 -0700331 self.overwrite_only,
Mark Schultea66c6872018-09-26 17:24:40 -0700332 self.endian,
Fabio Utzig263d4392018-06-05 10:37:35 -0300333 self.__class__.__name__,
334 len(self.payload))
David Brown23f91ad2017-05-16 11:38:17 -0600335
Fabio Utzig7c00acd2019-01-07 09:54:20 -0200336 def load(self, path):
337 """Load an image from a given file"""
338 ext = os.path.splitext(path)[1][1:].lower()
Fabio Utzig1f508922020-01-15 11:37:51 -0300339 try:
340 if ext == INTEL_HEX_EXT:
341 ih = IntelHex(path)
Mateusz Michalek63fa7e42024-08-09 12:16:40 +0200342 self.infile_data = ih.tobinarray()
343 self.payload = copy.copy(self.infile_data)
Fabio Utzig1f508922020-01-15 11:37:51 -0300344 self.base_addr = ih.minaddr()
345 else:
346 with open(path, 'rb') as f:
Mateusz Michalek63fa7e42024-08-09 12:16:40 +0200347 self.infile_data = f.read()
348 self.payload = copy.copy(self.infile_data)
Fabio Utzig1f508922020-01-15 11:37:51 -0300349 except FileNotFoundError:
350 raise click.UsageError("Input file not found")
Mateusz Michalek63fa7e42024-08-09 12:16:40 +0200351 self.image_size = len(self.payload)
352
353 # Add the image header if needed.
354 if self.pad_header and self.header_size > 0:
355 if self.base_addr:
356 # Adjust base_addr for new header
357 self.base_addr -= self.header_size
358 self.payload = bytes([self.erased_val] * self.header_size) + \
359 self.payload
360
361 self.check_header()
362
363 def load_compressed(self, data, compression_header):
364 """Load an image from buffer"""
365 self.payload = compression_header + data
366 self.image_size = len(self.payload)
Fabio Utzig7c00acd2019-01-07 09:54:20 -0200367
368 # Add the image header if needed.
369 if self.pad_header and self.header_size > 0:
370 if self.base_addr:
371 # Adjust base_addr for new header
372 self.base_addr -= self.header_size
Fabio Utzig9117fde2019-10-17 11:11:46 -0300373 self.payload = bytes([self.erased_val] * self.header_size) + \
374 self.payload
Fabio Utzig7c00acd2019-01-07 09:54:20 -0200375
Fabio Utzig9a492d52020-01-15 11:31:52 -0300376 self.check_header()
Fabio Utzig7c00acd2019-01-07 09:54:20 -0200377
Fabio Utzigedbabcf2019-10-11 13:03:37 -0300378 def save(self, path, hex_addr=None):
Fabio Utzig7c00acd2019-01-07 09:54:20 -0200379 """Save an image from a given file"""
Fabio Utzig7c00acd2019-01-07 09:54:20 -0200380 ext = os.path.splitext(path)[1][1:].lower()
381 if ext == INTEL_HEX_EXT:
382 # input was in binary format, but HEX needs to know the base addr
Fabio Utzigedbabcf2019-10-11 13:03:37 -0300383 if self.base_addr is None and hex_addr is None:
Fabio Utzig1f508922020-01-15 11:37:51 -0300384 raise click.UsageError("No address exists in input file "
385 "neither was it provided by user")
Fabio Utzig7c00acd2019-01-07 09:54:20 -0200386 h = IntelHex()
Fabio Utzigedbabcf2019-10-11 13:03:37 -0300387 if hex_addr is not None:
388 self.base_addr = hex_addr
Fabio Utzig7c00acd2019-01-07 09:54:20 -0200389 h.frombytes(bytes=self.payload, offset=self.base_addr)
Fabio Utzigedbabcf2019-10-11 13:03:37 -0300390 if self.pad:
Fabio Utzig2269f472019-10-17 11:14:33 -0300391 trailer_size = self._trailer_size(self.align, self.max_sectors,
392 self.overwrite_only,
Fabio Utzig9a492d52020-01-15 11:31:52 -0300393 self.enckey,
394 self.save_enctlv,
395 self.enctlv_len)
Fabio Utzig2269f472019-10-17 11:14:33 -0300396 trailer_addr = (self.base_addr + self.slot_size) - trailer_size
Kristine Jassmann73c38c62021-02-03 16:56:14 +0000397 if self.confirm and not self.overwrite_only:
Roland Mikhel57041742023-02-03 14:43:13 +0100398 magic_align_size = align_up(len(self.boot_magic),
399 self.max_align)
Kristine Jassmann73c38c62021-02-03 16:56:14 +0000400 image_ok_idx = -(magic_align_size + self.max_align)
Alexander Mihajlovicf4df58f2022-08-17 15:44:53 +0200401 flag = bytearray([self.erased_val] * self.max_align)
Roland Mikhel57041742023-02-03 14:43:13 +0100402 flag[0] = 0x01 # image_ok = 0x01
403 h.puts(trailer_addr + trailer_size + image_ok_idx,
404 bytes(flag))
Wouter Cappellec028d452022-01-25 10:25:24 +0100405 h.puts(trailer_addr + (trailer_size - len(self.boot_magic)),
406 bytes(self.boot_magic))
Fabio Utzig7c00acd2019-01-07 09:54:20 -0200407 h.tofile(path, 'hex')
408 else:
Fabio Utzigedbabcf2019-10-11 13:03:37 -0300409 if self.pad:
410 self.pad_to(self.slot_size)
Fabio Utzig7c00acd2019-01-07 09:54:20 -0200411 with open(path, 'wb') as f:
412 f.write(self.payload)
413
Fabio Utzig9a492d52020-01-15 11:31:52 -0300414 def check_header(self):
Fabio Utzigf5556c32019-10-23 11:00:27 -0300415 if self.header_size > 0 and not self.pad_header:
David Brown23f91ad2017-05-16 11:38:17 -0600416 if any(v != 0 for v in self.payload[0:self.header_size]):
Fabio Utzig9a492d52020-01-15 11:31:52 -0300417 raise click.UsageError("Header padding was not requested and "
418 "image does not start with zeros")
419
420 def check_trailer(self):
Fabio Utzig263d4392018-06-05 10:37:35 -0300421 if self.slot_size > 0:
Fabio Utzigdcf0c9b2018-06-11 12:27:49 -0700422 tsize = self._trailer_size(self.align, self.max_sectors,
Fabio Utzig9a492d52020-01-15 11:31:52 -0300423 self.overwrite_only, self.enckey,
424 self.save_enctlv, self.enctlv_len)
Fabio Utzig263d4392018-06-05 10:37:35 -0300425 padding = self.slot_size - (len(self.payload) + tsize)
426 if padding < 0:
Fabio Utzig9a492d52020-01-15 11:31:52 -0300427 msg = "Image size (0x{:x}) + trailer (0x{:x}) exceeds " \
428 "requested size 0x{:x}".format(
429 len(self.payload), tsize, self.slot_size)
430 raise click.UsageError(msg)
David Brown23f91ad2017-05-16 11:38:17 -0600431
Fabio Utzig960b4c52020-04-02 13:07:12 -0300432 def ecies_hkdf(self, enckey, plainkey):
433 if isinstance(enckey, ecdsa.ECDSA256P1Public):
434 newpk = ec.generate_private_key(ec.SECP256R1(), default_backend())
435 shared = newpk.exchange(ec.ECDH(), enckey._get_public())
436 else:
437 newpk = X25519PrivateKey.generate()
438 shared = newpk.exchange(enckey._get_public())
Fabio Utzig7a3b2602019-10-22 09:56:44 -0300439 derived_key = HKDF(
440 algorithm=hashes.SHA256(), length=48, salt=None,
441 info=b'MCUBoot_ECIES_v1', backend=default_backend()).derive(shared)
442 encryptor = Cipher(algorithms.AES(derived_key[:16]),
443 modes.CTR(bytes([0] * 16)),
444 backend=default_backend()).encryptor()
445 cipherkey = encryptor.update(plainkey) + encryptor.finalize()
446 mac = hmac.HMAC(derived_key[16:], hashes.SHA256(),
447 backend=default_backend())
448 mac.update(cipherkey)
449 ciphermac = mac.finalize()
Fabio Utzig960b4c52020-04-02 13:07:12 -0300450 if isinstance(enckey, ecdsa.ECDSA256P1Public):
451 pubk = newpk.public_key().public_bytes(
452 encoding=Encoding.X962,
453 format=PublicFormat.UncompressedPoint)
454 else:
455 pubk = newpk.public_key().public_bytes(
456 encoding=Encoding.Raw,
457 format=PublicFormat.Raw)
Fabio Utzig7a3b2602019-10-22 09:56:44 -0300458 return cipherkey, ciphermac, pubk
459
David Vinczedde178d2020-03-26 20:06:01 +0100460 def create(self, key, public_key_format, enckey, dependencies=None,
Mateusz Michalek63fa7e42024-08-09 12:16:40 +0200461 sw_type=None, custom_tlvs=None, compression_tlvs=None,
462 compression_type=None, encrypt_keylen=128, clear=False,
Dominik Ermel1c04eac2024-09-12 19:37:40 +0000463 fixed_sig=None, pub_key=None, vector_to_sign=None,
Mateusz Michalek1da18e92025-01-02 14:48:28 +0100464 user_sha='auto', is_pure=False, keep_comp_size=False, dont_encrypt=False):
Fabio Utzig649d80f2019-09-12 10:26:23 -0300465 self.enckey = enckey
466
Dominik Ermelc894d042024-08-22 14:58:15 +0000467 # key decides on sha, then pub_key; of both are none default is used
468 check_key = key if key is not None else pub_key
Dominik Ermel1c04eac2024-09-12 19:37:40 +0000469 hash_algorithm, hash_tlv = key_and_user_sha_to_alg_and_tlv(check_key, user_sha, is_pure)
Dominik Ermelc894d042024-08-22 14:58:15 +0000470
David Vincze71b8f982020-03-17 19:08:12 +0100471 # Calculate the hash of the public key
472 if key is not None:
473 pub = key.get_public_bytes()
Roland Mikhel57041742023-02-03 14:43:13 +0100474 sha = hash_algorithm()
David Vincze71b8f982020-03-17 19:08:12 +0100475 sha.update(pub)
476 pubbytes = sha.digest()
Andrzej Puzdrowski160303c2022-03-15 15:41:14 +0100477 elif pub_key is not None:
Andrzej Puzdrowskidfce0be2022-03-28 09:34:15 +0200478 if hasattr(pub_key, 'sign'):
Antonio de Angelis284b8fe2022-11-15 13:54:47 +0000479 print(os.path.basename(__file__) + ": sign the payload")
Andrzej Puzdrowski160303c2022-03-15 15:41:14 +0100480 pub = pub_key.get_public_bytes()
Roland Mikhel57041742023-02-03 14:43:13 +0100481 sha = hash_algorithm()
Andrzej Puzdrowski160303c2022-03-15 15:41:14 +0100482 sha.update(pub)
483 pubbytes = sha.digest()
David Vincze71b8f982020-03-17 19:08:12 +0100484 else:
485 pubbytes = bytes(hashlib.sha256().digest_size)
486
David Vincze1a7a6902020-02-18 15:05:16 +0100487 protected_tlv_size = 0
488
489 if self.security_counter is not None:
490 # Size of the security counter TLV: header ('HH') + payload ('I')
491 # = 4 + 4 = 8 Bytes
492 protected_tlv_size += TLV_SIZE + 4
493
David Vincze71b8f982020-03-17 19:08:12 +0100494 if sw_type is not None:
495 if len(sw_type) > MAX_SW_TYPE_LENGTH:
496 msg = "'{}' is too long ({} characters) for sw_type. Its " \
497 "maximum allowed length is 12 characters.".format(
498 sw_type, len(sw_type))
499 raise click.UsageError(msg)
500
501 image_version = (str(self.version.major) + '.'
502 + str(self.version.minor) + '.'
503 + str(self.version.revision))
504
505 # The image hash is computed over the image header, the image
506 # itself and the protected TLV area. However, the boot record TLV
507 # (which is part of the protected area) should contain this hash
508 # before it is even calculated. For this reason the script fills
509 # this field with zeros and the bootloader will insert the right
510 # value later.
Roland Mikhel57041742023-02-03 14:43:13 +0100511 digest = bytes(hash_algorithm().digest_size)
David Vincze71b8f982020-03-17 19:08:12 +0100512
513 # Create CBOR encoded boot record
514 boot_record = create_sw_component_data(sw_type, image_version,
Roland Mikhel57041742023-02-03 14:43:13 +0100515 hash_tlv, digest,
David Vincze71b8f982020-03-17 19:08:12 +0100516 pubbytes)
517
518 protected_tlv_size += TLV_SIZE + len(boot_record)
519
David Vincze1a7a6902020-02-18 15:05:16 +0100520 if dependencies is not None:
521 # Size of a Dependency TLV = Header ('HH') + Payload('IBBHI')
522 # = 4 + 12 = 16 Bytes
David Vinczeda8c9192019-03-26 17:17:41 +0100523 dependencies_num = len(dependencies[DEP_IMAGES_KEY])
David Vincze1a7a6902020-02-18 15:05:16 +0100524 protected_tlv_size += (dependencies_num * 16)
525
Mateusz Michalek1da18e92025-01-02 14:48:28 +0100526 if keep_comp_size:
527 compression_tlvs["COMP_DEC_SIZE"] = struct.pack(
528 self.get_struct_endian() + 'L', self.image_size)
Mateusz Michalek63fa7e42024-08-09 12:16:40 +0200529 if compression_tlvs is not None:
530 for value in compression_tlvs.values():
531 protected_tlv_size += TLV_SIZE + len(value)
David Vinczeb2a1a482020-09-18 11:54:30 +0200532 if custom_tlvs is not None:
Ihor Slabkyy24d93732020-03-10 15:33:57 +0200533 for value in custom_tlvs.values():
534 protected_tlv_size += TLV_SIZE + len(value)
535
David Vincze1a7a6902020-02-18 15:05:16 +0100536 if protected_tlv_size != 0:
537 # Add the size of the TLV info header
538 protected_tlv_size += TLV_INFO_SIZE
David Vinczeda8c9192019-03-26 17:17:41 +0100539
Ihor Slabkyy24d93732020-03-10 15:33:57 +0200540 # At this point the image is already on the payload
541 #
542 # This adds the padding if image is not aligned to the 16 Bytes
543 # in encrypted mode
Mateusz Michalek1da18e92025-01-02 14:48:28 +0100544 if self.enckey is not None and dont_encrypt is False:
Ihor Slabkyy24d93732020-03-10 15:33:57 +0200545 pad_len = len(self.payload) % 16
546 if pad_len > 0:
Fabio Utzigd62631a2021-02-04 19:45:27 -0300547 pad = bytes(16 - pad_len)
548 if isinstance(self.payload, bytes):
549 self.payload += pad
550 else:
551 self.payload.extend(pad)
Ihor Slabkyy24d93732020-03-10 15:33:57 +0200552
Mateusz Michalek63fa7e42024-08-09 12:16:40 +0200553 compression_flags = 0x0
554 if compression_tlvs is not None:
Mateusz Michalekd69933c2024-10-04 13:36:52 +0200555 if compression_type in ["lzma2", "lzma2armthumb"]:
Mateusz Michalek63fa7e42024-08-09 12:16:40 +0200556 compression_flags = IMAGE_F['COMPRESSED_LZMA2']
Mateusz Michalekd69933c2024-10-04 13:36:52 +0200557 if compression_type == "lzma2armthumb":
558 compression_flags |= IMAGE_F['COMPRESSED_ARM_THUMB']
Ihor Slabkyy24d93732020-03-10 15:33:57 +0200559 # This adds the header to the payload as well
Salome Thirot0f641972021-05-14 11:19:55 +0100560 if encrypt_keylen == 256:
Mateusz Michalek63fa7e42024-08-09 12:16:40 +0200561 self.add_header(enckey, protected_tlv_size, compression_flags, 256)
Salome Thirot0f641972021-05-14 11:19:55 +0100562 else:
Mateusz Michalek63fa7e42024-08-09 12:16:40 +0200563 self.add_header(enckey, protected_tlv_size, compression_flags)
David Brown23f91ad2017-05-16 11:38:17 -0600564
Fabio Utzig510fddb2019-09-12 12:15:36 -0300565 prot_tlv = TLV(self.endian, TLV_PROT_INFO_MAGIC)
David Brown23f91ad2017-05-16 11:38:17 -0600566
Fabio Utzig510fddb2019-09-12 12:15:36 -0300567 # Protected TLVs must be added first, because they are also included
568 # in the hash calculation
569 protected_tlv_off = None
David Vinczeda8c9192019-03-26 17:17:41 +0100570 if protected_tlv_size != 0:
David Vincze1a7a6902020-02-18 15:05:16 +0100571
572 e = STRUCT_ENDIAN_DICT[self.endian]
573
574 if self.security_counter is not None:
575 payload = struct.pack(e + 'I', self.security_counter)
576 prot_tlv.add('SEC_CNT', payload)
577
David Vincze71b8f982020-03-17 19:08:12 +0100578 if sw_type is not None:
579 prot_tlv.add('BOOT_RECORD', boot_record)
580
David Vincze1a7a6902020-02-18 15:05:16 +0100581 if dependencies is not None:
582 for i in range(dependencies_num):
583 payload = struct.pack(
Rustam Ismayilov36f8bf32023-11-27 21:44:54 +0100584 e + 'B3x' + 'BBHI',
585 int(dependencies[DEP_IMAGES_KEY][i]),
586 dependencies[DEP_VERSIONS_KEY][i].major,
587 dependencies[DEP_VERSIONS_KEY][i].minor,
588 dependencies[DEP_VERSIONS_KEY][i].revision,
589 dependencies[DEP_VERSIONS_KEY][i].build
590 )
David Vincze1a7a6902020-02-18 15:05:16 +0100591 prot_tlv.add('DEPENDENCY', payload)
David Vinczeda8c9192019-03-26 17:17:41 +0100592
Mateusz Michalek63fa7e42024-08-09 12:16:40 +0200593 if compression_tlvs is not None:
594 for tag, value in compression_tlvs.items():
595 prot_tlv.add(tag, value)
David Vinczeb2a1a482020-09-18 11:54:30 +0200596 if custom_tlvs is not None:
597 for tag, value in custom_tlvs.items():
598 prot_tlv.add(tag, value)
Ihor Slabkyy24d93732020-03-10 15:33:57 +0200599
Fabio Utzig510fddb2019-09-12 12:15:36 -0300600 protected_tlv_off = len(self.payload)
Mateusz Michalek1da18e92025-01-02 14:48:28 +0100601
Fabio Utzig510fddb2019-09-12 12:15:36 -0300602 self.payload += prot_tlv.get()
603
604 tlv = TLV(self.endian)
David Vinczeda8c9192019-03-26 17:17:41 +0100605
Dominik Ermelc894d042024-08-22 14:58:15 +0000606 # These signature is done over sha of image. In case of
607 # EC signatures so called Pure algorithm, designated to be run
608 # over entire message is used with sha of image as message,
609 # so, for example, in case of ED25519 we have here SHAxxx-ED25519-SHA512.
Roland Mikhel57041742023-02-03 14:43:13 +0100610 sha = hash_algorithm()
David Brown23f91ad2017-05-16 11:38:17 -0600611 sha.update(self.payload)
612 digest = sha.digest()
Roland Mikhel57041742023-02-03 14:43:13 +0100613 tlv.add(hash_tlv, digest)
Mateusz Michalek63fa7e42024-08-09 12:16:40 +0200614 self.image_hash = digest
Dominik Ermel1c04eac2024-09-12 19:37:40 +0000615 # Unless pure, we are signing digest.
616 message = digest
617
618 if is_pure:
619 # Note that when Pure signature is used, hash TLV is not present.
620 message = bytes(self.payload)
621 e = STRUCT_ENDIAN_DICT[self.endian]
622 sig_pure = struct.pack(e + '?', True)
623 tlv.add('SIG_PURE', sig_pure)
David Brown23f91ad2017-05-16 11:38:17 -0600624
Fabio Utzig08a716d2022-11-18 10:54:07 -0300625 if vector_to_sign == 'payload':
626 # Stop amending data to the image
627 # Just keep data vector which is expected to be signed
628 print(os.path.basename(__file__) + ': export payload')
629 return
630 elif vector_to_sign == 'digest':
631 self.payload = digest
632 print(os.path.basename(__file__) + ': export digest')
633 return
634
Almir Okato3eb50262022-05-09 16:23:50 -0300635 if key is not None or fixed_sig is not None:
636 if public_key_format == 'hash':
637 tlv.add('KEYHASH', pubbytes)
Fabio Utzig8101d1f2019-05-09 15:03:22 -0300638 else:
Almir Okato3eb50262022-05-09 16:23:50 -0300639 tlv.add('PUBKEY', pub)
640
Almir Okato3eb50262022-05-09 16:23:50 -0300641 if key is not None and fixed_sig is None:
Roland Mikhel57041742023-02-03 14:43:13 +0100642 # `sign` expects the full image payload (hashing done
643 # internally), while `sign_digest` expects only the digest
644 # of the payload
Almir Okato3eb50262022-05-09 16:23:50 -0300645
646 if hasattr(key, 'sign'):
Antonio de Angelis284b8fe2022-11-15 13:54:47 +0000647 print(os.path.basename(__file__) + ": sign the payload")
Almir Okato3eb50262022-05-09 16:23:50 -0300648 sig = key.sign(bytes(self.payload))
649 else:
Antonio de Angelis284b8fe2022-11-15 13:54:47 +0000650 print(os.path.basename(__file__) + ": sign the digest")
Dominik Ermelc894d042024-08-22 14:58:15 +0000651 sig = key.sign_digest(message)
David Vincze7f982b02023-04-27 16:12:17 +0200652 tlv.add(key.sig_tlv(), sig)
Almir Okato3eb50262022-05-09 16:23:50 -0300653 self.signature = sig
654 elif fixed_sig is not None and key is None:
David Vincze7f982b02023-04-27 16:12:17 +0200655 tlv.add(pub_key.sig_tlv(), fixed_sig['value'])
Almir Okato3eb50262022-05-09 16:23:50 -0300656 self.signature = fixed_sig['value']
657 else:
658 raise click.UsageError("Can not sign using key and provide fixed-signature at the same time")
David Brown23f91ad2017-05-16 11:38:17 -0600659
Fabio Utzig510fddb2019-09-12 12:15:36 -0300660 # At this point the image was hashed + signed, we can remove the
661 # protected TLVs from the payload (will be re-added later)
662 if protected_tlv_off is not None:
663 self.payload = self.payload[:protected_tlv_off]
664
Mateusz Michalek1da18e92025-01-02 14:48:28 +0100665 if enckey is not None and dont_encrypt is False:
Salome Thirot0f641972021-05-14 11:19:55 +0100666 if encrypt_keylen == 256:
667 plainkey = os.urandom(32)
668 else:
669 plainkey = os.urandom(16)
Fabio Utzig7a3b2602019-10-22 09:56:44 -0300670
671 if isinstance(enckey, rsa.RSAPublic):
672 cipherkey = enckey._get_public().encrypt(
673 plainkey, padding.OAEP(
674 mgf=padding.MGF1(algorithm=hashes.SHA256()),
675 algorithm=hashes.SHA256(),
676 label=None))
Fabio Utzig9a492d52020-01-15 11:31:52 -0300677 self.enctlv_len = len(cipherkey)
Fabio Utzig7a3b2602019-10-22 09:56:44 -0300678 tlv.add('ENCRSA2048', cipherkey)
Fabio Utzig960b4c52020-04-02 13:07:12 -0300679 elif isinstance(enckey, (ecdsa.ECDSA256P1Public,
680 x25519.X25519Public)):
681 cipherkey, mac, pubk = self.ecies_hkdf(enckey, plainkey)
Fabio Utzig9a492d52020-01-15 11:31:52 -0300682 enctlv = pubk + mac + cipherkey
683 self.enctlv_len = len(enctlv)
Fabio Utzig960b4c52020-04-02 13:07:12 -0300684 if isinstance(enckey, ecdsa.ECDSA256P1Public):
685 tlv.add('ENCEC256', enctlv)
686 else:
687 tlv.add('ENCX25519', enctlv)
Fabio Utzig06b77b82018-08-23 16:01:16 -0300688
Michel Jaouend09aa6b2022-01-07 16:48:58 +0100689 if not clear:
690 nonce = bytes([0] * 16)
691 cipher = Cipher(algorithms.AES(plainkey), modes.CTR(nonce),
692 backend=default_backend())
693 encryptor = cipher.encryptor()
694 img = bytes(self.payload[self.header_size:])
695 self.payload[self.header_size:] = \
696 encryptor.update(img) + encryptor.finalize()
Fabio Utzig06b77b82018-08-23 16:01:16 -0300697
Fabio Utzig510fddb2019-09-12 12:15:36 -0300698 self.payload += prot_tlv.get()
699 self.payload += tlv.get()
David Brown23f91ad2017-05-16 11:38:17 -0600700
Fabio Utzig9a492d52020-01-15 11:31:52 -0300701 self.check_trailer()
702
Mateusz Michalek63fa7e42024-08-09 12:16:40 +0200703 def get_struct_endian(self):
704 return STRUCT_ENDIAN_DICT[self.endian]
705
Andrzej Puzdrowski160303c2022-03-15 15:41:14 +0100706 def get_signature(self):
707 return self.signature
708
Mateusz Michalek63fa7e42024-08-09 12:16:40 +0200709 def get_infile_data(self):
710 return self.infile_data
711
712 def add_header(self, enckey, protected_tlv_size, compression_flags, aes_length=128):
Fabio Utzigcd284062018-11-30 11:05:45 -0200713 """Install the image header."""
David Brown23f91ad2017-05-16 11:38:17 -0600714
David Brown0f0c6a82017-06-08 09:26:24 -0600715 flags = 0
Fabio Utzig06b77b82018-08-23 16:01:16 -0300716 if enckey is not None:
Salome Thirot0f641972021-05-14 11:19:55 +0100717 if aes_length == 128:
718 flags |= IMAGE_F['ENCRYPTED_AES128']
719 else:
720 flags |= IMAGE_F['ENCRYPTED_AES256']
David Vincze1e0c5442020-04-07 14:12:33 +0200721 if self.load_addr != 0:
722 # Indicates that this image should be loaded into RAM
723 # instead of run directly from flash.
724 flags |= IMAGE_F['RAM_LOAD']
Dominik Ermel50820b12020-12-14 13:16:46 +0000725 if self.rom_fixed:
726 flags |= IMAGE_F['ROM_FIXED']
Mateusz Wielgosdc030552024-02-26 15:02:45 -0600727 if self.non_bootable:
728 flags |= IMAGE_F['NON_BOOTABLE']
David Brown23f91ad2017-05-16 11:38:17 -0600729
Mark Schultea66c6872018-09-26 17:24:40 -0700730 e = STRUCT_ENDIAN_DICT[self.endian]
731 fmt = (e +
David Vinczeda8c9192019-03-26 17:17:41 +0100732 # type ImageHdr struct {
733 'I' + # Magic uint32
734 'I' + # LoadAddr uint32
735 'H' + # HdrSz uint16
736 'H' + # PTLVSz uint16
737 'I' + # ImgSz uint32
738 'I' + # Flags uint32
739 'BBHI' + # Vers ImageVersion
740 'I' # Pad1 uint32
741 ) # }
David Brown23f91ad2017-05-16 11:38:17 -0600742 assert struct.calcsize(fmt) == IMAGE_HEADER_SIZE
743 header = struct.pack(fmt,
Roland Mikhel57041742023-02-03 14:43:13 +0100744 IMAGE_MAGIC,
745 self.rom_fixed or self.load_addr,
746 self.header_size,
747 protected_tlv_size, # TLV Info header +
748 # Protected TLVs
749 len(self.payload) - self.header_size, # ImageSz
Mateusz Michalek63fa7e42024-08-09 12:16:40 +0200750 flags | compression_flags,
Roland Mikhel57041742023-02-03 14:43:13 +0100751 self.version.major,
752 self.version.minor or 0,
753 self.version.revision or 0,
754 self.version.build or 0,
755 0) # Pad1
David Brown23f91ad2017-05-16 11:38:17 -0600756 self.payload = bytearray(self.payload)
757 self.payload[:len(header)] = header
758
Fabio Utzig9a492d52020-01-15 11:31:52 -0300759 def _trailer_size(self, write_size, max_sectors, overwrite_only, enckey,
760 save_enctlv, enctlv_len):
Fabio Utzig519285f2018-06-04 11:11:53 -0300761 # NOTE: should already be checked by the argument parser
Fabio Utzig649d80f2019-09-12 10:26:23 -0300762 magic_size = 16
Gustavo Henrique Niheicf120ba2021-11-22 18:44:11 -0300763 magic_align_size = align_up(magic_size, self.max_align)
Fabio Utzigdcf0c9b2018-06-11 12:27:49 -0700764 if overwrite_only:
Kristine Jassmann73c38c62021-02-03 16:56:14 +0000765 return self.max_align * 2 + magic_align_size
Fabio Utzigdcf0c9b2018-06-11 12:27:49 -0700766 else:
Kristine Jassmann73c38c62021-02-03 16:56:14 +0000767 if write_size not in set([1, 2, 4, 8, 16, 32]):
Fabio Utzig1f508922020-01-15 11:37:51 -0300768 raise click.BadParameter("Invalid alignment: {}".format(
769 write_size))
Fabio Utzigdcf0c9b2018-06-11 12:27:49 -0700770 m = DEFAULT_MAX_SECTORS if max_sectors is None else max_sectors
Fabio Utzig649d80f2019-09-12 10:26:23 -0300771 trailer = m * 3 * write_size # status area
772 if enckey is not None:
Fabio Utzig9a492d52020-01-15 11:31:52 -0300773 if save_enctlv:
774 # TLV saved by the bootloader is aligned
Gustavo Henrique Niheicf120ba2021-11-22 18:44:11 -0300775 keylen = align_up(enctlv_len, self.max_align)
Fabio Utzig9a492d52020-01-15 11:31:52 -0300776 else:
Gustavo Henrique Niheicf120ba2021-11-22 18:44:11 -0300777 keylen = align_up(16, self.max_align)
Fabio Utzig9a492d52020-01-15 11:31:52 -0300778 trailer += keylen * 2 # encryption keys
Kristine Jassmann73c38c62021-02-03 16:56:14 +0000779 trailer += self.max_align * 4 # image_ok/copy_done/swap_info/swap_size
780 trailer += magic_align_size
Fabio Utzig649d80f2019-09-12 10:26:23 -0300781 return trailer
Fabio Utzig519285f2018-06-04 11:11:53 -0300782
Fabio Utzig263d4392018-06-05 10:37:35 -0300783 def pad_to(self, size):
David Brown23f91ad2017-05-16 11:38:17 -0600784 """Pad the image to the given size, with the given flash alignment."""
Fabio Utzigdcf0c9b2018-06-11 12:27:49 -0700785 tsize = self._trailer_size(self.align, self.max_sectors,
Fabio Utzig9a492d52020-01-15 11:31:52 -0300786 self.overwrite_only, self.enckey,
787 self.save_enctlv, self.enctlv_len)
David Brown23f91ad2017-05-16 11:38:17 -0600788 padding = size - (len(self.payload) + tsize)
Henrik Brix Andersen0ce958e2020-03-11 14:04:11 +0100789 pbytes = bytearray([self.erased_val] * padding)
Gustavo Henrique Niheicf120ba2021-11-22 18:44:11 -0300790 pbytes += bytearray([self.erased_val] * (tsize - len(self.boot_magic)))
791 pbytes += self.boot_magic
Kristine Jassmann73c38c62021-02-03 16:56:14 +0000792 if self.confirm and not self.overwrite_only:
793 magic_size = 16
Gustavo Henrique Niheicf120ba2021-11-22 18:44:11 -0300794 magic_align_size = align_up(magic_size, self.max_align)
Kristine Jassmann73c38c62021-02-03 16:56:14 +0000795 image_ok_idx = -(magic_align_size + self.max_align)
796 pbytes[image_ok_idx] = 0x01 # image_ok = 0x01
David Brown23f91ad2017-05-16 11:38:17 -0600797 self.payload += pbytes
Fabio Utzig4a5477a2019-05-27 15:45:08 -0300798
799 @staticmethod
800 def verify(imgfile, key):
Lucian Zala79c284b2024-01-30 17:43:11 +0200801 ext = os.path.splitext(imgfile)[1][1:].lower()
802 try:
803 if ext == INTEL_HEX_EXT:
804 b = IntelHex(imgfile).tobinstr()
805 else:
806 with open(imgfile, 'rb') as f:
807 b = f.read()
808 except FileNotFoundError:
809 raise click.UsageError(f"Image file {imgfile} not found")
Fabio Utzig4a5477a2019-05-27 15:45:08 -0300810
811 magic, _, header_size, _, img_size = struct.unpack('IIHHI', b[:16])
Marek Pietae9555102019-08-08 16:08:16 +0200812 version = struct.unpack('BBHI', b[20:28])
813
Fabio Utzig4a5477a2019-05-27 15:45:08 -0300814 if magic != IMAGE_MAGIC:
Dominik Ermel1c04eac2024-09-12 19:37:40 +0000815 return VerifyResult.INVALID_MAGIC, None, None, None
Fabio Utzig4a5477a2019-05-27 15:45:08 -0300816
Fabio Utzigd12a8da2021-01-21 08:44:59 -0300817 tlv_off = header_size + img_size
Rustam Ismayilov36f8bf32023-11-27 21:44:54 +0100818 tlv_info = b[tlv_off:tlv_off + TLV_INFO_SIZE]
Fabio Utzig4a5477a2019-05-27 15:45:08 -0300819 magic, tlv_tot = struct.unpack('HH', tlv_info)
Fabio Utzigd12a8da2021-01-21 08:44:59 -0300820 if magic == TLV_PROT_INFO_MAGIC:
821 tlv_off += tlv_tot
Rustam Ismayilov36f8bf32023-11-27 21:44:54 +0100822 tlv_info = b[tlv_off:tlv_off + TLV_INFO_SIZE]
Fabio Utzigd12a8da2021-01-21 08:44:59 -0300823 magic, tlv_tot = struct.unpack('HH', tlv_info)
824
Fabio Utzig4a5477a2019-05-27 15:45:08 -0300825 if magic != TLV_INFO_MAGIC:
Dominik Ermel1c04eac2024-09-12 19:37:40 +0000826 return VerifyResult.INVALID_TLV_INFO_MAGIC, None, None, None
827
828 # This is set by existence of TLV SIG_PURE
829 is_pure = False
Fabio Utzig4a5477a2019-05-27 15:45:08 -0300830
Fabio Utzigd12a8da2021-01-21 08:44:59 -0300831 prot_tlv_size = tlv_off
Rustam Ismayilov36f8bf32023-11-27 21:44:54 +0100832 hash_region = b[:prot_tlv_size]
Dominik Ermel1c04eac2024-09-12 19:37:40 +0000833 tlv_end = tlv_off + tlv_tot
834 tlv_off += TLV_INFO_SIZE # skip tlv info
835
836 # First scan all TLVs in search of SIG_PURE
837 while tlv_off < tlv_end:
838 tlv = b[tlv_off:tlv_off + TLV_SIZE]
839 tlv_type, _, tlv_len = struct.unpack('BBH', tlv)
840 if tlv_type == TLV_VALUES['SIG_PURE']:
841 is_pure = True
842 break
843 tlv_off += TLV_SIZE + tlv_len
844
Rustam Ismayilov36f8bf32023-11-27 21:44:54 +0100845 digest = None
Georgij Cernysiov17b56a02025-05-13 18:27:04 +0200846 tlv_off = prot_tlv_size
Fabio Utzig4a5477a2019-05-27 15:45:08 -0300847 tlv_end = tlv_off + tlv_tot
848 tlv_off += TLV_INFO_SIZE # skip tlv info
849 while tlv_off < tlv_end:
Rustam Ismayilov36f8bf32023-11-27 21:44:54 +0100850 tlv = b[tlv_off:tlv_off + TLV_SIZE]
Fabio Utzig4a5477a2019-05-27 15:45:08 -0300851 tlv_type, _, tlv_len = struct.unpack('BBH', tlv)
Dominik Ermelc894d042024-08-22 14:58:15 +0000852 if is_sha_tlv(tlv_type):
Rustam Ismayilov36f8bf32023-11-27 21:44:54 +0100853 if not tlv_matches_key_type(tlv_type, key):
Dominik Ermel1c04eac2024-09-12 19:37:40 +0000854 return VerifyResult.KEY_MISMATCH, None, None, None
Fabio Utzig4a5477a2019-05-27 15:45:08 -0300855 off = tlv_off + TLV_SIZE
Rustam Ismayilov36f8bf32023-11-27 21:44:54 +0100856 digest = get_digest(tlv_type, hash_region)
857 if digest == b[off:off + tlv_len]:
Fabio Utzig4a5477a2019-05-27 15:45:08 -0300858 if key is None:
Dominik Ermel1c04eac2024-09-12 19:37:40 +0000859 return VerifyResult.OK, version, digest, None
Fabio Utzig4a5477a2019-05-27 15:45:08 -0300860 else:
Dominik Ermel1c04eac2024-09-12 19:37:40 +0000861 return VerifyResult.INVALID_HASH, None, None, None
862 elif not is_pure and key is not None and tlv_type == TLV_VALUES[key.sig_tlv()]:
Fabio Utzig4a5477a2019-05-27 15:45:08 -0300863 off = tlv_off + TLV_SIZE
Rustam Ismayilov36f8bf32023-11-27 21:44:54 +0100864 tlv_sig = b[off:off + tlv_len]
Fabio Utzigd12a8da2021-01-21 08:44:59 -0300865 payload = b[:prot_tlv_size]
Fabio Utzig4a5477a2019-05-27 15:45:08 -0300866 try:
Fabio Utzig8101d1f2019-05-09 15:03:22 -0300867 if hasattr(key, 'verify'):
868 key.verify(tlv_sig, payload)
869 else:
870 key.verify_digest(tlv_sig, digest)
Dominik Ermel1c04eac2024-09-12 19:37:40 +0000871 return VerifyResult.OK, version, digest, None
872 except InvalidSignature:
873 # continue to next TLV
874 pass
875 elif is_pure and key is not None and tlv_type in ALLOWED_PURE_SIG_TLVS:
876 off = tlv_off + TLV_SIZE
877 tlv_sig = b[off:off + tlv_len]
878 try:
879 key.verify_digest(tlv_sig, hash_region)
880 return VerifyResult.OK, version, None, tlv_sig
Fabio Utzig4a5477a2019-05-27 15:45:08 -0300881 except InvalidSignature:
882 # continue to next TLV
883 pass
884 tlv_off += TLV_SIZE + tlv_len
Dominik Ermel1c04eac2024-09-12 19:37:40 +0000885 return VerifyResult.INVALID_SIGNATURE, None, None, None