def _flash_encryption_operation_aes_xts(output_file, input_file, flash_address, keyfile, do_decrypt): """ Apply the AES-XTS algorithm with the hardware addressing scheme used by Espressif key = AES-XTS key (32 or 64 bytes) flash_address = address in flash to encrypt at. Must be multiple of 16 bytes. indata = Data to encrypt/decrypt. Must be multiple of 16 bytes. encrypt = True to Encrypt indata, False to decrypt indata. Returns a bitstring of the ciphertext or plaintext result. """ backend = default_backend() key = _load_hardware_key(keyfile) indata = input_file.read() if flash_address % 16 != 0: raise esptool.FatalError( "Starting flash address 0x%x must be a multiple of 16" % flash_address) if len(indata) % 16 != 0: raise esptool.FatalError( "Input data length (%d) must be a multiple of 16" % len(indata)) if len(indata) == 0: raise esptool.FatalError("Input data must be longer than 0") # left pad for a 1024-bit aligned address pad_left = flash_address % 0x80 indata = (b"\x00" * pad_left) + indata # right pad for full 1024-bit blocks pad_right = len(indata) % 0x80 if pad_right > 0: pad_right = 0x80 - pad_right indata = indata + (b"\x00" * pad_right) inblocks = _split_blocks(indata, 0x80) # split into 1024 bit blocks output = b"" for inblock in inblocks: # for each block tweak = struct.pack("<I", (flash_address & ~0x7F)) + (b"\x00" * 12) flash_address += 0x80 # for next block if len(tweak) != 16: raise esptool.FatalError( "Length of tweak must be 16, was {}".format(len(tweak))) cipher = Cipher(algorithms.AES(key), modes.XTS(tweak), backend=backend) encryptor = cipher.decryptor() if do_decrypt else cipher.encryptor() inblock = inblock[::-1] # reverse input outblock = encryptor.update(inblock) # standard algo output += outblock[::-1] # reverse output # undo any padding we applied to the input if pad_right != 0: output = output[:-pad_right] if pad_left != 0: output = output[pad_left:] # output length matches original input if len(output) != len(indata) - pad_left - pad_right: raise esptool.FatalError( "Length of input data ({}) should match the output data ({})". format(len(indata) - pad_left - pad_right, len(output))) output_file.write(output)
def check_error(esp, efuses, args): if efuses.get_coding_scheme_warnings(): raise esptool.FatalError("Error(s) were detected in eFuses") print("No errors detected")
def burn_key(esp, efuses, args): # check block choice if args.block in ["flash_encryption", "BLK1"]: block_num = 1 elif args.block in ["secure_boot", "BLK2"]: block_num = 2 elif args.block == "BLK3": block_num = 3 else: raise RuntimeError("args.block argument not in list!") # check keyfile keyfile = args.keyfile keyfile.seek(0, 2) # seek t oend size = keyfile.tell() keyfile.seek(0) if size != 32: raise esptool.FatalError( "Incorrect key file size %d. Key file must be 32 bytes (256 bits) of raw binary key data." % size) # check existing data efuse = [e for e in efuses if e.register_name == "BLK%d" % block_num][0] original = efuse.get_raw() EMPTY_KEY = b'\x00' * 32 if original != EMPTY_KEY: if not args.force_write_always: raise esptool.FatalError("Key block already has value %s." % efuse.get()) else: print( "WARNING: Key appears to have a value already. Trying anyhow, due to --force-write-always (result will be bitwise OR of new and old values.)" ) if not efuse.is_writeable(): if not args.force_write_always: raise esptool.FatalError( "The efuse block has already been write protected.") else: print( "WARNING: Key appears to be write protected. Trying anyhow, due to --force-write-always" ) msg = "Write key in efuse block %d. " % block_num if args.no_protect_key: msg += "The key block will left readable and writeable (due to --no-protect-key)" else: msg += "The key block will be read and write protected (no further changes or readback)" confirm(msg, args) new_value = keyfile.read(32) new = efuse.burn(new_value) print("Burned key data. New value: %s" % (new, )) if not args.no_protect_key: print("Disabling read/write to key efuse block...") efuse.disable_write() efuse.disable_read() if efuse.is_readable(): print( "WARNING: Key does not appear to have been read protected. Perhaps read disable efuse is write protected?" ) if efuse.is_writeable(): print( "WARNING: Key does not appear to have been write protected. Perhaps write disable efuse is write protected?" ) else: print("Key is left unprotected as per --no-protect-key argument.")
args = EspEfuseArgs() args.block = self.BLK3 args.bit_number = [ 0, 1, 2, 4, 8, 16, 32, 64, 96, 128, 160, 192, 224, 255 ] self.operations.burn_bit(self.esp, self.efuses, args) words = self.efuses.blocks[self.efuses.get_index_block_by_name( self.BLK3)].get_bitstring() self.assertEqual( "0x8000000100000001000000010000000100000001000000010000000100010117", words) if __name__ == "__main__": if len(sys.argv) < 3: print("Usage: %s --i-use-fpga <serial port> [optional tests]" % sys.argv[0]) sys.exit(1) if sys.argv[1] != "--i-use-fpga": raise esptool.FatalError( "You need to use --i-use-fpga to confirm these tests " "are not being run on a real ESP chip!") serialport = serial.Serial(sys.argv[2], 115200) serialport.dtr = False serialport.rts = False # unittest also uses argv, so trim the args we used sys.argv = [sys.argv[0]] + sys.argv[3:] print("Running espefuse.py tests...") unittest.main(buffer=True)
def set_flash_voltage(esp, efuses, args): raise esptool.FatalError("set_flash_voltage is not supported!")
def burn(self, new_value): # Writing the BLK0 default MAC is not sensible, as it's written in the factory. raise esptool.FatalError("Writing Factory MAC address is not supported")
def wait_idle(): for _ in range(10): if esp.read_reg(EFUSE_REG_CMD) == 0: return raise esptool.FatalError( "Timed out waiting for Efuse controller command to complete")
def check_efuse_name(efuse_name, efuse_list): if efuse_name not in self._choices: raise esptool.FatalError( "Invalid the efuse name '{}'. Available the efuse names: {}" .format(efuse_name, self._choices))
def save(self, new_value): raise esptool.FatalError("Writing to VRef is not supported.")
def check_wr_data(self): wr_data = self.wr_bitarray if wr_data.all(False): # nothing to burn if self.parent.debug: print("[{:02}] {:20} nothing to burn".format( self.id, self.name)) return False if len(wr_data.bytes) != len(self.bitarray.bytes): raise esptool.FatalError( "Data does not fit: the block%d size is %d bytes, data is %d bytes" % (self.id, len(self.bitarray.bytes), len(wr_data.bytes))) self.check_wr_rd_protect() if self.get_bitstring().all(False): print("[{:02}] {:20} is empty, will burn the new value".format( self.id, self.name)) else: # the written block in chip is not empty if self.get_bitstring() == wr_data: print( "[{:02}] {:20} is already written the same value, continue with EMPTY_BLOCK" .format(self.id, self.name)) wr_data.set(0) else: print("[{:02}] {:20} is not empty".format(self.id, self.name)) print("\t(written ):", self.get_bitstring()) print("\t(to write):", wr_data) mask = self.get_bitstring() & wr_data if mask == wr_data: print( "\tAll wr_data bits are set in the written block, continue with EMPTY_BLOCK." ) wr_data.set(0) else: coding_scheme = self.get_coding_scheme() if coding_scheme == self.parent.REGS.CODING_SCHEME_NONE: print("\t(coding scheme = NONE)") elif coding_scheme == self.parent.REGS.CODING_SCHEME_RS: print("\t(coding scheme = RS)") error_msg = "\tBurn into %s is forbidden (RS coding scheme does not allow this)." % ( self.name) self.parent.print_error_msg(error_msg) elif coding_scheme == self.parent.REGS.CODING_SCHEME_34: print("\t(coding scheme = 3/4)") data_can_not_be_burn = False for i in range(0, self.get_bitstring().len, 6 * 8): rd_chunk = self.get_bitstring()[i:i + 6 * 8:] wr_chunk = wr_data[i:i + 6 * 8:] if rd_chunk.any(True): if wr_chunk.any(True): print( "\twritten chunk [%d] and wr_chunk are not empty. " % (i // (6 * 8)), end="") if rd_chunk == wr_chunk: print( "wr_chunk == rd_chunk. Countinue with empty chunk." ) wr_data[i:i + 6 * 8:].set(0) else: print( "wr_chunk != rd_chunk. Can not burn." ) print("\twritten ", rd_chunk) print("\tto write", wr_chunk) data_can_not_be_burn = True if data_can_not_be_burn: error_msg = "\tBurn into %s is forbidden (3/4 coding scheme does not allow this)." % ( self.name) self.parent.print_error_msg(error_msg) else: raise esptool.FatalError( "The coding scheme ({}) is not supported".format( coding_scheme))
def __init__(self, esp, skip_connect=False, debug=False, do_not_confirm=False): self._esp = esp self.debug = debug self.do_not_confirm = do_not_confirm if esp.CHIP_NAME != "ESP32": raise esptool.FatalError( "Expected the 'esp' param for ESP32 chip but got for '%s'." % (esp.CHIP_NAME)) self.blocks = [ EfuseBlock(self, self.Blocks.get(block), skip_read=skip_connect) for block in self.Blocks.BLOCKS ] if not skip_connect: self.get_coding_scheme_warnings() self.efuses = [ EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.EFUSES ] if skip_connect: self.efuses += [ EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.KEYBLOCKS_256 ] self.efuses += [ EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.CUSTOM_MAC ] self.efuses += [ EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.ADC_CALIBRATION ] else: if self.coding_scheme == self.REGS.CODING_SCHEME_NONE: self.efuses += [ EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.KEYBLOCKS_256 ] elif self.coding_scheme == self.REGS.CODING_SCHEME_34: self.efuses += [ EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.KEYBLOCKS_192 ] else: raise esptool.FatalError( "The coding scheme (%d) - is not supported" % self.coding_scheme) if self["MAC_VERSION"].get() == 1: self.efuses += [ EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.CUSTOM_MAC ] if self["BLK3_PART_RESERVE"].get(): self.efuses += [ EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.ADC_CALIBRATION ]
def burn_key(esp, efuses, args): datafile_list = args.keyfile[ 0:len([keyfile for keyfile in args.keyfile if keyfile is not None]):] block_name_list = args.block[ 0:len([block for block in args.block if block is not None]):] efuses.force_write_always = args.force_write_always no_protect_key = args.no_protect_key util.check_duplicate_name_in_list(block_name_list) if len(block_name_list) != len(datafile_list): raise esptool.FatalError( "The number of blocks (%d) and datafile (%d) should be the same." % (len(block_name_list), len(datafile_list))) print("Burn keys to blocks:") for block_name, datafile in zip(block_name_list, datafile_list): efuse = None for block in efuses.blocks: if block_name == block.name or block_name in block.alias: efuse = efuses[block.name] if efuse is None: raise esptool.FatalError("Unknown block name - %s" % (block_name)) num_bytes = efuse.bit_len // 8 data = datafile.read() revers_msg = None if block_name in ("flash_encryption", "secure_boot_v1"): revers_msg = "\tReversing the byte order" data = data[::-1] print(" - %s -> [%s]" % (efuse.name, util.hexify(data, " "))) if revers_msg: print(revers_msg) if len(data) != num_bytes: raise esptool.FatalError( "Incorrect key file size %d. " "Key file must be %d bytes (%d bits) of raw binary key data." % (len(data), num_bytes, num_bytes * 8)) efuse.save(data) if block_name in ("flash_encryption", "secure_boot_v1"): if not no_protect_key: print("\tDisabling read to key block") efuse.disable_read() if not no_protect_key: print("\tDisabling write to key block") efuse.disable_write() print("") if args.no_protect_key: print("Key is left unprotected as per --no-protect-key argument.") msg = "Burn keys in efuse blocks.\n" if no_protect_key: msg += ( "The key block will left readable and writeable (due to --no-protect-key)" ) else: msg += "The key block will be read and write protected " "(no further changes or readback)" print(msg, "\n") if not efuses.burn_all(check_batch_mode=True): return print("Successful")
def _esptool_write_flash(self, esp, args): '''Method to write to flash. This method implements the esptool.py v2.6 write_flash(esp, args) function with some modifications. The modifications are to allow the progress of the write_flash(esp, args) to be shown in this GUI class.''' # set args.compress based on default behaviour: # -> if either --compress or --no-compress is set, honour that # -> otherwise, set --compress unless --no-stub is set if args.compress is None and not args.no_compress: args.compress = not args.no_stub # verify file sizes fit in flash msg = 'Verifying file sizes can fit in flash...' self._update_status(msg) print(msg) flash_end = esptool.flash_size_bytes(args.flash_size) for address, argfile in args.addr_filename: argfile.seek(0, 2) # seek to end if address + argfile.tell() > flash_end: raise esptool.FatalError(( "File %s (length %d) at offset %d will not fit in %d bytes of flash. " + "Use --flash-size argument, or change flashing address.") % (argfile.name, argfile.tell(), address, flash_end)) argfile.seek(0) if args.erase_all: msg = 'Erasing flash (this may take a while)...' self._update_status(msg) esptool.erase_flash(esp, args) for address, argfile in args.addr_filename: if args.no_stub: #print( 'Erasing flash...' ) msg = 'Erasing flash...' self._update_status(msg) print(msg) image = esptool.pad_to(argfile.read(), 4) if len(image) == 0: #print( 'WARNING: File %s is empty' % argfile.name ) msg = 'WARNING: File %s is empty' % argfile.name self._update_status(msg) print(msg) continue image = esptool._update_image_flash_params(esp, address, args, image) calcmd5 = hashlib.md5(image).hexdigest() uncsize = len(image) if args.compress: uncimage = image image = zlib.compress(uncimage, 9) ratio = uncsize / len(image) blocks = esp.flash_defl_begin(uncsize, len(image), address) else: ratio = 1.0 blocks = esp.flash_begin(uncsize, address) argfile.seek(0) # in case we need it again seq = 0 written = 0 t = time.time() while len(image) > 0: #print( '\rWriting at 0x%08x... (%d %%)' % ( address + seq * esp.FLASH_WRITE_SIZE, 100 * (seq + 1) // blocks), end='' ) msg = 'Writing at 0x%08x... (%d %%)' % ( address + seq * esp.FLASH_WRITE_SIZE, 100 * (seq + 1) // blocks) self._update_status(msg) print(msg, end='') sys.stdout.flush() block = image[0:esp.FLASH_WRITE_SIZE] if args.compress: esp.flash_defl_block(block, seq, timeout=esptool.DEFAULT_TIMEOUT * ratio * 2) else: # Pad the last block block = block + b'\xff' * (esp.FLASH_WRITE_SIZE - len(block)) esp.flash_block(block, seq) image = image[esp.FLASH_WRITE_SIZE:] seq += 1 written += len(block) t = time.time() - t speed_msg = "" if args.compress: if t > 0.0: speed_msg = " (effective %.1f kbit/s)" % (uncsize / t * 8 / 1000) print( 'Wrote %d bytes (%d compressed) at 0x%08x in %.1f seconds%s...' % (uncsize, written, address, t, speed_msg)) else: if t > 0.0: speed_msg = " (%.1f kbit/s)" % (written / t * 8 / 1000) print('Wrote %d bytes at 0x%08x in %.1f seconds%s...' % (written, address, t, speed_msg)) msg = 'Writing completed in %.1f seconds%s...' % (t, speed_msg) self._update_status(msg) try: res = esp.flash_md5sum(address, uncsize) if res != calcmd5: print('File md5: %s' % calcmd5) print('Flash md5: %s' % res) print('MD5 of 0xFF is %s' % (hashlib.md5(b'\xFF' * uncsize).hexdigest())) raise esptool.FatalError( "MD5 of file does not match data in flash!") else: #print( 'Hash of data verified.' ) msg = 'Hash of data verified.' self._update_status(msg) print(msg) except esptool.NotImplementedInROMError: pass print('\nLeaving...') if esp.IS_STUB: # skip sending flash_finish to ROM loader here, # as it causes the loader to exit and run user code esp.flash_begin(0, 0) if args.compress: esp.flash_defl_finish(False) else: esp.flash_finish(False) if args.verify: print('Verifying just-written flash...') print( '(This option is deprecated, flash contents are now always read back after flashing.)' ) msg = 'Verifying just-written flash...' self._update_status(msg) #print( msg ) esptool.verify_flash(esp, args) msg = '-- verify OK (digest matched)' self._update_status(msg)
def burn(self, new_value): # need to calculate the CRC before we can write the MAC raise esptool.FatalError("Writing MAC address is not yet supported")
def get_custom_mac(esp, efuses, args): raise esptool.FatalError("get_custom_mac is not supported!")
def burn_efuse(esp, efuses, args): def print_attention(blocked_efuses_after_burn): if len(blocked_efuses_after_burn): print( " ATTENTION! This BLOCK uses NOT the NONE coding scheme and after 'BURN', these efuses can not be burned in the feature:" ) for i in range(0, len(blocked_efuses_after_burn), 5): print( " ", "".join("{}".format(blocked_efuses_after_burn[i:i + 5:]))) efuse_name_list = [name for name in args.name_value_pairs.keys()] burn_efuses_list = [efuses[name] for name in efuse_name_list] old_value_list = [efuses[name].get_raw() for name in efuse_name_list] new_value_list = [value for value in args.name_value_pairs.values()] util.check_duplicate_name_in_list(efuse_name_list) attention = "" print("The efuses to burn:") for block in efuses.blocks: burn_list_a_block = [ e for e in burn_efuses_list if e.block == block.id ] if len(burn_list_a_block): print(" from BLOCK%d" % (block.id)) for field in burn_list_a_block: print(" - %s" % (field.name)) if efuses.blocks[field.block].get_coding_scheme( ) != efuses.REGS.CODING_SCHEME_NONE: using_the_same_block_names = [ e.name for e in efuses if e.block == field.block ] wr_names = [e.name for e in burn_list_a_block] blocked_efuses_after_burn = [ name for name in using_the_same_block_names if name not in wr_names ] attention = " (see 'ATTENTION!' above)" if attention: print_attention(blocked_efuses_after_burn) print("\nBurning efuses{}:".format(attention)) for efuse, new_value in zip(burn_efuses_list, new_value_list): print("\n - '{}' ({}) {} -> {}".format( efuse.name, efuse.description, efuse.get_bitstring(), efuse.convert_to_bitstring(new_value))) efuse.save(new_value) efuses.burn_all() print("Checking efuses...") raise_error = False for efuse, old_value, new_value in zip(burn_efuses_list, old_value_list, new_value_list): if not efuse.is_readable(): print( "Efuse %s is read-protected. Read back the burn value is not possible." % efuse.name) else: new_value = efuse.convert_to_bitstring(new_value) burned_value = efuse.get_bitstring() if burned_value != new_value: print(burned_value, "->", new_value, "Efuse %s failed to burn. Protected?" % efuse.name) raise_error = True if raise_error: raise esptool.FatalError("The burn was not successful.") else: print("Successful")
def disable_write(self): num_bit = self.write_disable_bit if not self.parent["WR_DIS"].is_writeable(): raise esptool.FatalError("This efuse cannot be write-disabled due to the WR_DIS field is already write-disabled") self.parent["WR_DIS"].save(1 << num_bit)
def burn_key(esp, efuses, args, digest=None): if digest is None: datafile_list = args.keyfile[0:len([name for name in args.keyfile if name is not None]):] else: datafile_list = digest[0:len([name for name in digest if name is not None]):] efuses.force_write_always = args.force_write_always block_name_list = args.block[0:len([name for name in args.block if name is not None]):] keypurpose_list = args.keypurpose[0:len([name for name in args.keypurpose if name is not None]):] util.check_duplicate_name_in_list(block_name_list) if len(block_name_list) != len(datafile_list) or len(block_name_list) != len(keypurpose_list): raise esptool.FatalError("The number of blocks (%d), datafile (%d) and keypurpose (%d) should be the same." % (len(block_name_list), len(datafile_list), len(keypurpose_list))) print("Burn keys to blocks:") for block_name, datafile, keypurpose in zip(block_name_list, datafile_list, keypurpose_list): efuse = None for block in efuses.blocks: if block_name == block.name or block_name in block.alias: efuse = efuses[block.name] if efuse is None: raise esptool.FatalError("Unknown block name - %s" % (block_name)) num_bytes = efuse.bit_len // 8 block_num = efuses.get_index_block_by_name(block_name) block = efuses.blocks[block_num] if digest is None: data = datafile.read() else: data = datafile print(" - %s" % (efuse.name), end=" ") revers_msg = None if efuses[block.key_purpose_name].need_reverse(keypurpose): revers_msg = "\tReversing byte order for AES-XTS hardware peripheral" data = data[::-1] print("-> [%s]" % (util.hexify(data, " "))) if revers_msg: print(revers_msg) if len(data) != num_bytes: raise esptool.FatalError("Incorrect key file size %d. Key file must be %d bytes (%d bits) of raw binary key data." % (len(data), num_bytes, num_bytes * 8)) if efuses[block.key_purpose_name].need_rd_protect(keypurpose): read_protect = False if args.no_read_protect else True else: read_protect = False write_protect = not args.no_write_protect # using efuse instead of a block gives the advantage of checking it as the whole field. efuse.save(data) disable_wr_protect_key_purpose = False if efuses[block.key_purpose_name].get() != keypurpose: if efuses[block.key_purpose_name].is_writeable(): print("\t'%s': '%s' -> '%s'." % (block.key_purpose_name, efuses[block.key_purpose_name].get(), keypurpose)) efuses[block.key_purpose_name].save(keypurpose) disable_wr_protect_key_purpose = True else: raise esptool.FatalError("It is not possible to change '%s' to '%s' because write protection bit is set." % (block.key_purpose_name, keypurpose)) else: print("\t'%s' is already '%s'." % (block.key_purpose_name, keypurpose)) if efuses[block.key_purpose_name].is_writeable(): disable_wr_protect_key_purpose = True if disable_wr_protect_key_purpose: print("\tDisabling write to '%s'." % block.key_purpose_name) efuses[block.key_purpose_name].disable_write() if read_protect: print("\tDisabling read to key block") efuse.disable_read() if write_protect: print("\tDisabling write to key block") efuse.disable_write() print("") if not write_protect: print("Keys will remain writeable (due to --no-write-protect)") if args.no_read_protect: print("Keys will remain readable (due to --no-read-protect)") if not efuses.burn_all(check_batch_mode=True): return print("Successful")
def read_protect_efuse(esp, efuses, args): util.check_duplicate_name_in_list(args.efuse_name) for efuse_name in args.efuse_name: efuse = efuses[efuse_name] if not efuse.is_readable(): print("Efuse %s is already read protected" % efuse.name) else: if esp.CHIP_NAME == "ESP32": if efuse_name == 'BLOCK2' and not efuses['ABS_DONE_0'].get( ) and "revision 3" in esp.get_chip_description(): if efuses['ABS_DONE_1'].get(): raise esptool.FatalError( "Secure Boot V2 is on (ABS_DONE_1 = True), BLOCK2 must be readable, stop this operation!" ) else: print( "In case using Secure Boot V2, the BLOCK2 must be readable, please stop this operation!" ) elif esp.CHIP_NAME == "ESP32-C2": error = not efuses['XTS_KEY_LENGTH_256'].get( ) and efuse_name == 'BLOCK_KEY0' error |= efuses['SECURE_BOOT_EN'].get() and efuse_name in [ 'BLOCK_KEY0', 'BLOCK_KEY0_HI_128' ] if error: raise esptool.FatalError( "%s must be readable, stop this operation!" % efuse_name) else: for block in efuses.Blocks.BLOCKS: block = efuses.Blocks.get(block) if block.name == efuse_name and block.key_purpose is not None: if not efuses[block.key_purpose].need_rd_protect( efuses[block.key_purpose].get()): raise esptool.FatalError( "%s must be readable, stop this operation!" % efuse_name) break # make full list of which efuses will be disabled (ie share a read disable bit) all_disabling = [ e for e in efuses if e.read_disable_bit == efuse.read_disable_bit ] names = ", ".join(e.name for e in all_disabling) print("Permanently read-disabling efuse%s %s" % ("s" if len(all_disabling) > 1 else "", names)) efuse.disable_read() if not efuses.burn_all(check_batch_mode=True): return print("Checking efuses...") raise_error = False for efuse_name in args.efuse_name: efuse = efuses[efuse_name] if efuse.is_readable(): print("Efuse %s is not read-protected." % efuse.name) raise_error = True if raise_error: raise esptool.FatalError("The burn was not successful.") else: print("Successful")
def _load_ecdsa_signing_key(args): sk = ecdsa.SigningKey.from_pem(args.keyfile.read()) if sk.curve != ecdsa.NIST256p: raise esptool.FatalError("Signing key uses incorrect curve. ESP32 Secure Boot only supports NIST256p (openssl calls this curve 'prime256v1") return sk
def sign_secure_boot_v2(args): """ Sign a firmware app image with an RSA private key using RSA-PSS, write output file with a Secure Boot V2 header appended. """ SECTOR_SIZE = 4096 SIG_BLOCK_SIZE = 1216 SIG_BLOCK_MAX_COUNT = 3 signature_sector = b"" key_count = len(args.keyfile) contents = args.datafile.read() if key_count > SIG_BLOCK_MAX_COUNT: print( "WARNING: Upto %d signing keys are supported for ESP32-S2. For ESP32-ECO3 only 1 signing key is supported", SIG_BLOCK_MAX_COUNT) if len(contents) % SECTOR_SIZE != 0: pad_by = SECTOR_SIZE - (len(contents) % SECTOR_SIZE) print( "Padding data contents by %d bytes so signature sector aligns at sector boundary" % pad_by) contents += b'\xff' * pad_by elif args.append_signatures: sig_block_num = 0 while sig_block_num < SIG_BLOCK_MAX_COUNT: sig_block = validate_signature_block(contents, sig_block_num) if sig_block is None: break signature_sector += sig_block # Signature sector is populated with already valid blocks sig_block_num += 1 assert len(signature_sector) % SIG_BLOCK_SIZE == 0 if sig_block_num == 0: print( "No valid signature blocks found. Discarding --append-signature and proceeding to sign the image afresh." ) else: print( "%d valid signature block(s) already present in the signature sector." % sig_block_num) empty_signature_blocks = SIG_BLOCK_MAX_COUNT - sig_block_num if key_count > empty_signature_blocks: raise esptool.FatalError( "Number of keys(%d) more than the empty signature blocks.(%d)" % (key_count, empty_signature_blocks)) contents = contents[:len( contents ) - SECTOR_SIZE] # Signature stripped off the content (the legitimate blocks are included in signature_sector) print("%d signing key(s) found." % key_count) # Calculate digest of data file digest = hashlib.sha256() digest.update(contents) digest = digest.digest() for keyfile in args.keyfile: private_key = _load_sbv2_rsa_signing_key(keyfile.read()) # Sign signature = private_key.sign( digest, padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=32, ), utils.Prehashed(hashes.SHA256())) rsa_primitives = _get_sbv2_rsa_primitives(private_key.public_key()) # Encode in signature block format # # Note: the [::-1] is to byte swap all of the bignum # values (signatures, coefficients) to little endian # for use with the RSA peripheral, rather than big endian # which is conventionally used for RSA. signature_block = struct.pack( "<BBxx32s384sI384sI384s", 0xe7, # magic byte 0x02, # version digest, int_to_bytes(rsa_primitives.n)[::-1], rsa_primitives.e, int_to_bytes(rsa_primitives.rinv)[::-1], rsa_primitives.m & 0xFFFFFFFF, signature[::-1]) signature_block += struct.pack( "<I", zlib.crc32(signature_block) & 0xffffffff) signature_block += b'\x00' * 16 # padding assert len(signature_block) == SIG_BLOCK_SIZE signature_sector += signature_block assert len(signature_sector) > 0 and len( signature_sector ) <= SIG_BLOCK_SIZE * 3 and len(signature_sector) % SIG_BLOCK_SIZE == 0 total_sig_blocks = len(signature_sector) // SIG_BLOCK_SIZE # Pad signature_sector to sector signature_sector = signature_sector + \ (b'\xff' * (SECTOR_SIZE - len(signature_sector))) assert len(signature_sector) == SECTOR_SIZE # Write to output file, or append to existing file if args.output is None: args.datafile.close() args.output = args.datafile.name with open(args.output, "wb") as f: f.write(contents + signature_sector) print( "Signed %d bytes of data from %s. Signature sector now has %d signature blocks." % (len(contents), args.datafile.name, total_sig_blocks))
def burn(self, new_value): # Writing the BLK0 default MAC is not sensible, as it's written in the factory. # # TODO: support writing a new base MAC @ efuse BLK3 raise esptool.FatalError("Writing MAC address is not supported")
def _flash_encryption_operation_esp32(output_file, input_file, flash_address, keyfile, flash_crypt_conf, do_decrypt): key = _load_hardware_key(keyfile) if flash_address % 16 != 0: raise esptool.FatalError( "Starting flash address 0x%x must be a multiple of 16" % flash_address) if flash_crypt_conf == 0: print("WARNING: Setting FLASH_CRYPT_CONF to zero is not recommended") if esptool.PYTHON2: tweak_range = _flash_encryption_tweak_range(flash_crypt_conf) else: tweak_range = _flash_encryption_tweak_range_bits(flash_crypt_conf) key = int.from_bytes(key, byteorder='big', signed=False) backend = default_backend() cipher = None block_offs = flash_address while True: block = input_file.read(16) if len(block) == 0: break elif len(block) < 16: if do_decrypt: raise esptool.FatalError( "Data length is not a multiple of 16 bytes") pad = 16 - len(block) block = block + os.urandom(pad) print( "Note: Padding with %d bytes of random data (encrypted data must be multiple of 16 bytes long)" % pad) if block_offs % 32 == 0 or cipher is None: # each bit of the flash encryption key is XORed with tweak bits derived from the offset of 32 byte block of flash block_key = _flash_encryption_tweak_key(key, block_offs, tweak_range) if cipher is None: # first pass cipher = Cipher(algorithms.AES(block_key), modes.ECB(), backend=backend) # note AES is used inverted for flash encryption, so # "decrypting" flash uses AES encrypt algorithm and vice # versa. (This does not weaken AES.) actor = cipher.encryptor() if do_decrypt else cipher.decryptor( ) else: # performance hack: changing the key using pyca-cryptography API requires recreating # 'actor'. With openssl backend, this re-initializes the openssl cipher context. To save some time, # manually call EVP_CipherInit_ex() in the openssl backend to update the key. # If it fails, fall back to recreating the entire context via public API. try: backend = actor._ctx._backend res = backend._lib.EVP_CipherInit_ex( actor._ctx._ctx, backend._ffi.NULL, backend._ffi.NULL, backend._ffi.from_buffer(block_key), backend._ffi.NULL, actor._ctx._operation, ) backend.openssl_assert(res != 0) except AttributeError: # backend is not an openssl backend, or implementation has changed: fall back to the slow safe version cipher.algorithm.key = block_key actor = cipher.encryptor( ) if do_decrypt else cipher.decryptor() block = block[::-1] # reverse input block byte order block = actor.update(block) output_file.write(block[::-1]) # reverse output block byte order block_offs += 16
def wait_idle(): deadline = time.time() + EFUSE_BURN_TIMEOUT while time.time() < deadline: if self._esp.read_reg(EFUSE_REG_CMD) == 0: return raise esptool.FatalError("Timed out waiting for Efuse controller command to complete")
def burn_key(esp, efuses, args, digest=None): if digest is None: datafile_list = args.keyfile[ 0:len([name for name in args.keyfile if name is not None]):] else: datafile_list = digest[ 0:len([name for name in digest if name is not None]):] efuses.force_write_always = args.force_write_always block_name_list = args.block[ 0:len([name for name in args.block if name is not None]):] keypurpose_list = args.keypurpose[ 0:len([name for name in args.keypurpose if name is not None]):] util.check_duplicate_name_in_list(keypurpose_list) if len(block_name_list) != len(datafile_list) or len( block_name_list) != len(keypurpose_list): raise esptool.FatalError( "The number of blocks (%d), datafile (%d) and " "keypurpose (%d) should be the same." % (len(block_name_list), len(datafile_list), len(keypurpose_list))) assert 1 <= len(block_name_list) <= 2, "Unexpected case" if len(block_name_list) == 2: incompatible = True if "XTS_AES_128_KEY" in keypurpose_list else False permitted_purposes = [ "XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS", "SECURE_BOOT_DIGEST", ] incompatible |= (keypurpose_list[0] in permitted_purposes and keypurpose_list[1] not in permitted_purposes) if incompatible: raise esptool.FatalError("These keypurposes are incompatible %s" % (keypurpose_list)) print("Burn keys to blocks:") for datafile, keypurpose in zip(datafile_list, keypurpose_list): data = datafile if isinstance(datafile, bytes) else datafile.read() if keypurpose == "XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS": efuse = efuses["BLOCK_KEY0_LOW_128"] elif keypurpose == "SECURE_BOOT_DIGEST": efuse = efuses["BLOCK_KEY0_HI_128"] if len(data) == 32: print( "\tProgramming only left-most 128-bits from SHA256 hash of " "public key to highest 128-bits of BLOCK KEY0") data = data[:16] elif len(data) != efuse.bit_len // 8: raise esptool.FatalError( "Wrong length of this file for SECURE_BOOT_DIGEST. " "Got %d (expected %d or %d)" % (len(data), 32, efuse.bit_len // 8)) assert len(data) == 16, "Only 16 bytes expected" else: efuse = efuses["BLOCK_KEY0"] num_bytes = efuse.bit_len // 8 print(" - %s" % (efuse.name), end=" ") revers_msg = None if keypurpose.startswith("XTS_AES_"): revers_msg = "\tReversing byte order for AES-XTS hardware peripheral" data = data[::-1] print("-> [%s]" % (util.hexify(data, " "))) if revers_msg: print(revers_msg) if len(data) != num_bytes: raise esptool.FatalError( "Incorrect key file size %d. " "Key file must be %d bytes (%d bits) of raw binary key data." % (len(data), num_bytes, num_bytes * 8)) if keypurpose.startswith("XTS_AES_"): read_protect = False if args.no_read_protect else True else: read_protect = False write_protect = not args.no_write_protect # using efuse instead of a block gives the advantage # of checking it as the whole field. efuse.save(data) if keypurpose == "XTS_AES_128_KEY": if efuses["XTS_KEY_LENGTH_256"].get(): print("\t'XTS_KEY_LENGTH_256' is already '1'") else: print("\tXTS_KEY_LENGTH_256 -> 1") efuses["XTS_KEY_LENGTH_256"].save(1) if read_protect: print("\tDisabling read to key block") efuse.disable_read() if write_protect: print("\tDisabling write to key block") efuse.disable_write() print("") if not write_protect: print("Keys will remain writeable (due to --no-write-protect)") if args.no_read_protect: print("Keys will remain readable (due to --no-read-protect)") if not efuses.burn_all(check_batch_mode=True): return print("Successful")