1# This file includes the operations with eFuses for ESP32S2 chip 2# 3# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD 4# 5# SPDX-License-Identifier: GPL-2.0-or-later 6 7import argparse 8import io 9import os # noqa: F401. It is used in IDF scripts 10import traceback 11 12import espsecure 13 14import esptool 15 16from . import fields 17from .. import util 18from ..base_operations import ( 19 add_common_commands, 20 add_force_write_always, 21 add_show_sensitive_info_option, 22 burn_bit, 23 burn_block_data, 24 burn_efuse, 25 check_error, 26 dump, 27 read_protect_efuse, 28 summary, 29 write_protect_efuse, 30) 31 32 33def protect_options(p): 34 p.add_argument( 35 "--no-write-protect", 36 help="Disable write-protecting of the key. The key remains writable. " 37 "(The keys use the RS coding scheme that does not support post-write " 38 "data changes. Forced write can damage RS encoding bits.) " 39 "The write-protecting of keypurposes does not depend on the option, " 40 "it will be set anyway.", 41 action="store_true", 42 ) 43 p.add_argument( 44 "--no-read-protect", 45 help="Disable read-protecting of the key. The key remains readable software." 46 "The key with keypurpose[USER, RESERVED and *_DIGEST] " 47 "will remain readable anyway. For the rest keypurposes the read-protection " 48 "will be defined the option (Read-protect by default).", 49 action="store_true", 50 ) 51 52 53def add_commands(subparsers, efuses): 54 add_common_commands(subparsers, efuses) 55 burn_key = subparsers.add_parser( 56 "burn_key", help="Burn the key block with the specified name" 57 ) 58 protect_options(burn_key) 59 add_force_write_always(burn_key) 60 add_show_sensitive_info_option(burn_key) 61 burn_key.add_argument( 62 "block", 63 help="Key block to burn", 64 action="append", 65 choices=efuses.BLOCKS_FOR_KEYS, 66 ) 67 burn_key.add_argument( 68 "keyfile", 69 help="File containing 256 bits of binary key data", 70 action="append", 71 type=argparse.FileType("rb"), 72 ) 73 burn_key.add_argument( 74 "keypurpose", 75 help="Purpose to set.", 76 action="append", 77 choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME, 78 ) 79 for _ in efuses.BLOCKS_FOR_KEYS: 80 burn_key.add_argument( 81 "block", 82 help="Key block to burn", 83 nargs="?", 84 action="append", 85 metavar="BLOCK", 86 choices=efuses.BLOCKS_FOR_KEYS, 87 ) 88 burn_key.add_argument( 89 "keyfile", 90 help="File containing 256 bits of binary key data", 91 nargs="?", 92 action="append", 93 metavar="KEYFILE", 94 type=argparse.FileType("rb"), 95 ) 96 burn_key.add_argument( 97 "keypurpose", 98 help="Purpose to set.", 99 nargs="?", 100 action="append", 101 metavar="KEYPURPOSE", 102 choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME, 103 ) 104 105 burn_key_digest = subparsers.add_parser( 106 "burn_key_digest", 107 help="Parse a RSA public key and burn the digest to key efuse block", 108 ) 109 protect_options(burn_key_digest) 110 add_force_write_always(burn_key_digest) 111 add_show_sensitive_info_option(burn_key_digest) 112 burn_key_digest.add_argument( 113 "block", 114 help="Key block to burn", 115 action="append", 116 choices=efuses.BLOCKS_FOR_KEYS, 117 ) 118 burn_key_digest.add_argument( 119 "keyfile", 120 help="Key file to digest (PEM format)", 121 action="append", 122 type=argparse.FileType("rb"), 123 ) 124 burn_key_digest.add_argument( 125 "keypurpose", 126 help="Purpose to set.", 127 action="append", 128 choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES, 129 ) 130 for _ in efuses.BLOCKS_FOR_KEYS: 131 burn_key_digest.add_argument( 132 "block", 133 help="Key block to burn", 134 nargs="?", 135 action="append", 136 metavar="BLOCK", 137 choices=efuses.BLOCKS_FOR_KEYS, 138 ) 139 burn_key_digest.add_argument( 140 "keyfile", 141 help="Key file to digest (PEM format)", 142 nargs="?", 143 action="append", 144 metavar="KEYFILE", 145 type=argparse.FileType("rb"), 146 ) 147 burn_key_digest.add_argument( 148 "keypurpose", 149 help="Purpose to set.", 150 nargs="?", 151 action="append", 152 metavar="KEYPURPOSE", 153 choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES, 154 ) 155 156 p = subparsers.add_parser( 157 "set_flash_voltage", 158 help="Permanently set the internal flash voltage regulator " 159 "to either 1.8V, 3.3V or OFF. " 160 "This means GPIO45 can be high or low at reset without " 161 "changing the flash voltage.", 162 ) 163 p.add_argument("voltage", help="Voltage selection", choices=["1.8V", "3.3V", "OFF"]) 164 165 p = subparsers.add_parser( 166 "burn_custom_mac", help="Burn a 48-bit Custom MAC Address to EFUSE BLOCK3." 167 ) 168 p.add_argument( 169 "mac", 170 help="Custom MAC Address to burn given in hexadecimal format with bytes " 171 "separated by colons (e.g. AA:CD:EF:01:02:03).", 172 type=fields.base_fields.CheckArgValue(efuses, "CUSTOM_MAC"), 173 ) 174 add_force_write_always(p) 175 176 p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.") 177 178 179def burn_custom_mac(esp, efuses, args): 180 efuses["CUSTOM_MAC"].save(args.mac) 181 if not efuses.burn_all(check_batch_mode=True): 182 return 183 get_custom_mac(esp, efuses, args) 184 print("Successful") 185 186 187def get_custom_mac(esp, efuses, args): 188 print("Custom MAC Address: {}".format(efuses["CUSTOM_MAC"].get())) 189 190 191def set_flash_voltage(esp, efuses, args): 192 sdio_force = efuses["VDD_SPI_FORCE"] 193 sdio_tieh = efuses["VDD_SPI_TIEH"] 194 sdio_reg = efuses["VDD_SPI_XPD"] 195 196 # check efuses aren't burned in a way which makes this impossible 197 if args.voltage == "OFF" and sdio_reg.get() != 0: 198 raise esptool.FatalError( 199 "Can't set flash regulator to OFF as VDD_SPI_XPD efuse is already burned" 200 ) 201 202 if args.voltage == "1.8V" and sdio_tieh.get() != 0: 203 raise esptool.FatalError( 204 "Can't set regulator to 1.8V is VDD_SPI_TIEH efuse is already burned" 205 ) 206 207 if args.voltage == "OFF": 208 msg = "Disable internal flash voltage regulator (VDD_SPI). SPI flash will " 209 "need to be powered from an external source.\n" 210 "The following efuse is burned: VDD_SPI_FORCE.\n" 211 "It is possible to later re-enable the internal regulator (%s) " % ( 212 "to 3.3V" if sdio_tieh.get() != 0 else "to 1.8V or 3.3V" 213 ) 214 "by burning an additional efuse" 215 elif args.voltage == "1.8V": 216 msg = "Set internal flash voltage regulator (VDD_SPI) to 1.8V.\n" 217 "The following efuses are burned: VDD_SPI_FORCE, VDD_SPI_XPD.\n" 218 "It is possible to later increase the voltage to 3.3V (permanently) " 219 "by burning additional efuse VDD_SPI_TIEH" 220 elif args.voltage == "3.3V": 221 msg = "Enable internal flash voltage regulator (VDD_SPI) to 3.3V.\n" 222 "The following efuses are burned: VDD_SPI_FORCE, VDD_SPI_XPD, VDD_SPI_TIEH." 223 print(msg) 224 225 sdio_force.save(1) # Disable GPIO45 226 if args.voltage != "OFF": 227 sdio_reg.save(1) # Enable internal regulator 228 if args.voltage == "3.3V": 229 sdio_tieh.save(1) 230 print("VDD_SPI setting complete.") 231 232 if not efuses.burn_all(check_batch_mode=True): 233 return 234 print("Successful") 235 236 237def adc_info(esp, efuses, args): 238 print("") 239 # fmt: off 240 if efuses["BLK_VERSION_MINOR"].get() == 1: 241 print("Temperature Sensor Calibration = {}C".format(efuses["TEMP_CALIB"].get())) 242 print("TADC_CALIB = {}C".format(efuses["ADC_CALIB"].get())) 243 print("RTCCALIB_V1IDX_A10H = ", efuses["RTCCALIB_V1IDX_A10H"].get()) 244 print("RTCCALIB_V1IDX_A11H = ", efuses["RTCCALIB_V1IDX_A11H"].get()) 245 print("RTCCALIB_V1IDX_A12H = ", efuses["RTCCALIB_V1IDX_A12H"].get()) 246 print("RTCCALIB_V1IDX_A13H = ", efuses["RTCCALIB_V1IDX_A13H"].get()) 247 print("RTCCALIB_V1IDX_A20H = ", efuses["RTCCALIB_V1IDX_A20H"].get()) 248 print("RTCCALIB_V1IDX_A21H = ", efuses["RTCCALIB_V1IDX_A21H"].get()) 249 print("RTCCALIB_V1IDX_A22H = ", efuses["RTCCALIB_V1IDX_A22H"].get()) 250 print("RTCCALIB_V1IDX_A23H = ", efuses["RTCCALIB_V1IDX_A23H"].get()) 251 print("RTCCALIB_V1IDX_A10L = ", efuses["RTCCALIB_V1IDX_A10L"].get()) 252 print("RTCCALIB_V1IDX_A11L = ", efuses["RTCCALIB_V1IDX_A11L"].get()) 253 print("RTCCALIB_V1IDX_A12L = ", efuses["RTCCALIB_V1IDX_A12L"].get()) 254 print("RTCCALIB_V1IDX_A13L = ", efuses["RTCCALIB_V1IDX_A13L"].get()) 255 print("RTCCALIB_V1IDX_A20L = ", efuses["RTCCALIB_V1IDX_A20L"].get()) 256 print("RTCCALIB_V1IDX_A21L = ", efuses["RTCCALIB_V1IDX_A21L"].get()) 257 print("RTCCALIB_V1IDX_A22L = ", efuses["RTCCALIB_V1IDX_A22L"].get()) 258 print("RTCCALIB_V1IDX_A23L = ", efuses["RTCCALIB_V1IDX_A23L"].get()) 259 else: 260 print("BLK_VERSION_MINOR = ", efuses["BLK_VERSION_MINOR"].get_meaning()) 261 # fmt: on 262 263 264def key_block_is_unused(block, key_purpose_block): 265 if not block.is_readable() or not block.is_writeable(): 266 return False 267 268 if key_purpose_block.get() != "USER" or not key_purpose_block.is_writeable(): 269 return False 270 271 if not block.get_bitstring().all(False): 272 return False 273 274 return True 275 276 277def get_next_key_block(efuses, current_key_block, block_name_list): 278 key_blocks = [b for b in efuses.blocks if b.key_purpose_name] 279 start = key_blocks.index(current_key_block) 280 281 # Sort key blocks so that we pick the next free block (and loop around if necessary) 282 key_blocks = key_blocks[start:] + key_blocks[0:start] 283 284 # Exclude any other blocks that will be be burned 285 key_blocks = [b for b in key_blocks if b.name not in block_name_list] 286 287 for block in key_blocks: 288 key_purpose_block = efuses[block.key_purpose_name] 289 if key_block_is_unused(block, key_purpose_block): 290 return block 291 292 return None 293 294 295def split_512_bit_key(efuses, block_name_list, datafile_list, keypurpose_list): 296 i = keypurpose_list.index("XTS_AES_256_KEY") 297 block_name = block_name_list[i] 298 299 block_num = efuses.get_index_block_by_name(block_name) 300 block = efuses.blocks[block_num] 301 302 data = datafile_list[i].read() 303 if len(data) != 64: 304 raise esptool.FatalError( 305 "Incorrect key file size %d, XTS_AES_256_KEY should be 64 bytes" % len(data) 306 ) 307 308 key_block_2 = get_next_key_block(efuses, block, block_name_list) 309 if not key_block_2: 310 raise esptool.FatalError("XTS_AES_256_KEY requires two free keyblocks") 311 312 keypurpose_list.append("XTS_AES_256_KEY_1") 313 datafile_list.append(io.BytesIO(data[:32])) 314 block_name_list.append(block_name) 315 316 keypurpose_list.append("XTS_AES_256_KEY_2") 317 datafile_list.append(io.BytesIO(data[32:])) 318 block_name_list.append(key_block_2.name) 319 320 keypurpose_list.pop(i) 321 datafile_list.pop(i) 322 block_name_list.pop(i) 323 324 325def burn_key(esp, efuses, args, digest=None): 326 if digest is None: 327 datafile_list = args.keyfile[ 328 0 : len([name for name in args.keyfile if name is not None]) : 329 ] 330 else: 331 datafile_list = digest[0 : len([name for name in digest if name is not None]) :] 332 efuses.force_write_always = args.force_write_always 333 block_name_list = args.block[ 334 0 : len([name for name in args.block if name is not None]) : 335 ] 336 keypurpose_list = args.keypurpose[ 337 0 : len([name for name in args.keypurpose if name is not None]) : 338 ] 339 340 if "XTS_AES_256_KEY" in keypurpose_list: 341 # XTS_AES_256_KEY is not an actual HW key purpose, needs to be split into 342 # XTS_AES_256_KEY_1 and XTS_AES_256_KEY_2 343 split_512_bit_key(efuses, block_name_list, datafile_list, keypurpose_list) 344 345 util.check_duplicate_name_in_list(block_name_list) 346 if len(block_name_list) != len(datafile_list) or len(block_name_list) != len( 347 keypurpose_list 348 ): 349 raise esptool.FatalError( 350 "The number of blocks (%d), datafile (%d) and keypurpose (%d) " 351 "should be the same." 352 % (len(block_name_list), len(datafile_list), len(keypurpose_list)) 353 ) 354 355 print("Burn keys to blocks:") 356 for block_name, datafile, keypurpose in zip( 357 block_name_list, datafile_list, keypurpose_list 358 ): 359 efuse = None 360 for block in efuses.blocks: 361 if block_name == block.name or block_name in block.alias: 362 efuse = efuses[block.name] 363 if efuse is None: 364 raise esptool.FatalError("Unknown block name - %s" % (block_name)) 365 num_bytes = efuse.bit_len // 8 366 367 block_num = efuses.get_index_block_by_name(block_name) 368 block = efuses.blocks[block_num] 369 370 if digest is None: 371 data = datafile.read() 372 else: 373 data = datafile 374 375 print(" - %s" % (efuse.name), end=" ") 376 revers_msg = None 377 if efuses[block.key_purpose_name].need_reverse(keypurpose): 378 revers_msg = "\tReversing byte order for AES-XTS hardware peripheral" 379 data = data[::-1] 380 print( 381 "-> [{}]".format( 382 util.hexify(data, " ") 383 if args.show_sensitive_info 384 else " ".join(["??"] * len(data)) 385 ) 386 ) 387 if revers_msg: 388 print(revers_msg) 389 if len(data) != num_bytes: 390 raise esptool.FatalError( 391 "Incorrect key file size %d. Key file must be %d bytes (%d bits) " 392 "of raw binary key data." % (len(data), num_bytes, num_bytes * 8) 393 ) 394 395 if efuses[block.key_purpose_name].need_rd_protect(keypurpose): 396 read_protect = False if args.no_read_protect else True 397 else: 398 read_protect = False 399 write_protect = not args.no_write_protect 400 401 # using efuse instead of a block gives the advantage of 402 # checking it as the whole field. 403 efuse.save(data) 404 405 disable_wr_protect_key_purpose = False 406 if efuses[block.key_purpose_name].get() != keypurpose: 407 if efuses[block.key_purpose_name].is_writeable(): 408 print( 409 "\t'%s': '%s' -> '%s'." 410 % ( 411 block.key_purpose_name, 412 efuses[block.key_purpose_name].get(), 413 keypurpose, 414 ) 415 ) 416 efuses[block.key_purpose_name].save(keypurpose) 417 disable_wr_protect_key_purpose = True 418 else: 419 raise esptool.FatalError( 420 "It is not possible to change '%s' to '%s' because " 421 "write protection bit is set." 422 % (block.key_purpose_name, keypurpose) 423 ) 424 else: 425 print("\t'%s' is already '%s'." % (block.key_purpose_name, keypurpose)) 426 if efuses[block.key_purpose_name].is_writeable(): 427 disable_wr_protect_key_purpose = True 428 429 if disable_wr_protect_key_purpose: 430 print("\tDisabling write to '%s'." % block.key_purpose_name) 431 efuses[block.key_purpose_name].disable_write() 432 433 if read_protect: 434 print("\tDisabling read to key block") 435 efuse.disable_read() 436 437 if write_protect: 438 print("\tDisabling write to key block") 439 efuse.disable_write() 440 print("") 441 442 if not write_protect: 443 print("Keys will remain writeable (due to --no-write-protect)") 444 if args.no_read_protect: 445 print("Keys will remain readable (due to --no-read-protect)") 446 447 if not efuses.burn_all(check_batch_mode=True): 448 return 449 print("Successful") 450 451 452def burn_key_digest(esp, efuses, args): 453 digest_list = [] 454 datafile_list = args.keyfile[ 455 0 : len([name for name in args.keyfile if name is not None]) : 456 ] 457 block_list = args.block[ 458 0 : len([block for block in args.block if block is not None]) : 459 ] 460 for block_name, datafile in zip(block_list, datafile_list): 461 efuse = None 462 for block in efuses.blocks: 463 if block_name == block.name or block_name in block.alias: 464 efuse = efuses[block.name] 465 if efuse is None: 466 raise esptool.FatalError("Unknown block name - %s" % (block_name)) 467 num_bytes = efuse.bit_len // 8 468 digest = espsecure._digest_sbv2_public_key(datafile) 469 if len(digest) != num_bytes: 470 raise esptool.FatalError( 471 "Incorrect digest size %d. Digest must be %d bytes (%d bits) of raw " 472 "binary key data." % (len(digest), num_bytes, num_bytes * 8) 473 ) 474 digest_list.append(digest) 475 burn_key(esp, efuses, args, digest=digest_list) 476 477 478def espefuse(esp, efuses, args, command): 479 parser = argparse.ArgumentParser() 480 subparsers = parser.add_subparsers(dest="operation") 481 add_commands(subparsers, efuses) 482 try: 483 cmd_line_args = parser.parse_args(command.split()) 484 except SystemExit: 485 traceback.print_stack() 486 raise esptool.FatalError('"{}" - incorrect command'.format(command)) 487 if cmd_line_args.operation == "execute_scripts": 488 configfiles = cmd_line_args.configfiles 489 index = cmd_line_args.index 490 # copy arguments from args to cmd_line_args 491 vars(cmd_line_args).update(vars(args)) 492 if cmd_line_args.operation == "execute_scripts": 493 cmd_line_args.configfiles = configfiles 494 cmd_line_args.index = index 495 if cmd_line_args.operation is None: 496 parser.print_help() 497 parser.exit(1) 498 operation_func = globals()[cmd_line_args.operation] 499 # each 'operation' is a module-level function of the same name 500 operation_func(esp, efuses, cmd_line_args) 501 502 503def execute_scripts(esp, efuses, args): 504 efuses.batch_mode_cnt += 1 505 del args.operation 506 scripts = args.scripts 507 del args.scripts 508 509 for file in scripts: 510 with open(file.name, "r") as file: 511 exec(compile(file.read(), file.name, "exec")) 512 513 if args.debug: 514 for block in efuses.blocks: 515 data = block.get_bitstring(from_read=False) 516 block.print_block(data, "regs_for_burn", args.debug) 517 518 efuses.batch_mode_cnt -= 1 519 if not efuses.burn_all(check_batch_mode=True): 520 return 521 print("Successful") 522