1# SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD 2# 3# SPDX-License-Identifier: GPL-2.0-or-later 4# PYTHON_ARGCOMPLETE_OK 5import argparse 6import hashlib 7import operator 8import os 9import struct 10import sys 11import tempfile 12import zlib 13from collections import namedtuple 14from io import IOBase 15 16from cryptography import exceptions 17from cryptography.hazmat.backends import default_backend 18from cryptography.hazmat.primitives import hashes 19from cryptography.hazmat.primitives import serialization 20from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa, utils 21from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes 22from cryptography.utils import int_to_bytes 23 24import ecdsa 25 26import esptool 27 28SIG_BLOCK_MAGIC = 0xE7 29 30# Scheme used in Secure Boot V2 31SIG_BLOCK_VERSION_RSA = 0x02 32SIG_BLOCK_VERSION_ECDSA = 0x03 33 34# SHA scheme used in Secure Boot V2 ECDSA signature blocks 35ECDSA_SHA_256 = 0x0 36ECDSA_SHA_384 = 0x1 37 38# Curve IDs used in Secure Boot V2 ECDSA signature blocks 39CURVE_ID_P192 = 1 40CURVE_ID_P256 = 2 41CURVE_ID_P384 = 3 42 43SECTOR_SIZE = 4096 44SIG_BLOCK_SIZE = ( 45 1216 # Refer to secure boot v2 signature block format for more details. 46) 47 48 49def get_chunks(source, chunk_len): 50 """Returns an iterator over 'chunk_len' chunks of 'source'""" 51 return (source[i : i + chunk_len] for i in range(0, len(source), chunk_len)) 52 53 54def endian_swap_words(source): 55 """Endian-swap each word in 'source' bitstring""" 56 assert len(source) % 4 == 0 57 words = "I" * (len(source) // 4) 58 return struct.pack("<" + words, *struct.unpack(">" + words, source)) 59 60 61def swap_word_order(source): 62 """Swap the order of the words in 'source' bitstring""" 63 assert len(source) % 4 == 0 64 words = "I" * (len(source) // 4) 65 return struct.pack(words, *reversed(struct.unpack(words, source))) 66 67 68def _load_hardware_key(keyfile): 69 """Load a 128/256/512-bit key, similar to stored in efuse, from a file 70 71 128-bit keys will be extended to 256-bit using the SHA256 of the key 72 192-bit keys will be extended to 256-bit using the same algorithm used 73 by hardware if 3/4 Coding Scheme is set. 74 """ 75 key = keyfile.read() 76 if len(key) not in [16, 24, 32, 64]: 77 raise esptool.FatalError( 78 "Key file contains wrong length (%d bytes), 16, 24, 32 or 64 expected." 79 % len(key) 80 ) 81 if len(key) == 16: 82 key = _sha256_digest(key) 83 print("Using 128-bit key (extended)") 84 elif len(key) == 24: 85 key = key + key[8:16] 86 assert len(key) == 32 87 print("Using 192-bit key (extended)") 88 elif len(key) == 32: 89 print("Using 256-bit key") 90 else: 91 print("Using 512-bit key") 92 return key 93 94 95def digest_secure_bootloader(args): 96 """Calculate the digest of a bootloader image, in the same way the hardware 97 secure boot engine would do so. Can be used with a pre-loaded key to update a 98 secure bootloader.""" 99 _check_output_is_not_input(args.keyfile, args.output) 100 _check_output_is_not_input(args.image, args.output) 101 _check_output_is_not_input(args.iv, args.output) 102 if args.iv is not None: 103 print("WARNING: --iv argument is for TESTING PURPOSES ONLY") 104 iv = args.iv.read(128) 105 else: 106 iv = os.urandom(128) 107 plaintext_image = args.image.read() 108 args.image.seek(0) 109 110 # secure boot engine reads in 128 byte blocks (ie SHA512 block 111 # size), but also doesn't look for any appended SHA-256 digest 112 fw_image = esptool.bin_image.ESP32FirmwareImage(args.image) 113 if fw_image.append_digest: 114 if len(plaintext_image) % 128 <= 32: 115 # ROM bootloader will read to the end of the 128 byte block, but not 116 # to the end of the SHA-256 digest at the end 117 new_len = len(plaintext_image) - (len(plaintext_image) % 128) 118 plaintext_image = plaintext_image[:new_len] 119 120 # if image isn't 128 byte multiple then pad with 0xFF (ie unwritten flash) 121 # as this is what the secure boot engine will see 122 if len(plaintext_image) % 128 != 0: 123 plaintext_image += b"\xFF" * (128 - (len(plaintext_image) % 128)) 124 125 plaintext = iv + plaintext_image 126 127 # Secure Boot digest algorithm in hardware uses AES256 ECB to 128 # produce a ciphertext, then feeds output through SHA-512 to 129 # produce the digest. Each block in/out of ECB is reordered 130 # (due to hardware quirks not for security.) 131 132 key = _load_hardware_key(args.keyfile) 133 backend = default_backend() 134 cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend) 135 encryptor = cipher.encryptor() 136 digest = hashlib.sha512() 137 138 for block in get_chunks(plaintext, 16): 139 block = block[::-1] # reverse each input block 140 141 cipher_block = encryptor.update(block) 142 # reverse and then byte swap each word in the output block 143 cipher_block = cipher_block[::-1] 144 for block in get_chunks(cipher_block, 4): 145 # Python hashlib can build each SHA block internally 146 digest.update(block[::-1]) 147 148 if args.output is None: 149 args.output = os.path.splitext(args.image.name)[0] + "-digest-0x0000.bin" 150 with open(args.output, "wb") as f: 151 f.write(iv) 152 digest = digest.digest() 153 for word in get_chunks(digest, 4): 154 f.write(word[::-1]) # swap word order in the result 155 f.write(b"\xFF" * (0x1000 - f.tell())) # pad to 0x1000 156 f.write(plaintext_image) 157 print("digest+image written to %s" % args.output) 158 159 160def _generate_ecdsa_signing_key(curve_id, keyfile): 161 sk = ecdsa.SigningKey.generate(curve=curve_id) 162 with open(keyfile, "wb") as f: 163 f.write(sk.to_pem()) 164 165 166def generate_signing_key(args): 167 if os.path.exists(args.keyfile): 168 raise esptool.FatalError("ERROR: Key file %s already exists" % args.keyfile) 169 if args.version == "1": 170 if hasattr(args, "scheme"): 171 if args.scheme != "ecdsa256" and args.scheme is not None: 172 raise esptool.FatalError("ERROR: V1 only supports ECDSA256") 173 """ 174 Generate an ECDSA signing key for signing secure boot images (post-bootloader) 175 """ 176 _generate_ecdsa_signing_key(ecdsa.NIST256p, args.keyfile) 177 print("ECDSA NIST256p private key in PEM format written to %s" % args.keyfile) 178 elif args.version == "2": 179 if args.scheme == "rsa3072" or args.scheme is None: 180 """Generate a RSA 3072 signing key for signing secure boot images""" 181 private_key = rsa.generate_private_key( 182 public_exponent=65537, key_size=3072, backend=default_backend() 183 ).private_bytes( 184 encoding=serialization.Encoding.PEM, 185 format=serialization.PrivateFormat.TraditionalOpenSSL, 186 encryption_algorithm=serialization.NoEncryption(), 187 ) 188 with open(args.keyfile, "wb") as f: 189 f.write(private_key) 190 print(f"RSA 3072 private key in PEM format written to {args.keyfile}") 191 elif args.scheme == "ecdsa192": 192 """Generate a ECDSA 192 signing key for signing secure boot images""" 193 _generate_ecdsa_signing_key(ecdsa.NIST192p, args.keyfile) 194 print(f"ECDSA NIST192p private key in PEM format written to {args.keyfile}") 195 elif args.scheme == "ecdsa256": 196 """Generate a ECDSA 256 signing key for signing secure boot images""" 197 _generate_ecdsa_signing_key(ecdsa.NIST256p, args.keyfile) 198 print(f"ECDSA NIST256p private key in PEM format written to {args.keyfile}") 199 elif args.scheme == "ecdsa384": 200 """Generate a ECDSA 384 signing key for signing secure boot images""" 201 _generate_ecdsa_signing_key(ecdsa.NIST384p, args.keyfile) 202 print(f"ECDSA NIST384p private key in PEM format written to {args.keyfile}") 203 else: 204 raise esptool.FatalError("ERROR: Unsupported signing scheme {args.scheme}") 205 206 207def load_ecdsa_signing_key(keyfile): 208 """Load ECDSA signing key""" 209 try: 210 sk = ecdsa.SigningKey.from_pem(keyfile.read()) 211 except ValueError: 212 raise esptool.FatalError( 213 "Incorrect ECDSA private key specified. " 214 "Please check algorithm and/or format." 215 ) 216 if sk.curve not in [ecdsa.NIST192p, ecdsa.NIST256p]: 217 raise esptool.FatalError("Supports NIST192p and NIST256p keys only") 218 return sk 219 220 221def _load_ecdsa_signing_key(keyfile): 222 """Load ECDSA signing key for Secure Boot V1 only""" 223 sk = load_ecdsa_signing_key(keyfile) 224 if sk.curve != ecdsa.NIST256p: 225 raise esptool.FatalError( 226 "Signing key uses incorrect curve. ESP32 Secure Boot only supports " 227 "NIST256p (openssl calls this curve 'prime256v1')" 228 ) 229 return sk 230 231 232def _load_ecdsa_verifying_key(keyfile): 233 """Load ECDSA verifying key for Secure Boot V1 only""" 234 try: 235 vk = ecdsa.VerifyingKey.from_pem(keyfile.read()) 236 except ValueError: 237 raise esptool.FatalError( 238 "Incorrect ECDSA public key specified. " 239 "Please check algorithm and/or format." 240 ) 241 if vk.curve != ecdsa.NIST256p: 242 raise esptool.FatalError( 243 "Signing key uses incorrect curve. ESP32 Secure Boot only supports " 244 "NIST256p (openssl calls this curve 'prime256v1')" 245 ) 246 return vk 247 248 249def _load_sbv2_signing_key(keydata): 250 """ 251 Load Secure Boot V2 signing key 252 253 can be rsa.RSAPrivateKey or ec.EllipticCurvePrivateKey 254 """ 255 sk = serialization.load_pem_private_key( 256 keydata, password=None, backend=default_backend() 257 ) 258 if isinstance(sk, rsa.RSAPrivateKey): 259 if sk.key_size != 3072: 260 raise esptool.FatalError( 261 "Key file has length %d bits. Secure boot v2 only supports RSA-3072." 262 % sk.key_size 263 ) 264 return sk 265 if isinstance(sk, ec.EllipticCurvePrivateKey): 266 if not isinstance(sk.curve, (ec.SECP192R1, ec.SECP256R1, ec.SECP384R1)): 267 raise esptool.FatalError( 268 "Key file uses incorrect curve. Secure Boot V2 + ECDSA only supports " 269 "NIST192p, NIST256p, NIST384p (aka prime192v1 / secp192r1, prime256v1 / secp256r1, secp384r1)" 270 ) 271 return sk 272 273 raise esptool.FatalError("Unsupported signing key for Secure Boot V2") 274 275 276def _load_sbv2_pub_key(keydata): 277 """ 278 Load Secure Boot V2 public key, can be rsa.RSAPublicKey or ec.EllipticCurvePublicKey 279 """ 280 vk = serialization.load_pem_public_key(keydata, backend=default_backend()) 281 if isinstance(vk, rsa.RSAPublicKey): 282 if vk.key_size != 3072: 283 raise esptool.FatalError( 284 "Key file has length %d bits. Secure boot v2 only supports RSA-3072." 285 % vk.key_size 286 ) 287 return vk 288 if isinstance(vk, ec.EllipticCurvePublicKey): 289 if not isinstance(vk.curve, (ec.SECP192R1, ec.SECP256R1, ec.SECP384R1)): 290 raise esptool.FatalError( 291 "Key file uses incorrect curve. Secure Boot V2 + ECDSA only supports " 292 "NIST192p, NIST256p, NIST384p (aka prime192v1 / secp192r1, prime256v1 / secp256r1, secp384r1)" 293 ) 294 return vk 295 296 raise esptool.FatalError("Unsupported public key for Secure Boot V2") 297 298 299def _get_sbv2_pub_key(keyfile): 300 key_data = keyfile.read() 301 if ( 302 b"-BEGIN RSA PRIVATE KEY" in key_data 303 or b"-BEGIN EC PRIVATE KEY" in key_data 304 or b"-BEGIN PRIVATE KEY" in key_data 305 ): 306 return _load_sbv2_signing_key(key_data).public_key() 307 elif b"-BEGIN PUBLIC KEY" in key_data: 308 vk = _load_sbv2_pub_key(key_data) 309 else: 310 raise esptool.FatalError( 311 "Verification key does not appear to be an RSA Private or " 312 "Public key in PEM format. Unsupported" 313 ) 314 return vk 315 316 317def _get_sbv2_rsa_primitives(public_key): 318 primitives = namedtuple("primitives", ["n", "e", "m", "rinv"]) 319 numbers = public_key.public_numbers() 320 primitives.n = numbers.n # 321 primitives.e = numbers.e # two public key components 322 323 # Note: this cheats and calls a private 'rsa' method to get the modular 324 # inverse calculation. 325 primitives.m = -rsa._modinv(primitives.n, 1 << 32) 326 327 rr = 1 << (public_key.key_size * 2) 328 primitives.rinv = rr % primitives.n 329 return primitives 330 331 332def _microecc_format(a, b, curve_len): 333 """ 334 Given two numbers (curve coordinates or (r,s) signature), write them out as a 335 little-endian byte sequence suitable for micro-ecc 336 "native little endian" mode 337 """ 338 byte_len = int(curve_len / 8) 339 ab = int_to_bytes(a, byte_len)[::-1] + int_to_bytes(b, byte_len)[::-1] 340 assert len(ab) in [48, 64, 96] 341 return ab 342 343 344def sign_data(args): 345 if args.keyfile: 346 _check_output_is_not_input(args.keyfile, args.output) 347 _check_output_is_not_input(args.datafile, args.output) 348 if args.version == "1": 349 return sign_secure_boot_v1(args) 350 elif args.version == "2": 351 return sign_secure_boot_v2(args) 352 353 354def sign_secure_boot_v1(args): 355 """ 356 Sign a data file with a ECDSA private key, append binary signature to file contents 357 """ 358 binary_content = args.datafile.read() 359 360 if args.hsm: 361 raise esptool.FatalError( 362 "Secure Boot V1 does not support signing using an " 363 "external Hardware Security Module (HSM)" 364 ) 365 366 if args.signature: 367 print("Pre-calculated signatures found") 368 if len(args.pub_key) > 1: 369 raise esptool.FatalError("Secure Boot V1 only supports one signing key") 370 signature = args.signature[0].read() 371 # get verifying/public key 372 vk = _load_ecdsa_verifying_key(args.pub_key[0]) 373 else: 374 if len(args.keyfile) > 1: 375 raise esptool.FatalError("Secure Boot V1 only supports one signing key") 376 sk = _load_ecdsa_signing_key(args.keyfile[0]) 377 378 # calculate signature of binary data 379 signature = sk.sign_deterministic(binary_content, hashlib.sha256) 380 # get verifying/public key 381 vk = sk.get_verifying_key() 382 383 # back-verify signature 384 vk.verify(signature, binary_content, hashlib.sha256) # throws exception on failure 385 if args.output is None or os.path.abspath(args.output) == os.path.abspath( 386 args.datafile.name 387 ): # append signature to input file 388 args.datafile.close() 389 outfile = open(args.datafile.name, "ab") 390 else: # write file & signature to new file 391 outfile = open(args.output, "wb") 392 outfile.write(binary_content) 393 outfile.write( 394 struct.pack("I", 0) 395 ) # Version indicator, allow for different curves/formats later 396 outfile.write(signature) 397 outfile.close() 398 print("Signed %d bytes of data from %s" % (len(binary_content), args.datafile.name)) 399 400 401def sign_secure_boot_v2(args): 402 """ 403 Sign a firmware app image with an RSA private key using RSA-PSS, 404 or ECDSA private key using P192 or P256 or P384. 405 406 Write output file with a Secure Boot V2 header appended. 407 """ 408 SIG_BLOCK_MAX_COUNT = 3 409 contents = args.datafile.read() 410 sig_block_num = 0 411 signature_sector = b"" 412 413 signature = args.signature 414 pub_key = args.pub_key 415 416 if len(contents) % SECTOR_SIZE != 0: 417 if args.signature: 418 raise esptool.FatalError( 419 "Secure Boot V2 requires the signature block to start " 420 "from a 4KB aligned sector " 421 "but the datafile supplied is not sector aligned." 422 ) 423 else: 424 pad_by = SECTOR_SIZE - (len(contents) % SECTOR_SIZE) 425 print( 426 f"Padding data contents by {pad_by} bytes " 427 "so signature sector aligns at sector boundary" 428 ) 429 contents += b"\xff" * pad_by 430 431 elif args.append_signatures: 432 while sig_block_num < SIG_BLOCK_MAX_COUNT: 433 sig_block = validate_signature_block(contents, sig_block_num) 434 if sig_block is None: 435 break 436 signature_sector += ( 437 sig_block # Signature sector is populated with already valid blocks 438 ) 439 sig_block_num += 1 440 441 if len(signature_sector) % SIG_BLOCK_SIZE != 0: 442 raise esptool.FatalError("Incorrect signature sector size") 443 444 if sig_block_num == 0: 445 print( 446 "No valid signature blocks found. " 447 "Discarding --append-signature and proceeding to sign the image afresh." 448 ) 449 else: 450 print( 451 f"{sig_block_num} valid signature block(s) already present " 452 "in the signature sector." 453 ) 454 if sig_block_num == SIG_BLOCK_MAX_COUNT: 455 raise esptool.FatalError( 456 f"Upto {SIG_BLOCK_MAX_COUNT} signature blocks are supported. " 457 "(For ESP32-ECO3 only 1 signature block is supported)" 458 ) 459 460 # Signature stripped off the content 461 # (the legitimate blocks are included in signature_sector) 462 contents = contents[: len(contents) - SECTOR_SIZE] 463 464 if args.hsm: 465 if args.hsm_config is None: 466 raise esptool.FatalError( 467 "Config file is required to generate signature using an external HSM." 468 ) 469 import espsecure.esp_hsm_sign as hsm 470 471 try: 472 config = hsm.read_hsm_config(args.hsm_config) 473 except Exception as e: 474 raise esptool.FatalError(f"Incorrect HSM config file format ({e})") 475 if pub_key is None: 476 pub_key = extract_pubkey_from_hsm(config) 477 signature = generate_signature_using_hsm(config, contents) 478 479 if signature: 480 print("Pre-calculated signatures found") 481 key_count = len(pub_key) 482 if len(signature) != key_count: 483 raise esptool.FatalError( 484 f"Number of public keys ({key_count}) not equal to " 485 f"the number of signatures {len(signature)}." 486 ) 487 else: 488 key_count = len(args.keyfile) 489 490 empty_signature_blocks = SIG_BLOCK_MAX_COUNT - sig_block_num 491 if key_count > empty_signature_blocks: 492 raise esptool.FatalError( 493 f"Number of keys({key_count}) more than the empty signature blocks." 494 f"({empty_signature_blocks})" 495 ) 496 497 print(f"{key_count} signing key(s) found.") 498 499 # Generate signature block using pre-calculated signatures 500 if signature: 501 signature_block = generate_signature_block_using_pre_calculated_signature( 502 signature, pub_key, contents 503 ) 504 # Generate signature block by signing using private keys 505 else: 506 signature_block = generate_signature_block_using_private_key( 507 args.keyfile, contents 508 ) 509 510 if signature_block is None or len(signature_block) == 0: 511 raise esptool.FatalError("Signature Block generation failed") 512 513 signature_sector += signature_block 514 515 if ( 516 len(signature_sector) < 0 517 and len(signature_sector) > SIG_BLOCK_SIZE * 3 518 and len(signature_sector) % SIG_BLOCK_SIZE != 0 519 ): 520 raise esptool.FatalError("Incorrect signature sector generation") 521 522 total_sig_blocks = len(signature_sector) // SIG_BLOCK_SIZE 523 524 # Pad signature_sector to sector 525 signature_sector = signature_sector + ( 526 b"\xff" * (SECTOR_SIZE - len(signature_sector)) 527 ) 528 if len(signature_sector) != SECTOR_SIZE: 529 raise esptool.FatalError("Incorrect signature sector size") 530 531 # Write to output file, or append to existing file 532 if args.output is None: 533 args.datafile.close() 534 args.output = args.datafile.name 535 with open(args.output, "wb") as f: 536 f.write(contents + signature_sector) 537 print( 538 f"Signed {len(contents)} bytes of data from {args.datafile.name}. " 539 f"Signature sector now has {total_sig_blocks} signature blocks." 540 ) 541 542 543def generate_signature_using_hsm(config, contents): 544 import espsecure.esp_hsm_sign as hsm 545 546 session = hsm.establish_session(config) 547 # get the private key 548 private_key = hsm.get_privkey_info(session, config) 549 # Sign payload 550 signature = hsm.sign_payload(private_key, contents) 551 hsm.close_connection(session) 552 temp_signature_file = tempfile.TemporaryFile() 553 temp_signature_file.write(signature) 554 temp_signature_file.seek(0) 555 return [temp_signature_file] 556 557 558def generate_signature_block_using_pre_calculated_signature( 559 signature, pub_key, contents 560): 561 signature_blocks = b"" 562 for sig, pk in zip(signature, pub_key): 563 try: 564 public_key = _get_sbv2_pub_key(pk) 565 signature = sig.read() 566 if isinstance(public_key, rsa.RSAPublicKey): 567 # Calculate digest of data file 568 digest = _sha256_digest(contents) 569 # RSA signature 570 rsa_primitives = _get_sbv2_rsa_primitives(public_key) 571 # Verify the signature 572 public_key.verify( 573 signature, 574 digest, 575 padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=32), 576 utils.Prehashed(hashes.SHA256()), 577 ) 578 579 signature_block = generate_rsa_signature_block( 580 digest, rsa_primitives, signature 581 ) 582 else: 583 # ECDSA signature 584 numbers = public_key.public_numbers() 585 if isinstance(numbers.curve, ec.SECP192R1): 586 curve_len = 192 587 curve_id = CURVE_ID_P192 588 hash_type = hashes.SHA256() 589 digest = _sha256_digest(contents) 590 elif isinstance(numbers.curve, ec.SECP256R1): 591 curve_len = 256 592 curve_id = CURVE_ID_P256 593 hash_type = hashes.SHA256() 594 digest = _sha256_digest(contents) 595 elif isinstance(numbers.curve, ec.SECP384R1): 596 curve_len = 384 597 curve_id = CURVE_ID_P384 598 hash_type = hashes.SHA384() 599 digest = _sha384_digest(contents) 600 else: 601 raise esptool.FatalError("Invalid ECDSA curve instance.") 602 603 # Verify the signature 604 public_key.verify( 605 signature, digest, ec.ECDSA(utils.Prehashed(hash_type)) 606 ) 607 608 pubkey_point = _microecc_format(numbers.x, numbers.y, curve_len) 609 r, s = utils.decode_dss_signature(signature) 610 signature_rs = _microecc_format(r, s, curve_len) 611 signature_block = generate_ecdsa_signature_block( 612 digest, curve_id, pubkey_point, signature_rs 613 ) 614 except exceptions.InvalidSignature: 615 raise esptool.FatalError( 616 "Signature verification failed: Invalid Signature\n" 617 "The pre-calculated signature has not been signed " 618 "using the given public key" 619 ) 620 signature_block += struct.pack("<I", zlib.crc32(signature_block) & 0xFFFFFFFF) 621 signature_block += b"\x00" * 16 # padding 622 623 if len(signature_block) != SIG_BLOCK_SIZE: 624 raise esptool.FatalError("Incorrect signature block size") 625 626 signature_blocks += signature_block 627 return signature_blocks 628 629 630def generate_signature_block_using_private_key(keyfiles, contents): 631 signature_blocks = b"" 632 for keyfile in keyfiles: 633 private_key = _load_sbv2_signing_key(keyfile.read()) 634 635 # Sign 636 if isinstance(private_key, rsa.RSAPrivateKey): 637 digest = _sha256_digest(contents) 638 # RSA signature 639 signature = private_key.sign( 640 digest, 641 padding.PSS( 642 mgf=padding.MGF1(hashes.SHA256()), 643 salt_length=32, 644 ), 645 utils.Prehashed(hashes.SHA256()), 646 ) 647 rsa_primitives = _get_sbv2_rsa_primitives(private_key.public_key()) 648 signature_block = generate_rsa_signature_block( 649 digest, rsa_primitives, signature 650 ) 651 else: 652 numbers = private_key.public_key().public_numbers() 653 if isinstance(private_key.curve, ec.SECP192R1): 654 curve_len = 192 655 curve_id = CURVE_ID_P192 656 hash_type = hashes.SHA256() 657 digest = _sha256_digest(contents) 658 elif isinstance(numbers.curve, ec.SECP256R1): 659 curve_len = 256 660 curve_id = CURVE_ID_P256 661 hash_type = hashes.SHA256() 662 digest = _sha256_digest(contents) 663 elif isinstance(numbers.curve, ec.SECP384R1): 664 curve_len = 384 665 curve_id = CURVE_ID_P384 666 hash_type = hashes.SHA384() 667 digest = _sha384_digest(contents) 668 else: 669 raise esptool.FatalError("Invalid ECDSA curve instance.") 670 671 # ECDSA signatures 672 signature = private_key.sign(digest, ec.ECDSA(utils.Prehashed(hash_type))) 673 674 pubkey_point = _microecc_format(numbers.x, numbers.y, curve_len) 675 676 r, s = utils.decode_dss_signature(signature) 677 signature_rs = _microecc_format(r, s, curve_len) 678 signature_block = generate_ecdsa_signature_block( 679 digest, curve_id, pubkey_point, signature_rs 680 ) 681 682 signature_block += struct.pack("<I", zlib.crc32(signature_block) & 0xFFFFFFFF) 683 signature_block += b"\x00" * 16 # padding 684 685 if len(signature_block) != SIG_BLOCK_SIZE: 686 raise esptool.FatalError("Incorrect signature block size") 687 688 signature_blocks += signature_block 689 return signature_blocks 690 691 692def generate_rsa_signature_block(digest, rsa_primitives, signature): 693 """ 694 Encode in rsa signature block format 695 696 Note: the [::-1] is to byte swap all of the bignum 697 values (signatures, coefficients) to little endian 698 for use with the RSA peripheral, rather than big endian 699 which is conventionally used for RSA. 700 """ 701 signature_block = struct.pack( 702 "<BBxx32s384sI384sI384s", 703 SIG_BLOCK_MAGIC, 704 SIG_BLOCK_VERSION_RSA, 705 digest, 706 int_to_bytes(rsa_primitives.n)[::-1], 707 rsa_primitives.e, 708 int_to_bytes(rsa_primitives.rinv)[::-1], 709 rsa_primitives.m & 0xFFFFFFFF, 710 signature[::-1], 711 ) 712 return signature_block 713 714 715def generate_ecdsa_signature_block(digest, curve_id, pubkey_point, signature_rs): 716 """ 717 Encode in rsa signature block format 718 719 # block is padded out to the much larger size 720 # of the RSA version of this structure 721 """ 722 723 if curve_id in [CURVE_ID_P192, CURVE_ID_P256]: 724 signature_block = struct.pack( 725 "<BBBx32sB64s64s1031x", 726 SIG_BLOCK_MAGIC, 727 SIG_BLOCK_VERSION_ECDSA, 728 ECDSA_SHA_256, 729 digest, 730 curve_id, 731 pubkey_point, 732 signature_rs, 733 ) 734 elif curve_id == CURVE_ID_P384: 735 signature_block = struct.pack( 736 "<BBBx48sB96s96s951x", 737 SIG_BLOCK_MAGIC, 738 SIG_BLOCK_VERSION_ECDSA, 739 ECDSA_SHA_384, 740 digest, 741 curve_id, 742 pubkey_point, 743 signature_rs, 744 ) 745 else: 746 raise esptool.FatalError( 747 "Invalid ECDSA curve ID detected while generating ECDSA signature block." 748 ) 749 750 return signature_block 751 752 753def verify_signature(args): 754 if args.version == "1": 755 return verify_signature_v1(args) 756 elif args.version == "2": 757 return verify_signature_v2(args) 758 759 760def verify_signature_v1(args): 761 """Verify a previously signed binary image, using the ECDSA public key""" 762 key_data = args.keyfile.read() 763 if b"-BEGIN EC PRIVATE KEY" in key_data: 764 sk = ecdsa.SigningKey.from_pem(key_data) 765 vk = sk.get_verifying_key() 766 elif b"-BEGIN PUBLIC KEY" in key_data: 767 vk = ecdsa.VerifyingKey.from_pem(key_data) 768 elif len(key_data) == 64: 769 vk = ecdsa.VerifyingKey.from_string(key_data, curve=ecdsa.NIST256p) 770 else: 771 raise esptool.FatalError( 772 "Verification key does not appear to be an EC key in PEM format " 773 "or binary EC public key data. Unsupported" 774 ) 775 776 if vk.curve != ecdsa.NIST256p: 777 raise esptool.FatalError( 778 "Public key uses incorrect curve. ESP32 Secure Boot only supports " 779 "NIST256p (openssl calls this curve 'prime256v1" 780 ) 781 782 binary_content = args.datafile.read() 783 data = binary_content[0:-68] 784 sig_version, signature = struct.unpack("I64s", binary_content[-68:]) 785 if sig_version != 0: 786 raise esptool.FatalError( 787 "Signature block has version %d. This version of espsecure " 788 "only supports version 0." % sig_version 789 ) 790 print("Verifying %d bytes of data" % len(data)) 791 try: 792 if vk.verify(signature, data, hashlib.sha256): 793 print("Signature is valid") 794 else: 795 raise esptool.FatalError("Signature is not valid") 796 except ecdsa.keys.BadSignatureError: 797 raise esptool.FatalError("Signature is not valid") 798 799 800def validate_signature_block(image_content, sig_blk_num): 801 offset = -SECTOR_SIZE + sig_blk_num * SIG_BLOCK_SIZE 802 sig_blk = image_content[offset : offset + SIG_BLOCK_SIZE] 803 assert len(sig_blk) == SIG_BLOCK_SIZE 804 805 # note: in case of ECDSA key, the exact fields in the middle are wrong 806 # (but unused here) 807 magic, version, _, _, _, _, _, _, blk_crc = struct.unpack( 808 "<BBxx32s384sI384sI384sI16x", sig_blk 809 ) 810 811 # The signature block(1216 bytes) consists of the data part(1196 bytes) 812 # followed by a crc32(4 byte) and a 16 byte pad. 813 calc_crc = zlib.crc32(sig_blk[:1196]) 814 815 is_invalid_block = magic != SIG_BLOCK_MAGIC 816 is_invalid_block |= version not in [SIG_BLOCK_VERSION_RSA, SIG_BLOCK_VERSION_ECDSA] 817 818 if is_invalid_block or blk_crc != calc_crc & 0xFFFFFFFF: # Signature block invalid 819 return None 820 key_type = "RSA" if version == SIG_BLOCK_VERSION_RSA else "ECDSA" 821 print(f"Signature block {sig_blk_num} is valid ({key_type}).") 822 return sig_blk 823 824 825def verify_signature_v2(args): 826 """Verify a previously signed binary image, using the RSA or ECDSA public key""" 827 828 keyfile = args.keyfile 829 if args.hsm: 830 if args.hsm_config is None: 831 raise esptool.FatalError( 832 "Config file is required to extract public key from an external HSM." 833 ) 834 import espsecure.esp_hsm_sign as hsm 835 836 try: 837 config = hsm.read_hsm_config(args.hsm_config) 838 except Exception as e: 839 raise esptool.FatalError(f"Incorrect HSM config file format ({e})") 840 # get public key from HSM 841 keyfile = extract_pubkey_from_hsm(config)[0] 842 843 vk = _get_sbv2_pub_key(keyfile) 844 845 if isinstance(vk, rsa.RSAPublicKey): 846 SIG_BLOCK_MAX_COUNT = 3 847 elif isinstance(vk, ec.EllipticCurvePublicKey): 848 SIG_BLOCK_MAX_COUNT = 1 849 850 image_content = args.datafile.read() 851 if len(image_content) < SECTOR_SIZE or len(image_content) % SECTOR_SIZE != 0: 852 raise esptool.FatalError( 853 "Invalid datafile. Data size should be non-zero & a multiple of 4096." 854 ) 855 856 valid = False 857 858 for sig_blk_num in range(SIG_BLOCK_MAX_COUNT): 859 sig_blk = validate_signature_block(image_content, sig_blk_num) 860 if sig_blk is None: 861 print(f"Signature block {sig_blk_num} invalid. Skipping.") 862 continue 863 _, version, ecdsa_sha_version = struct.unpack("<BBBx", sig_blk[:4]) 864 865 if version == SIG_BLOCK_VERSION_ECDSA and ecdsa_sha_version == ECDSA_SHA_384: 866 blk_digest = struct.unpack("<48s", sig_blk[4:52])[0] 867 digest = _sha384_digest(image_content[:-SECTOR_SIZE]) 868 else: 869 blk_digest = struct.unpack("<32s", sig_blk[4:36])[0] 870 digest = _sha256_digest(image_content[:-SECTOR_SIZE]) 871 872 if blk_digest != digest: 873 raise esptool.FatalError( 874 "Signature block image digest does not match " 875 f"the actual image digest {digest}. Expected {blk_digest}." 876 ) 877 878 try: 879 if isinstance(vk, rsa.RSAPublicKey): 880 _, _, _, _, signature, _ = struct.unpack( 881 "<384sI384sI384sI16x", sig_blk[36:] 882 ) 883 vk.verify( 884 signature[::-1], 885 digest, 886 padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=32), 887 utils.Prehashed(hashes.SHA256()), 888 ) 889 else: 890 if ecdsa_sha_version == ECDSA_SHA_256: 891 curve_id, _pubkey, encoded_rs = struct.unpack( 892 "B64s64s1031x4x16x", sig_blk[36:] 893 ) 894 elif ecdsa_sha_version == ECDSA_SHA_384: 895 curve_id, _pubkey, encoded_rs = struct.unpack( 896 "B96s96s951x4x16x", sig_blk[52:] 897 ) 898 899 assert curve_id in (CURVE_ID_P192, CURVE_ID_P256, CURVE_ID_P384) 900 901 # length of each number in the keypair 902 if curve_id == CURVE_ID_P192: 903 keylen = 24 904 hash_type = hashes.SHA256() 905 elif curve_id == CURVE_ID_P256: 906 keylen = 32 907 hash_type = hashes.SHA256() 908 elif curve_id == CURVE_ID_P384: 909 keylen = 48 910 hash_type = hashes.SHA384() 911 912 r = int.from_bytes(encoded_rs[:keylen], "little") 913 s = int.from_bytes(encoded_rs[keylen : keylen * 2], "little") 914 915 signature = utils.encode_dss_signature(r, s) 916 917 vk.verify(signature, digest, ec.ECDSA(utils.Prehashed(hash_type))) 918 919 key_type = "RSA" if isinstance(vk, rsa.RSAPublicKey) else "ECDSA" 920 921 print( 922 f"Signature block {sig_blk_num} verification successful using " 923 f"the supplied key ({key_type})." 924 ) 925 valid = True 926 927 except exceptions.InvalidSignature: 928 print( 929 f"Signature block {sig_blk_num} is not signed by the supplied key. " 930 "Checking the next block" 931 ) 932 continue 933 934 if not valid: 935 raise esptool.FatalError( 936 "Checked all blocks. Signature could not be verified with the provided key." 937 ) 938 939 940def extract_public_key(args): 941 _check_output_is_not_input(args.keyfile, args.public_keyfile) 942 if args.version == "1": 943 """ 944 Load an ECDSA private key and extract the embedded public key 945 as raw binary data. 946 """ 947 sk = _load_ecdsa_signing_key(args.keyfile) 948 vk = sk.get_verifying_key() 949 args.public_keyfile.write(vk.to_string()) 950 elif args.version == "2": 951 """ 952 Load an RSA or an ECDSA private key and extract the public key 953 as raw binary data. 954 """ 955 sk = _load_sbv2_signing_key(args.keyfile.read()) 956 vk = sk.public_key().public_bytes( 957 encoding=serialization.Encoding.PEM, 958 format=serialization.PublicFormat.SubjectPublicKeyInfo, 959 ) 960 args.public_keyfile.write(vk) 961 print( 962 "%s public key extracted to %s" % (args.keyfile.name, args.public_keyfile.name) 963 ) 964 965 966def extract_pubkey_from_hsm(config): 967 import espsecure.esp_hsm_sign as hsm 968 969 session = hsm.establish_session(config) 970 # get public key from HSM 971 public_key = hsm.get_pubkey(session, config) 972 hsm.close_connection(session) 973 974 pem = public_key.public_bytes( 975 encoding=serialization.Encoding.PEM, 976 format=serialization.PublicFormat.SubjectPublicKeyInfo, 977 ) 978 temp_pub_key_file = tempfile.TemporaryFile() 979 temp_pub_key_file.write(pem) 980 temp_pub_key_file.seek(0) 981 return [temp_pub_key_file] 982 983 984def _sha256_digest(data): 985 digest = hashlib.sha256() 986 digest.update(data) 987 return digest.digest() 988 989 990def _sha384_digest(contents): 991 # Calculate digest of data file 992 digest = hashlib.sha384() 993 digest.update(contents) 994 return digest.digest() 995 996 997def signature_info_v2(args): 998 """ 999 Validates the signature block and prints the RSA/ECDSA public key 1000 digest for valid blocks 1001 """ 1002 SIG_BLOCK_MAX_COUNT = 3 1003 1004 image_content = args.datafile.read() 1005 if len(image_content) < SECTOR_SIZE or len(image_content) % SECTOR_SIZE != 0: 1006 raise esptool.FatalError( 1007 "Invalid datafile. Data size should be non-zero & a multiple of 4096." 1008 ) 1009 1010 for sig_blk_num in range(SIG_BLOCK_MAX_COUNT): 1011 sig_blk = validate_signature_block(image_content, sig_blk_num) 1012 if sig_blk is None: 1013 print( 1014 "Signature block %d absent/invalid. Skipping checking next blocks." 1015 % sig_blk_num 1016 ) 1017 return 1018 1019 _, version, ecdsa_sha_version = struct.unpack("<BBBx", sig_blk[:4]) 1020 1021 if version == SIG_BLOCK_VERSION_ECDSA and ecdsa_sha_version == ECDSA_SHA_384: 1022 sig_data = struct.unpack("<BBxx48s1164x", sig_blk) 1023 digest = _sha384_digest(image_content[:-SECTOR_SIZE]) 1024 else: 1025 sig_data = struct.unpack("<BBxx32s1180x", sig_blk) 1026 digest = _sha256_digest(image_content[:-SECTOR_SIZE]) 1027 1028 if sig_data[2] != digest: 1029 raise esptool.FatalError( 1030 "Digest in signature block %d doesn't match the image digest." 1031 % (sig_blk_num) 1032 ) 1033 1034 offset = -SECTOR_SIZE + sig_blk_num * SIG_BLOCK_SIZE 1035 sig_blk = image_content[offset : offset + SIG_BLOCK_SIZE] 1036 if sig_data[1] == SIG_BLOCK_VERSION_RSA: 1037 key_digest = _sha256_digest(sig_blk[36:812]) 1038 elif sig_data[1] == SIG_BLOCK_VERSION_ECDSA: 1039 if ecdsa_sha_version == ECDSA_SHA_384: 1040 key_digest = _sha256_digest(sig_blk[52:149]) 1041 else: 1042 key_digest = _sha256_digest(sig_blk[36:101]) 1043 else: 1044 raise esptool.FatalError( 1045 "Unsupported scheme in signature block %d" % (sig_blk_num) 1046 ) 1047 1048 print( 1049 "Public key digest for block %d: %s" 1050 % (sig_blk_num, " ".join("{:02x}".format(c) for c in bytearray(key_digest))) 1051 ) 1052 1053 1054def _digest_sbv2_public_key(keyfile): 1055 public_key = _get_sbv2_pub_key(keyfile) 1056 1057 if isinstance(public_key, rsa.RSAPublicKey): 1058 rsa_primitives = _get_sbv2_rsa_primitives(public_key) 1059 1060 # Encode in the same way it is represented in the signature block 1061 # 1062 # Note: the [::-1] is to byte swap all of the bignum 1063 # values (signatures, coefficients) to little endian 1064 # for use with the RSA peripheral, rather than big endian 1065 # which is conventionally used for RSA. 1066 binary_format = struct.pack( 1067 "<384sI384sI", 1068 int_to_bytes(rsa_primitives.n)[::-1], 1069 rsa_primitives.e, 1070 int_to_bytes(rsa_primitives.rinv)[::-1], 1071 rsa_primitives.m & 0xFFFFFFFF, 1072 ) 1073 else: # ECC public key 1074 numbers = public_key.public_numbers() 1075 if isinstance(public_key.curve, ec.SECP192R1): 1076 curve_len = 192 1077 curve_id = CURVE_ID_P192 1078 elif isinstance(public_key.curve, ec.SECP256R1): 1079 curve_len = 256 1080 curve_id = CURVE_ID_P256 1081 elif isinstance(public_key.curve, ec.SECP384R1): 1082 curve_len = 384 1083 curve_id = CURVE_ID_P384 1084 1085 pubkey_point = _microecc_format(numbers.x, numbers.y, curve_len) 1086 1087 if curve_id == CURVE_ID_P384: 1088 binary_format = struct.pack( 1089 "<B96s", 1090 curve_id, 1091 pubkey_point, 1092 ) 1093 else: 1094 binary_format = struct.pack( 1095 "<B64s", 1096 curve_id, 1097 pubkey_point, 1098 ) 1099 1100 return hashlib.sha256(binary_format).digest() 1101 1102 1103def digest_sbv2_public_key(args): 1104 _check_output_is_not_input(args.keyfile, args.output) 1105 public_key_digest = _digest_sbv2_public_key(args.keyfile) 1106 with open(args.output, "wb") as f: 1107 print( 1108 "Writing the public key digest of %s to %s." 1109 % (args.keyfile.name, args.output) 1110 ) 1111 f.write(public_key_digest) 1112 1113 1114def digest_rsa_public_key(args): 1115 # Kept for compatibility purpose 1116 digest_sbv2_public_key(args) 1117 1118 1119def digest_private_key(args): 1120 _check_output_is_not_input(args.keyfile, args.digest_file) 1121 sk = _load_ecdsa_signing_key(args.keyfile) 1122 repr(sk.to_string()) 1123 digest = hashlib.sha256() 1124 digest.update(sk.to_string()) 1125 result = digest.digest() 1126 if args.keylen == 192: 1127 result = result[0:24] 1128 args.digest_file.write(result) 1129 print( 1130 "SHA-256 digest of private key %s%s written to %s" 1131 % ( 1132 args.keyfile.name, 1133 "" if args.keylen == 256 else " (truncated to 192 bits)", 1134 args.digest_file.name, 1135 ) 1136 ) 1137 1138 1139# flash encryption key tweaking pattern: the nth bit of the key is 1140# flipped if the kth bit in the flash offset is set, where mapping 1141# from n to k is provided by this list of 'n' bit offsets (range k) 1142# fmt: off 1143_FLASH_ENCRYPTION_TWEAK_PATTERN = [ 1144 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 1145 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 1146 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 1147 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 1148 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 1149 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 1150 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 1151 12, 11, 10, 9, 8, 7, 6, 5, 1152 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 1153 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 1154 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 1155 10, 9, 8, 7, 6, 5, 1156 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 1157 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 1158 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 1159 8, 7, 6, 5 1160] 1161assert len(_FLASH_ENCRYPTION_TWEAK_PATTERN) == 256 1162# fmt: on 1163 1164 1165def _flash_encryption_tweak_range(flash_crypt_config=0xF): 1166 """Return a list of the bit indexes that the "key tweak" applies to, 1167 as determined by the FLASH_CRYPT_CONFIG 4 bit efuse value. 1168 """ 1169 tweak_range = [] 1170 if (flash_crypt_config & 1) != 0: 1171 tweak_range += range(67) 1172 if (flash_crypt_config & 2) != 0: 1173 tweak_range += range(67, 132) 1174 if (flash_crypt_config & 4) != 0: 1175 tweak_range += range(132, 195) 1176 if (flash_crypt_config & 8) != 0: 1177 tweak_range += range(195, 256) 1178 return tweak_range 1179 1180 1181def _flash_encryption_tweak_range_bits(flash_crypt_config=0xF): 1182 """Return bits (in reverse order) that the "key tweak" applies to, 1183 as determined by the FLASH_CRYPT_CONFIG 4 bit efuse value. 1184 """ 1185 tweak_range = 0 1186 if (flash_crypt_config & 1) != 0: 1187 tweak_range |= ( 1188 0xFFFFFFFFFFFFFFFFE00000000000000000000000000000000000000000000000 1189 ) 1190 if (flash_crypt_config & 2) != 0: 1191 tweak_range |= ( 1192 0x00000000000000001FFFFFFFFFFFFFFFF0000000000000000000000000000000 1193 ) 1194 if (flash_crypt_config & 4) != 0: 1195 tweak_range |= ( 1196 0x000000000000000000000000000000000FFFFFFFFFFFFFFFE000000000000000 1197 ) 1198 if (flash_crypt_config & 8) != 0: 1199 tweak_range |= ( 1200 0x0000000000000000000000000000000000000000000000001FFFFFFFFFFFFFFF 1201 ) 1202 return tweak_range 1203 1204 1205# Forward bit order masks 1206mul1 = 0x0000200004000080000004000080001000000200004000080000040000800010 1207mul2 = 0x0000000000000000200000000000000010000000000000002000000000000001 1208 1209mul1_mask = 0xFFFFFFFFFFFFFF801FFFFFFFFFFFFFF00FFFFFFFFFFFFFF81FFFFFFFFFFFFFF0 1210mul2_mask = 0x000000000000007FE00000000000000FF000000000000007E00000000000000F 1211 1212 1213def _flash_encryption_tweak_key(key, offset, tweak_range): 1214 """Apply XOR "tweak" values to the key, derived from flash offset 1215 'offset'. This matches the ESP32 hardware flash encryption. 1216 1217 tweak_range is a list of bit indexes to apply the tweak to, as 1218 generated by _flash_encryption_tweak_range() from the 1219 FLASH_CRYPT_CONFIG efuse value. 1220 1221 Return tweaked key 1222 """ 1223 addr = offset >> 5 1224 key ^= ((mul1 * addr) | ((mul2 * addr) & mul2_mask)) & tweak_range 1225 return int.to_bytes(key, length=32, byteorder="big", signed=False) 1226 1227 1228def generate_flash_encryption_key(args): 1229 print("Writing %d random bits to key file %s" % (args.keylen, args.key_file.name)) 1230 args.key_file.write(os.urandom(args.keylen // 8)) 1231 1232 1233def _flash_encryption_operation_esp32( 1234 output_file, input_file, flash_address, keyfile, flash_crypt_conf, do_decrypt 1235): 1236 key = _load_hardware_key(keyfile) 1237 1238 if flash_address % 16 != 0: 1239 raise esptool.FatalError( 1240 "Starting flash address 0x%x must be a multiple of 16" % flash_address 1241 ) 1242 1243 if flash_crypt_conf == 0: 1244 print("WARNING: Setting FLASH_CRYPT_CONF to zero is not recommended") 1245 1246 tweak_range = _flash_encryption_tweak_range_bits(flash_crypt_conf) 1247 key = int.from_bytes(key, byteorder="big", signed=False) 1248 1249 backend = default_backend() 1250 1251 cipher = None 1252 block_offs = flash_address 1253 while True: 1254 block = input_file.read(16) 1255 if len(block) == 0: 1256 break 1257 elif len(block) < 16: 1258 if do_decrypt: 1259 raise esptool.FatalError("Data length is not a multiple of 16 bytes") 1260 pad = 16 - len(block) 1261 block = block + os.urandom(pad) 1262 print( 1263 "Note: Padding with %d bytes of random data " 1264 "(encrypted data must be multiple of 16 bytes long)" % pad 1265 ) 1266 1267 if block_offs % 32 == 0 or cipher is None: 1268 # each bit of the flash encryption key is XORed with tweak bits 1269 # derived from the offset of 32 byte block of flash 1270 block_key = _flash_encryption_tweak_key(key, block_offs, tweak_range) 1271 1272 if cipher is None: # first pass 1273 cipher = Cipher(algorithms.AES(block_key), modes.ECB(), backend=backend) 1274 1275 # note AES is used inverted for flash encryption, so 1276 # "decrypting" flash uses AES encrypt algorithm and vice 1277 # versa. (This does not weaken AES.) 1278 actor = cipher.encryptor() if do_decrypt else cipher.decryptor() 1279 else: 1280 # performance hack: changing the key using pyca-cryptography API 1281 # requires recreating'actor'. 1282 # With openssl backend, this re-initializes the openssl cipher context. 1283 # To save some time, manually call EVP_CipherInit_ex() in the openssl 1284 # backend to update the key. 1285 # If it fails, fall back to recreating the entire context via public API 1286 try: 1287 backend = actor._ctx._backend 1288 res = backend._lib.EVP_CipherInit_ex( 1289 actor._ctx._ctx, 1290 backend._ffi.NULL, 1291 backend._ffi.NULL, 1292 backend._ffi.from_buffer(block_key), 1293 backend._ffi.NULL, 1294 actor._ctx._operation, 1295 ) 1296 backend.openssl_assert(res != 0) 1297 except AttributeError: 1298 # backend is not an openssl backend, or implementation has changed: 1299 # fall back to the slow safe version 1300 cipher.algorithm.key = block_key 1301 actor = cipher.encryptor() if do_decrypt else cipher.decryptor() 1302 1303 block = block[::-1] # reverse input block byte order 1304 block = actor.update(block) 1305 1306 output_file.write(block[::-1]) # reverse output block byte order 1307 block_offs += 16 1308 1309 1310def _flash_encryption_operation_aes_xts( 1311 output_file, input_file, flash_address, keyfile, do_decrypt 1312): 1313 """ 1314 Apply the AES-XTS algorithm with the hardware addressing scheme used by Espressif 1315 1316 key = AES-XTS key (32 or 64 bytes) 1317 flash_address = address in flash to encrypt at. Must be multiple of 16 bytes. 1318 indata = Data to encrypt/decrypt. Must be multiple of 16 bytes. 1319 encrypt = True to Encrypt indata, False to decrypt indata. 1320 1321 Returns a bitstring of the ciphertext or plaintext result. 1322 """ 1323 1324 backend = default_backend() 1325 key = _load_hardware_key(keyfile) 1326 indata = input_file.read() 1327 1328 if flash_address % 16 != 0: 1329 raise esptool.FatalError( 1330 "Starting flash address 0x%x must be a multiple of 16" % flash_address 1331 ) 1332 1333 if len(indata) % 16 != 0: 1334 raise esptool.FatalError( 1335 "Input data length (%d) must be a multiple of 16" % len(indata) 1336 ) 1337 1338 if len(indata) == 0: 1339 raise esptool.FatalError("Input data must be longer than 0") 1340 1341 # left pad for a 1024-bit aligned address 1342 pad_left = flash_address % 0x80 1343 indata = (b"\x00" * pad_left) + indata 1344 1345 # right pad for full 1024-bit blocks 1346 pad_right = len(indata) % 0x80 1347 if pad_right > 0: 1348 pad_right = 0x80 - pad_right 1349 indata = indata + (b"\x00" * pad_right) 1350 1351 inblocks = _split_blocks(indata, 0x80) # split into 1024 bit blocks 1352 1353 output = [] 1354 for inblock in inblocks: # for each block 1355 tweak = struct.pack("<I", (flash_address & ~0x7F)) + (b"\x00" * 12) 1356 flash_address += 0x80 # for next block 1357 1358 if len(tweak) != 16: 1359 raise esptool.FatalError( 1360 "Length of tweak must be 16, was {}".format(len(tweak)) 1361 ) 1362 1363 cipher = Cipher(algorithms.AES(key), modes.XTS(tweak), backend=backend) 1364 encryptor = cipher.decryptor() if do_decrypt else cipher.encryptor() 1365 1366 inblock = inblock[::-1] # reverse input 1367 outblock = encryptor.update(inblock) # standard algo 1368 output.append(outblock[::-1]) # reverse output 1369 1370 output = b"".join(output) 1371 1372 # undo any padding we applied to the input 1373 if pad_right != 0: 1374 output = output[:-pad_right] 1375 if pad_left != 0: 1376 output = output[pad_left:] 1377 1378 # output length matches original input 1379 if len(output) != len(indata) - pad_left - pad_right: 1380 raise esptool.FatalError( 1381 "Length of input data ({}) should match the output data ({})".format( 1382 len(indata) - pad_left - pad_right, len(output) 1383 ) 1384 ) 1385 1386 output_file.write(output) 1387 1388 1389def _split_blocks(text, block_len=16): 1390 """Take a bitstring, split it into chunks of "block_len" each""" 1391 assert len(text) % block_len == 0 1392 pos = 0 1393 while pos < len(text): 1394 yield text[pos : pos + block_len] 1395 pos = pos + block_len 1396 1397 1398def decrypt_flash_data(args): 1399 _check_output_is_not_input(args.keyfile, args.output) 1400 _check_output_is_not_input(args.encrypted_file, args.output) 1401 if args.aes_xts: 1402 return _flash_encryption_operation_aes_xts( 1403 args.output, args.encrypted_file, args.address, args.keyfile, True 1404 ) 1405 else: 1406 return _flash_encryption_operation_esp32( 1407 args.output, 1408 args.encrypted_file, 1409 args.address, 1410 args.keyfile, 1411 args.flash_crypt_conf, 1412 True, 1413 ) 1414 1415 1416def encrypt_flash_data(args): 1417 _check_output_is_not_input(args.keyfile, args.output) 1418 _check_output_is_not_input(args.plaintext_file, args.output) 1419 if args.aes_xts: 1420 return _flash_encryption_operation_aes_xts( 1421 args.output, args.plaintext_file, args.address, args.keyfile, False 1422 ) 1423 else: 1424 return _flash_encryption_operation_esp32( 1425 args.output, 1426 args.plaintext_file, 1427 args.address, 1428 args.keyfile, 1429 args.flash_crypt_conf, 1430 False, 1431 ) 1432 1433 1434def _samefile(p1, p2): 1435 return os.path.normcase(os.path.normpath(p1)) == os.path.normcase( 1436 os.path.normpath(p2) 1437 ) 1438 1439 1440def _check_output_is_not_input(input_file, output_file): 1441 i = getattr(input_file, "name", input_file) 1442 o = getattr(output_file, "name", output_file) 1443 # i & o should be string containing the path to files if espsecure 1444 # was invoked from command line 1445 # i & o still can be something else when espsecure was imported 1446 # and the functions used directly (e.g. io.BytesIO()) 1447 check_f = _samefile if isinstance(i, str) and isinstance(o, str) else operator.eq 1448 if check_f(i, o): 1449 raise esptool.FatalError( 1450 'The input "{}" and output "{}" should not be the same!'.format(i, o) 1451 ) 1452 1453 1454class OutFileType(object): 1455 """ 1456 This class is a replacement of argparse.FileType('wb'). 1457 It doesn't create a file immediately but only during thefirst write. 1458 This allows us to do some checking before, 1459 e.g. that we are not overwriting the input. 1460 1461 argparse.FileType('w')('-') returns STDOUT but argparse.FileType('wb') is not. 1462 1463 The file object is not closed on failure 1464 just like in the case of argparse.FileType('w'). 1465 """ 1466 1467 def __init__(self): 1468 self.path = None 1469 self.file_obj = None 1470 1471 def __call__(self, path): 1472 self.path = path 1473 return self 1474 1475 def __repr__(self): 1476 return "{}({})".format(type(self).__name__, self.path) 1477 1478 def write(self, payload): 1479 if len(payload) > 0: 1480 if not self.file_obj: 1481 self.file_obj = open(self.path, "wb") 1482 self.file_obj.write(payload) 1483 1484 def close(self): 1485 if self.file_obj: 1486 self.file_obj.close() 1487 self.file_obj = None 1488 1489 @property 1490 def name(self): 1491 return self.path 1492 1493 1494def main(custom_commandline=None): 1495 """ 1496 Main function for espsecure 1497 1498 custom_commandline - Optional override for default arguments parsing 1499 (that uses sys.argv), can be a list of custom arguments as strings. 1500 Arguments and their values need to be added as individual items to the list 1501 e.g. "--port /dev/ttyUSB1" thus becomes ['--port', '/dev/ttyUSB1']. 1502 """ 1503 parser = argparse.ArgumentParser( 1504 description="espsecure.py v%s - ESP32 Secure Boot & Flash Encryption tool" 1505 % esptool.__version__, 1506 prog="espsecure", 1507 ) 1508 1509 subparsers = parser.add_subparsers( 1510 dest="operation", help="Run espsecure.py {command} -h for additional help" 1511 ) 1512 1513 p = subparsers.add_parser( 1514 "digest_secure_bootloader", 1515 help="Take a bootloader binary image and a secure boot key, " 1516 "and output a combined digest+binary suitable for flashing along " 1517 "with the precalculated secure boot key.", 1518 ) 1519 p.add_argument( 1520 "--keyfile", 1521 "-k", 1522 help="256 bit key for secure boot digest.", 1523 type=argparse.FileType("rb"), 1524 required=True, 1525 ) 1526 p.add_argument("--output", "-o", help="Output file for signed digest image.") 1527 p.add_argument( 1528 "--iv", 1529 help="128 byte IV file. Supply a file for testing purposes only, " 1530 "if not supplied an IV will be randomly generated.", 1531 type=argparse.FileType("rb"), 1532 ) 1533 p.add_argument( 1534 "image", 1535 help="Bootloader image file to calculate digest from", 1536 type=argparse.FileType("rb"), 1537 ) 1538 1539 p = subparsers.add_parser( 1540 "generate_signing_key", 1541 help="Generate a private key for signing secure boot images " 1542 "as per the secure boot version. " 1543 "Key file is generated in PEM format, " 1544 "Secure Boot V1 - ECDSA NIST256p private key. " 1545 "Secure Boot V2 - RSA 3072, ECDSA NIST384p, ECDSA NIST256p, ECDSA NIST192p private key.", 1546 ) 1547 p.add_argument( 1548 "--version", 1549 "-v", 1550 help="Version of the secure boot signing scheme to use.", 1551 choices=["1", "2"], 1552 default="1", 1553 ) 1554 p.add_argument( 1555 "--scheme", 1556 "-s", 1557 help="Scheme of secure boot signing.", 1558 choices=["rsa3072", "ecdsa192", "ecdsa256", "ecdsa384"], 1559 required=False, 1560 ) 1561 p.add_argument( 1562 "keyfile", help="Filename for private key file (embedded public key)" 1563 ) 1564 1565 p = subparsers.add_parser( 1566 "sign_data", 1567 help="Sign a data file for use with secure boot. " 1568 "Signing algorithm is deterministic ECDSA w/ SHA-512 (V1) " 1569 "or either RSA-PSS or ECDSA w/ SHA-256 or ECDSA w/ SHA-384 (V2).", 1570 ) 1571 p.add_argument( 1572 "--version", 1573 "-v", 1574 help="Version of the secure boot signing scheme to use.", 1575 choices=["1", "2"], 1576 required=True, 1577 ) 1578 p.add_argument( 1579 "--keyfile", 1580 "-k", 1581 help="Private key file for signing. Key is in PEM format.", 1582 type=argparse.FileType("rb"), 1583 nargs="+", 1584 ) 1585 p.add_argument( 1586 "--append_signatures", 1587 "-a", 1588 help="Append signature block(s) to already signed image. " 1589 "Not valid for ESP32 and ESP32-C2.", 1590 action="store_true", 1591 ) 1592 p.add_argument( 1593 "--hsm", 1594 help="Use an external Hardware Security Module " 1595 "to generate signature using PKCS#11 interface.", 1596 action="store_true", 1597 ) 1598 p.add_argument( 1599 "--hsm-config", 1600 help="Config file for the external Hardware Security Module " 1601 "to be used to generate signature.", 1602 default=None, 1603 ) 1604 p.add_argument( 1605 "--pub-key", 1606 help="Public key files corresponding to the private key used to generate " 1607 "the pre-calculated signatures. Keys should be in PEM format.", 1608 type=argparse.FileType("rb"), 1609 nargs="+", 1610 ) 1611 p.add_argument( 1612 "--signature", 1613 help="Pre-calculated signatures. " 1614 "Signatures generated using external private keys e.g. keys stored in HSM.", 1615 type=argparse.FileType("rb"), 1616 nargs="+", 1617 default=None, 1618 ) 1619 p.add_argument( 1620 "--output", 1621 "-o", 1622 help="Output file for signed digest image. Default is to sign the input file.", 1623 ) 1624 p.add_argument( 1625 "datafile", 1626 help="File to sign. For version 1, this can be any file. " 1627 "For version 2, this must be a valid app image.", 1628 type=argparse.FileType("rb"), 1629 ) 1630 1631 p = subparsers.add_parser( 1632 "verify_signature", 1633 help='Verify a data file previously signed by "sign_data", ' 1634 "using the public key.", 1635 ) 1636 p.add_argument( 1637 "--version", 1638 "-v", 1639 help="Version of the secure boot scheme to use.", 1640 choices=["1", "2"], 1641 required=True, 1642 ) 1643 p.add_argument( 1644 "--hsm", 1645 help="Use an external Hardware Security Module " 1646 "to verify signature using PKCS#11 interface.", 1647 action="store_true", 1648 ) 1649 p.add_argument( 1650 "--hsm-config", 1651 help="Config file for the external Hardware Security Module " 1652 "to be used to verify signature.", 1653 default=None, 1654 ) 1655 p.add_argument( 1656 "--keyfile", 1657 "-k", 1658 help="Public key file for verification. " 1659 "Can be private or public key in PEM format.", 1660 type=argparse.FileType("rb"), 1661 ) 1662 p.add_argument( 1663 "datafile", 1664 help="Signed data file to verify signature.", 1665 type=argparse.FileType("rb"), 1666 ) 1667 1668 p = subparsers.add_parser( 1669 "extract_public_key", 1670 help="Extract the public verification key for signatures, " 1671 "save it as a raw binary file.", 1672 ) 1673 p.add_argument( 1674 "--version", 1675 "-v", 1676 help="Version of the secure boot signing scheme to use.", 1677 choices=["1", "2"], 1678 default="1", 1679 ) 1680 p.add_argument( 1681 "--keyfile", 1682 "-k", 1683 help="Private key file (PEM format) to extract the " 1684 "public verification key from.", 1685 type=argparse.FileType("rb"), 1686 required=True, 1687 ) 1688 p.add_argument( 1689 "public_keyfile", help="File to save new public key into", type=OutFileType() 1690 ) 1691 1692 # Kept for compatibility purpose. We can deprecate this in a future release 1693 p = subparsers.add_parser( 1694 "digest_rsa_public_key", 1695 help="Generate an SHA-256 digest of the RSA public key. " 1696 "This digest is burned into the eFuse and asserts the legitimacy " 1697 "of the public key for Secure boot v2.", 1698 ) 1699 p.add_argument( 1700 "--keyfile", 1701 "-k", 1702 help="Public key file for verification. " 1703 "Can be private or public key in PEM format.", 1704 type=argparse.FileType("rb"), 1705 required=True, 1706 ) 1707 p.add_argument("--output", "-o", help="Output file for the digest.", required=True) 1708 1709 p = subparsers.add_parser( 1710 "digest_sbv2_public_key", 1711 help="Generate an SHA-256 digest of the public key. " 1712 "This digest is burned into the eFuse and asserts the legitimacy " 1713 "of the public key for Secure boot v2.", 1714 ) 1715 p.add_argument( 1716 "--keyfile", 1717 "-k", 1718 help="Public key file for verification. " 1719 "Can be private or public key in PEM format.", 1720 type=argparse.FileType("rb"), 1721 required=True, 1722 ) 1723 p.add_argument("--output", "-o", help="Output file for the digest.", required=True) 1724 1725 p = subparsers.add_parser( 1726 "signature_info_v2", 1727 help="Reads the signature block and provides the signature block information.", 1728 ) 1729 p.add_argument( 1730 "datafile", 1731 help="Secure boot v2 signed data file.", 1732 type=argparse.FileType("rb"), 1733 ) 1734 1735 p = subparsers.add_parser( 1736 "digest_private_key", 1737 help="Generate an SHA-256 digest of the private signing key. " 1738 "This can be used as a reproducible secure bootloader (only secure boot v1) " 1739 "or flash encryption key.", 1740 ) 1741 p.add_argument( 1742 "--keyfile", 1743 "-k", 1744 help="Private key file (PEM format) to generate a digest from.", 1745 type=argparse.FileType("rb"), 1746 required=True, 1747 ) 1748 p.add_argument( 1749 "--keylen", 1750 "-l", 1751 help="Length of private key digest file to generate (in bits). " 1752 "3/4 Coding Scheme requires 192 bit key.", 1753 choices=[192, 256], 1754 default=256, 1755 type=int, 1756 ) 1757 p.add_argument( 1758 "digest_file", help="File to write 32 byte digest into", type=OutFileType() 1759 ) 1760 1761 p = subparsers.add_parser( 1762 "generate_flash_encryption_key", 1763 help="Generate a development-use flash encryption key with random data.", 1764 ) 1765 p.add_argument( 1766 "--keylen", 1767 "-l", 1768 help="Length of private key digest file to generate (in bits). " 1769 "3/4 Coding Scheme requires 192 bit key.", 1770 choices=[128, 192, 256, 512], 1771 default=256, 1772 type=int, 1773 ) 1774 p.add_argument( 1775 "key_file", 1776 help="File to write 16, 24, 32 or 64 byte key into", 1777 type=OutFileType(), 1778 ) 1779 1780 p = subparsers.add_parser( 1781 "decrypt_flash_data", 1782 help="Decrypt some data read from encrypted flash (using known key)", 1783 ) 1784 p.add_argument( 1785 "encrypted_file", 1786 help="File with encrypted flash contents", 1787 type=argparse.FileType("rb"), 1788 ) 1789 p.add_argument( 1790 "--aes_xts", 1791 "-x", 1792 help="Decrypt data using AES-XTS as used on " 1793 "ESP32-S2, ESP32-C2, ESP32-C3, ESP32-C6, ESP32-C5, ESP32-C61 and ESP32-P4", 1794 action="store_true", 1795 ) 1796 p.add_argument( 1797 "--keyfile", 1798 "-k", 1799 help="File with flash encryption key", 1800 type=argparse.FileType("rb"), 1801 required=True, 1802 ) 1803 p.add_argument( 1804 "--output", 1805 "-o", 1806 help="Output file for plaintext data.", 1807 type=OutFileType(), 1808 required=True, 1809 ) 1810 p.add_argument( 1811 "--address", 1812 "-a", 1813 help="Address offset in flash that file was read from.", 1814 required=True, 1815 type=esptool.arg_auto_int, 1816 ) 1817 p.add_argument( 1818 "--flash_crypt_conf", 1819 help="Override FLASH_CRYPT_CONF efuse value (default is 0XF).", 1820 required=False, 1821 default=0xF, 1822 type=esptool.arg_auto_int, 1823 ) 1824 1825 p = subparsers.add_parser( 1826 "encrypt_flash_data", 1827 help="Encrypt some data suitable for encrypted flash (using known key)", 1828 ) 1829 p.add_argument( 1830 "--aes_xts", 1831 "-x", 1832 help="Encrypt data using AES-XTS as used on " 1833 "ESP32-S2, ESP32-C2, ESP32-C3, ESP32-C6, ESP32-C5, ESP32-C61 and ESP32-P4", 1834 action="store_true", 1835 ) 1836 p.add_argument( 1837 "--keyfile", 1838 "-k", 1839 help="File with flash encryption key", 1840 type=argparse.FileType("rb"), 1841 required=True, 1842 ) 1843 p.add_argument( 1844 "--output", 1845 "-o", 1846 help="Output file for encrypted data.", 1847 type=OutFileType(), 1848 required=True, 1849 ) 1850 p.add_argument( 1851 "--address", 1852 "-a", 1853 help="Address offset in flash where file will be flashed.", 1854 required=True, 1855 type=esptool.arg_auto_int, 1856 ) 1857 p.add_argument( 1858 "--flash_crypt_conf", 1859 help="Override FLASH_CRYPT_CONF efuse value (default is 0XF).", 1860 required=False, 1861 default=0xF, 1862 type=esptool.arg_auto_int, 1863 ) 1864 p.add_argument( 1865 "plaintext_file", 1866 help="File with plaintext content for encrypting", 1867 type=argparse.FileType("rb"), 1868 ) 1869 1870 # Enable argcomplete only on Unix-like systems 1871 if sys.platform != "win32": 1872 try: 1873 import argcomplete 1874 1875 argcomplete.autocomplete(parser) 1876 except ImportError: 1877 pass 1878 1879 args = parser.parse_args(custom_commandline) 1880 print("espsecure.py v%s" % esptool.__version__) 1881 if args.operation is None: 1882 parser.print_help() 1883 parser.exit(1) 1884 1885 try: 1886 # each 'operation' is a module-level function of the same name 1887 operation_func = globals()[args.operation] 1888 operation_func(args) 1889 finally: 1890 for arg_name in vars(args): 1891 obj = getattr(args, arg_name) 1892 if isinstance(obj, (OutFileType, IOBase)): 1893 obj.close() 1894 elif isinstance(obj, list): 1895 for f in [o for o in obj if isinstance(o, IOBase)]: 1896 f.close() 1897 1898 1899def _main(): 1900 try: 1901 main() 1902 except esptool.FatalError as e: 1903 print("\nA fatal error occurred: %s" % e) 1904 sys.exit(2) 1905 except ValueError as e: 1906 try: 1907 if [arg for arg in e.args if "Could not deserialize key data." in arg]: 1908 print( 1909 "Note: This error originates from the cryptography module. " 1910 "It is likely not a problem with espsecure, " 1911 "please make sure you are using a compatible OpenSSL backend." 1912 ) 1913 finally: 1914 raise 1915 1916 1917if __name__ == "__main__": 1918 _main() 1919