1# Copyright 2018 Nordic Semiconductor ASA 2# Copyright 2017-2020 Linaro Limited 3# Copyright 2019-2024 Arm Limited 4# 5# SPDX-License-Identifier: Apache-2.0 6# 7# 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 19""" 20Image signing and management. 21""" 22 23import hashlib 24import os.path 25import struct 26from enum import Enum 27 28import click 29from cryptography.exceptions import InvalidSignature 30from cryptography.hazmat.backends import default_backend 31from cryptography.hazmat.primitives import hashes, hmac 32from cryptography.hazmat.primitives.asymmetric import ec, padding 33from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey 34from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes 35from cryptography.hazmat.primitives.kdf.hkdf import HKDF 36from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat 37from intelhex import IntelHex 38 39from . import version as versmod, keys 40from .boot_record import create_sw_component_data 41from .keys import rsa, ecdsa, x25519 42 43IMAGE_MAGIC = 0x96f3b83d 44IMAGE_HEADER_SIZE = 32 45BIN_EXT = "bin" 46INTEL_HEX_EXT = "hex" 47DEFAULT_MAX_SECTORS = 128 48DEFAULT_MAX_ALIGN = 8 49DEP_IMAGES_KEY = "images" 50DEP_VERSIONS_KEY = "versions" 51MAX_SW_TYPE_LENGTH = 12 # Bytes 52 53# Image header flags. 54IMAGE_F = { 55 'PIC': 0x0000001, 56 'ENCRYPTED_AES128': 0x0000004, 57 'ENCRYPTED_AES256': 0x0000008, 58 'NON_BOOTABLE': 0x0000010, 59 'RAM_LOAD': 0x0000020, 60 'ROM_FIXED': 0x0000100, 61} 62 63TLV_VALUES = { 64 'KEYHASH': 0x01, 65 'PUBKEY': 0x02, 66 'SHA256': 0x10, 67 'SHA384': 0x11, 68 'RSA2048': 0x20, 69 'ECDSASIG': 0x22, 70 'RSA3072': 0x23, 71 'ED25519': 0x24, 72 'ENCRSA2048': 0x30, 73 'ENCKW': 0x31, 74 'ENCEC256': 0x32, 75 'ENCX25519': 0x33, 76 'DEPENDENCY': 0x40, 77 'SEC_CNT': 0x50, 78 'BOOT_RECORD': 0x60, 79} 80 81TLV_SIZE = 4 82TLV_INFO_SIZE = 4 83TLV_INFO_MAGIC = 0x6907 84TLV_PROT_INFO_MAGIC = 0x6908 85 86TLV_VENDOR_RES_MIN = 0x00a0 87TLV_VENDOR_RES_MAX = 0xfffe 88 89STRUCT_ENDIAN_DICT = { 90 'little': '<', 91 'big': '>' 92} 93 94VerifyResult = Enum('VerifyResult', 95 ['OK', 'INVALID_MAGIC', 'INVALID_TLV_INFO_MAGIC', 'INVALID_HASH', 'INVALID_SIGNATURE', 96 'KEY_MISMATCH']) 97 98 99def align_up(num, align): 100 assert (align & (align - 1) == 0) and align != 0 101 return (num + (align - 1)) & ~(align - 1) 102 103 104class TLV(): 105 def __init__(self, endian, magic=TLV_INFO_MAGIC): 106 self.magic = magic 107 self.buf = bytearray() 108 self.endian = endian 109 110 def __len__(self): 111 return TLV_INFO_SIZE + len(self.buf) 112 113 def add(self, kind, payload): 114 """ 115 Add a TLV record. Kind should be a string found in TLV_VALUES above. 116 """ 117 e = STRUCT_ENDIAN_DICT[self.endian] 118 if isinstance(kind, int): 119 if not TLV_VENDOR_RES_MIN <= kind <= TLV_VENDOR_RES_MAX: 120 msg = "Invalid custom TLV type value '0x{:04x}', allowed " \ 121 "value should be between 0x{:04x} and 0x{:04x}".format( 122 kind, TLV_VENDOR_RES_MIN, TLV_VENDOR_RES_MAX) 123 raise click.UsageError(msg) 124 buf = struct.pack(e + 'HH', kind, len(payload)) 125 else: 126 buf = struct.pack(e + 'BBH', TLV_VALUES[kind], 0, len(payload)) 127 self.buf += buf 128 self.buf += payload 129 130 def get(self): 131 if len(self.buf) == 0: 132 return bytes() 133 e = STRUCT_ENDIAN_DICT[self.endian] 134 header = struct.pack(e + 'HH', self.magic, len(self)) 135 return header + bytes(self.buf) 136 137 138def get_digest(tlv_type, hash_region): 139 if tlv_type == TLV_VALUES["SHA384"]: 140 sha = hashlib.sha384() 141 elif tlv_type == TLV_VALUES["SHA256"]: 142 sha = hashlib.sha256() 143 144 sha.update(hash_region) 145 return sha.digest() 146 147 148def tlv_matches_key_type(tlv_type, key): 149 """Check if provided key matches to TLV record in the image""" 150 return (key is None or 151 type(key) == keys.ECDSA384P1 and tlv_type == TLV_VALUES["SHA384"] or 152 type(key) != keys.ECDSA384P1 and tlv_type == TLV_VALUES["SHA256"]) 153 154 155class Image: 156 157 def __init__(self, version=None, header_size=IMAGE_HEADER_SIZE, 158 pad_header=False, pad=False, confirm=False, align=1, 159 slot_size=0, max_sectors=DEFAULT_MAX_SECTORS, 160 overwrite_only=False, endian="little", load_addr=0, 161 rom_fixed=None, erased_val=None, save_enctlv=False, 162 security_counter=None, max_align=None): 163 164 if load_addr and rom_fixed: 165 raise click.UsageError("Can not set rom_fixed and load_addr at the same time") 166 167 self.version = version or versmod.decode_version("0") 168 self.header_size = header_size 169 self.pad_header = pad_header 170 self.pad = pad 171 self.confirm = confirm 172 self.align = align 173 self.slot_size = slot_size 174 self.max_sectors = max_sectors 175 self.overwrite_only = overwrite_only 176 self.endian = endian 177 self.base_addr = None 178 self.load_addr = 0 if load_addr is None else load_addr 179 self.rom_fixed = rom_fixed 180 self.erased_val = 0xff if erased_val is None else int(erased_val, 0) 181 self.payload = [] 182 self.enckey = None 183 self.save_enctlv = save_enctlv 184 self.enctlv_len = 0 185 self.max_align = max(DEFAULT_MAX_ALIGN, align) if max_align is None else int(max_align) 186 187 if self.max_align == DEFAULT_MAX_ALIGN: 188 self.boot_magic = bytes([ 189 0x77, 0xc2, 0x95, 0xf3, 190 0x60, 0xd2, 0xef, 0x7f, 191 0x35, 0x52, 0x50, 0x0f, 192 0x2c, 0xb6, 0x79, 0x80, ]) 193 else: 194 lsb = self.max_align & 0x00ff 195 msb = (self.max_align & 0xff00) >> 8 196 align = bytes([msb, lsb]) if self.endian == "big" else bytes([lsb, msb]) 197 self.boot_magic = align + bytes([0x2d, 0xe1, 198 0x5d, 0x29, 0x41, 0x0b, 199 0x8d, 0x77, 0x67, 0x9c, 200 0x11, 0x0f, 0x1f, 0x8a, ]) 201 202 if security_counter == 'auto': 203 # Security counter has not been explicitly provided, 204 # generate it from the version number 205 self.security_counter = ((self.version.major << 24) 206 + (self.version.minor << 16) 207 + self.version.revision) 208 else: 209 self.security_counter = security_counter 210 211 def __repr__(self): 212 return "<Image version={}, header_size={}, security_counter={}, \ 213 base_addr={}, load_addr={}, align={}, slot_size={}, \ 214 max_sectors={}, overwrite_only={}, endian={} format={}, \ 215 payloadlen=0x{:x}>".format( 216 self.version, 217 self.header_size, 218 self.security_counter, 219 self.base_addr if self.base_addr is not None else "N/A", 220 self.load_addr, 221 self.align, 222 self.slot_size, 223 self.max_sectors, 224 self.overwrite_only, 225 self.endian, 226 self.__class__.__name__, 227 len(self.payload)) 228 229 def load(self, path): 230 """Load an image from a given file""" 231 ext = os.path.splitext(path)[1][1:].lower() 232 try: 233 if ext == INTEL_HEX_EXT: 234 ih = IntelHex(path) 235 self.payload = ih.tobinarray() 236 self.base_addr = ih.minaddr() 237 else: 238 with open(path, 'rb') as f: 239 self.payload = f.read() 240 except FileNotFoundError: 241 raise click.UsageError("Input file not found") 242 243 # Add the image header if needed. 244 if self.pad_header and self.header_size > 0: 245 if self.base_addr: 246 # Adjust base_addr for new header 247 self.base_addr -= self.header_size 248 self.payload = bytes([self.erased_val] * self.header_size) + \ 249 self.payload 250 251 self.check_header() 252 253 def save(self, path, hex_addr=None): 254 """Save an image from a given file""" 255 ext = os.path.splitext(path)[1][1:].lower() 256 if ext == INTEL_HEX_EXT: 257 # input was in binary format, but HEX needs to know the base addr 258 if self.base_addr is None and hex_addr is None: 259 raise click.UsageError("No address exists in input file " 260 "neither was it provided by user") 261 h = IntelHex() 262 if hex_addr is not None: 263 self.base_addr = hex_addr 264 h.frombytes(bytes=self.payload, offset=self.base_addr) 265 if self.pad: 266 trailer_size = self._trailer_size(self.align, self.max_sectors, 267 self.overwrite_only, 268 self.enckey, 269 self.save_enctlv, 270 self.enctlv_len) 271 trailer_addr = (self.base_addr + self.slot_size) - trailer_size 272 if self.confirm and not self.overwrite_only: 273 magic_align_size = align_up(len(self.boot_magic), 274 self.max_align) 275 image_ok_idx = -(magic_align_size + self.max_align) 276 flag = bytearray([self.erased_val] * self.max_align) 277 flag[0] = 0x01 # image_ok = 0x01 278 h.puts(trailer_addr + trailer_size + image_ok_idx, 279 bytes(flag)) 280 h.puts(trailer_addr + (trailer_size - len(self.boot_magic)), 281 bytes(self.boot_magic)) 282 h.tofile(path, 'hex') 283 else: 284 if self.pad: 285 self.pad_to(self.slot_size) 286 with open(path, 'wb') as f: 287 f.write(self.payload) 288 289 def check_header(self): 290 if self.header_size > 0 and not self.pad_header: 291 if any(v != 0 for v in self.payload[0:self.header_size]): 292 raise click.UsageError("Header padding was not requested and " 293 "image does not start with zeros") 294 295 def check_trailer(self): 296 if self.slot_size > 0: 297 tsize = self._trailer_size(self.align, self.max_sectors, 298 self.overwrite_only, self.enckey, 299 self.save_enctlv, self.enctlv_len) 300 padding = self.slot_size - (len(self.payload) + tsize) 301 if padding < 0: 302 msg = "Image size (0x{:x}) + trailer (0x{:x}) exceeds " \ 303 "requested size 0x{:x}".format( 304 len(self.payload), tsize, self.slot_size) 305 raise click.UsageError(msg) 306 307 def ecies_hkdf(self, enckey, plainkey): 308 if isinstance(enckey, ecdsa.ECDSA256P1Public): 309 newpk = ec.generate_private_key(ec.SECP256R1(), default_backend()) 310 shared = newpk.exchange(ec.ECDH(), enckey._get_public()) 311 else: 312 newpk = X25519PrivateKey.generate() 313 shared = newpk.exchange(enckey._get_public()) 314 derived_key = HKDF( 315 algorithm=hashes.SHA256(), length=48, salt=None, 316 info=b'MCUBoot_ECIES_v1', backend=default_backend()).derive(shared) 317 encryptor = Cipher(algorithms.AES(derived_key[:16]), 318 modes.CTR(bytes([0] * 16)), 319 backend=default_backend()).encryptor() 320 cipherkey = encryptor.update(plainkey) + encryptor.finalize() 321 mac = hmac.HMAC(derived_key[16:], hashes.SHA256(), 322 backend=default_backend()) 323 mac.update(cipherkey) 324 ciphermac = mac.finalize() 325 if isinstance(enckey, ecdsa.ECDSA256P1Public): 326 pubk = newpk.public_key().public_bytes( 327 encoding=Encoding.X962, 328 format=PublicFormat.UncompressedPoint) 329 else: 330 pubk = newpk.public_key().public_bytes( 331 encoding=Encoding.Raw, 332 format=PublicFormat.Raw) 333 return cipherkey, ciphermac, pubk 334 335 def create(self, key, public_key_format, enckey, dependencies=None, 336 sw_type=None, custom_tlvs=None, encrypt_keylen=128, clear=False, 337 fixed_sig=None, pub_key=None, vector_to_sign=None): 338 self.enckey = enckey 339 340 # Check what hashing algorithm should be used 341 if (key and isinstance(key, ecdsa.ECDSA384P1) 342 or pub_key and isinstance(pub_key, ecdsa.ECDSA384P1Public)): 343 hash_algorithm = hashlib.sha384 344 hash_tlv = "SHA384" 345 else: 346 hash_algorithm = hashlib.sha256 347 hash_tlv = "SHA256" 348 # Calculate the hash of the public key 349 if key is not None: 350 pub = key.get_public_bytes() 351 sha = hash_algorithm() 352 sha.update(pub) 353 pubbytes = sha.digest() 354 elif pub_key is not None: 355 if hasattr(pub_key, 'sign'): 356 print(os.path.basename(__file__) + ": sign the payload") 357 pub = pub_key.get_public_bytes() 358 sha = hash_algorithm() 359 sha.update(pub) 360 pubbytes = sha.digest() 361 else: 362 pubbytes = bytes(hashlib.sha256().digest_size) 363 364 protected_tlv_size = 0 365 366 if self.security_counter is not None: 367 # Size of the security counter TLV: header ('HH') + payload ('I') 368 # = 4 + 4 = 8 Bytes 369 protected_tlv_size += TLV_SIZE + 4 370 371 if sw_type is not None: 372 if len(sw_type) > MAX_SW_TYPE_LENGTH: 373 msg = "'{}' is too long ({} characters) for sw_type. Its " \ 374 "maximum allowed length is 12 characters.".format( 375 sw_type, len(sw_type)) 376 raise click.UsageError(msg) 377 378 image_version = (str(self.version.major) + '.' 379 + str(self.version.minor) + '.' 380 + str(self.version.revision)) 381 382 # The image hash is computed over the image header, the image 383 # itself and the protected TLV area. However, the boot record TLV 384 # (which is part of the protected area) should contain this hash 385 # before it is even calculated. For this reason the script fills 386 # this field with zeros and the bootloader will insert the right 387 # value later. 388 digest = bytes(hash_algorithm().digest_size) 389 390 # Create CBOR encoded boot record 391 boot_record = create_sw_component_data(sw_type, image_version, 392 hash_tlv, digest, 393 pubbytes) 394 395 protected_tlv_size += TLV_SIZE + len(boot_record) 396 397 if dependencies is not None: 398 # Size of a Dependency TLV = Header ('HH') + Payload('IBBHI') 399 # = 4 + 12 = 16 Bytes 400 dependencies_num = len(dependencies[DEP_IMAGES_KEY]) 401 protected_tlv_size += (dependencies_num * 16) 402 403 if custom_tlvs is not None: 404 for value in custom_tlvs.values(): 405 protected_tlv_size += TLV_SIZE + len(value) 406 407 if protected_tlv_size != 0: 408 # Add the size of the TLV info header 409 protected_tlv_size += TLV_INFO_SIZE 410 411 # At this point the image is already on the payload 412 # 413 # This adds the padding if image is not aligned to the 16 Bytes 414 # in encrypted mode 415 if self.enckey is not None: 416 pad_len = len(self.payload) % 16 417 if pad_len > 0: 418 pad = bytes(16 - pad_len) 419 if isinstance(self.payload, bytes): 420 self.payload += pad 421 else: 422 self.payload.extend(pad) 423 424 # This adds the header to the payload as well 425 if encrypt_keylen == 256: 426 self.add_header(enckey, protected_tlv_size, 256) 427 else: 428 self.add_header(enckey, protected_tlv_size) 429 430 prot_tlv = TLV(self.endian, TLV_PROT_INFO_MAGIC) 431 432 # Protected TLVs must be added first, because they are also included 433 # in the hash calculation 434 protected_tlv_off = None 435 if protected_tlv_size != 0: 436 437 e = STRUCT_ENDIAN_DICT[self.endian] 438 439 if self.security_counter is not None: 440 payload = struct.pack(e + 'I', self.security_counter) 441 prot_tlv.add('SEC_CNT', payload) 442 443 if sw_type is not None: 444 prot_tlv.add('BOOT_RECORD', boot_record) 445 446 if dependencies is not None: 447 for i in range(dependencies_num): 448 payload = struct.pack( 449 e + 'B3x' + 'BBHI', 450 int(dependencies[DEP_IMAGES_KEY][i]), 451 dependencies[DEP_VERSIONS_KEY][i].major, 452 dependencies[DEP_VERSIONS_KEY][i].minor, 453 dependencies[DEP_VERSIONS_KEY][i].revision, 454 dependencies[DEP_VERSIONS_KEY][i].build 455 ) 456 prot_tlv.add('DEPENDENCY', payload) 457 458 if custom_tlvs is not None: 459 for tag, value in custom_tlvs.items(): 460 prot_tlv.add(tag, value) 461 462 protected_tlv_off = len(self.payload) 463 self.payload += prot_tlv.get() 464 465 tlv = TLV(self.endian) 466 467 # Note that ecdsa wants to do the hashing itself, which means 468 # we get to hash it twice. 469 sha = hash_algorithm() 470 sha.update(self.payload) 471 digest = sha.digest() 472 tlv.add(hash_tlv, digest) 473 474 if vector_to_sign == 'payload': 475 # Stop amending data to the image 476 # Just keep data vector which is expected to be signed 477 print(os.path.basename(__file__) + ': export payload') 478 return 479 elif vector_to_sign == 'digest': 480 self.payload = digest 481 print(os.path.basename(__file__) + ': export digest') 482 return 483 484 if key is not None or fixed_sig is not None: 485 if public_key_format == 'hash': 486 tlv.add('KEYHASH', pubbytes) 487 else: 488 tlv.add('PUBKEY', pub) 489 490 if key is not None and fixed_sig is None: 491 # `sign` expects the full image payload (hashing done 492 # internally), while `sign_digest` expects only the digest 493 # of the payload 494 495 if hasattr(key, 'sign'): 496 print(os.path.basename(__file__) + ": sign the payload") 497 sig = key.sign(bytes(self.payload)) 498 else: 499 print(os.path.basename(__file__) + ": sign the digest") 500 sig = key.sign_digest(digest) 501 tlv.add(key.sig_tlv(), sig) 502 self.signature = sig 503 elif fixed_sig is not None and key is None: 504 tlv.add(pub_key.sig_tlv(), fixed_sig['value']) 505 self.signature = fixed_sig['value'] 506 else: 507 raise click.UsageError("Can not sign using key and provide fixed-signature at the same time") 508 509 # At this point the image was hashed + signed, we can remove the 510 # protected TLVs from the payload (will be re-added later) 511 if protected_tlv_off is not None: 512 self.payload = self.payload[:protected_tlv_off] 513 514 if enckey is not None: 515 if encrypt_keylen == 256: 516 plainkey = os.urandom(32) 517 else: 518 plainkey = os.urandom(16) 519 520 if isinstance(enckey, rsa.RSAPublic): 521 cipherkey = enckey._get_public().encrypt( 522 plainkey, padding.OAEP( 523 mgf=padding.MGF1(algorithm=hashes.SHA256()), 524 algorithm=hashes.SHA256(), 525 label=None)) 526 self.enctlv_len = len(cipherkey) 527 tlv.add('ENCRSA2048', cipherkey) 528 elif isinstance(enckey, (ecdsa.ECDSA256P1Public, 529 x25519.X25519Public)): 530 cipherkey, mac, pubk = self.ecies_hkdf(enckey, plainkey) 531 enctlv = pubk + mac + cipherkey 532 self.enctlv_len = len(enctlv) 533 if isinstance(enckey, ecdsa.ECDSA256P1Public): 534 tlv.add('ENCEC256', enctlv) 535 else: 536 tlv.add('ENCX25519', enctlv) 537 538 if not clear: 539 nonce = bytes([0] * 16) 540 cipher = Cipher(algorithms.AES(plainkey), modes.CTR(nonce), 541 backend=default_backend()) 542 encryptor = cipher.encryptor() 543 img = bytes(self.payload[self.header_size:]) 544 self.payload[self.header_size:] = \ 545 encryptor.update(img) + encryptor.finalize() 546 547 self.payload += prot_tlv.get() 548 self.payload += tlv.get() 549 550 self.check_trailer() 551 552 def get_signature(self): 553 return self.signature 554 555 def add_header(self, enckey, protected_tlv_size, aes_length=128): 556 """Install the image header.""" 557 558 flags = 0 559 if enckey is not None: 560 if aes_length == 128: 561 flags |= IMAGE_F['ENCRYPTED_AES128'] 562 else: 563 flags |= IMAGE_F['ENCRYPTED_AES256'] 564 if self.load_addr != 0: 565 # Indicates that this image should be loaded into RAM 566 # instead of run directly from flash. 567 flags |= IMAGE_F['RAM_LOAD'] 568 if self.rom_fixed: 569 flags |= IMAGE_F['ROM_FIXED'] 570 571 e = STRUCT_ENDIAN_DICT[self.endian] 572 fmt = (e + 573 # type ImageHdr struct { 574 'I' + # Magic uint32 575 'I' + # LoadAddr uint32 576 'H' + # HdrSz uint16 577 'H' + # PTLVSz uint16 578 'I' + # ImgSz uint32 579 'I' + # Flags uint32 580 'BBHI' + # Vers ImageVersion 581 'I' # Pad1 uint32 582 ) # } 583 assert struct.calcsize(fmt) == IMAGE_HEADER_SIZE 584 header = struct.pack(fmt, 585 IMAGE_MAGIC, 586 self.rom_fixed or self.load_addr, 587 self.header_size, 588 protected_tlv_size, # TLV Info header + 589 # Protected TLVs 590 len(self.payload) - self.header_size, # ImageSz 591 flags, 592 self.version.major, 593 self.version.minor or 0, 594 self.version.revision or 0, 595 self.version.build or 0, 596 0) # Pad1 597 self.payload = bytearray(self.payload) 598 self.payload[:len(header)] = header 599 600 def _trailer_size(self, write_size, max_sectors, overwrite_only, enckey, 601 save_enctlv, enctlv_len): 602 # NOTE: should already be checked by the argument parser 603 magic_size = 16 604 magic_align_size = align_up(magic_size, self.max_align) 605 if overwrite_only: 606 return self.max_align * 2 + magic_align_size 607 else: 608 if write_size not in set([1, 2, 4, 8, 16, 32]): 609 raise click.BadParameter("Invalid alignment: {}".format( 610 write_size)) 611 m = DEFAULT_MAX_SECTORS if max_sectors is None else max_sectors 612 trailer = m * 3 * write_size # status area 613 if enckey is not None: 614 if save_enctlv: 615 # TLV saved by the bootloader is aligned 616 keylen = align_up(enctlv_len, self.max_align) 617 else: 618 keylen = align_up(16, self.max_align) 619 trailer += keylen * 2 # encryption keys 620 trailer += self.max_align * 4 # image_ok/copy_done/swap_info/swap_size 621 trailer += magic_align_size 622 return trailer 623 624 def pad_to(self, size): 625 """Pad the image to the given size, with the given flash alignment.""" 626 tsize = self._trailer_size(self.align, self.max_sectors, 627 self.overwrite_only, self.enckey, 628 self.save_enctlv, self.enctlv_len) 629 padding = size - (len(self.payload) + tsize) 630 pbytes = bytearray([self.erased_val] * padding) 631 pbytes += bytearray([self.erased_val] * (tsize - len(self.boot_magic))) 632 pbytes += self.boot_magic 633 if self.confirm and not self.overwrite_only: 634 magic_size = 16 635 magic_align_size = align_up(magic_size, self.max_align) 636 image_ok_idx = -(magic_align_size + self.max_align) 637 pbytes[image_ok_idx] = 0x01 # image_ok = 0x01 638 self.payload += pbytes 639 640 @staticmethod 641 def verify(imgfile, key): 642 ext = os.path.splitext(imgfile)[1][1:].lower() 643 try: 644 if ext == INTEL_HEX_EXT: 645 b = IntelHex(imgfile).tobinstr() 646 else: 647 with open(imgfile, 'rb') as f: 648 b = f.read() 649 except FileNotFoundError: 650 raise click.UsageError(f"Image file {imgfile} not found") 651 652 magic, _, header_size, _, img_size = struct.unpack('IIHHI', b[:16]) 653 version = struct.unpack('BBHI', b[20:28]) 654 655 if magic != IMAGE_MAGIC: 656 return VerifyResult.INVALID_MAGIC, None, None 657 658 tlv_off = header_size + img_size 659 tlv_info = b[tlv_off:tlv_off + TLV_INFO_SIZE] 660 magic, tlv_tot = struct.unpack('HH', tlv_info) 661 if magic == TLV_PROT_INFO_MAGIC: 662 tlv_off += tlv_tot 663 tlv_info = b[tlv_off:tlv_off + TLV_INFO_SIZE] 664 magic, tlv_tot = struct.unpack('HH', tlv_info) 665 666 if magic != TLV_INFO_MAGIC: 667 return VerifyResult.INVALID_TLV_INFO_MAGIC, None, None 668 669 prot_tlv_size = tlv_off 670 hash_region = b[:prot_tlv_size] 671 digest = None 672 tlv_end = tlv_off + tlv_tot 673 tlv_off += TLV_INFO_SIZE # skip tlv info 674 while tlv_off < tlv_end: 675 tlv = b[tlv_off:tlv_off + TLV_SIZE] 676 tlv_type, _, tlv_len = struct.unpack('BBH', tlv) 677 if tlv_type == TLV_VALUES["SHA256"] or tlv_type == TLV_VALUES["SHA384"]: 678 if not tlv_matches_key_type(tlv_type, key): 679 return VerifyResult.KEY_MISMATCH, None, None 680 off = tlv_off + TLV_SIZE 681 digest = get_digest(tlv_type, hash_region) 682 if digest == b[off:off + tlv_len]: 683 if key is None: 684 return VerifyResult.OK, version, digest 685 else: 686 return VerifyResult.INVALID_HASH, None, None 687 elif key is not None and tlv_type == TLV_VALUES[key.sig_tlv()]: 688 off = tlv_off + TLV_SIZE 689 tlv_sig = b[off:off + tlv_len] 690 payload = b[:prot_tlv_size] 691 try: 692 if hasattr(key, 'verify'): 693 key.verify(tlv_sig, payload) 694 else: 695 key.verify_digest(tlv_sig, digest) 696 return VerifyResult.OK, version, digest 697 except InvalidSignature: 698 # continue to next TLV 699 pass 700 tlv_off += TLV_SIZE + tlv_len 701 return VerifyResult.INVALID_SIGNATURE, None, None 702