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