1# This file includes the operations with eFuses for ESP32-C61 chip 2# 3# SPDX-FileCopyrightText: 2024 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 " 38 "post-write 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. For the ECDSA_KEY purpose use PEM file.", 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. For the ECDSA_KEY purpose use PEM file.", 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 raise esptool.FatalError("set_flash_voltage is not supported!") 193 194 195def adc_info(esp, efuses, args): 196 print("not supported yet") 197 198 199def key_block_is_unused(block, key_purpose_block): 200 if not block.is_readable() or not block.is_writeable(): 201 return False 202 203 if key_purpose_block.get() != "USER" or not key_purpose_block.is_writeable(): 204 return False 205 206 if not block.get_bitstring().all(False): 207 return False 208 209 return True 210 211 212def get_next_key_block(efuses, current_key_block, block_name_list): 213 key_blocks = [b for b in efuses.blocks if b.key_purpose_name] 214 start = key_blocks.index(current_key_block) 215 216 # Sort key blocks so that we pick the next free block (and loop around if necessary) 217 key_blocks = key_blocks[start:] + key_blocks[0:start] 218 219 # Exclude any other blocks that will be be burned 220 key_blocks = [b for b in key_blocks if b.name not in block_name_list] 221 222 for block in key_blocks: 223 key_purpose_block = efuses[block.key_purpose_name] 224 if key_block_is_unused(block, key_purpose_block): 225 return block 226 227 return None 228 229 230def split_512_bit_key(efuses, block_name_list, datafile_list, keypurpose_list): 231 i = keypurpose_list.index("XTS_AES_256_KEY") 232 block_name = block_name_list[i] 233 234 block_num = efuses.get_index_block_by_name(block_name) 235 block = efuses.blocks[block_num] 236 237 data = datafile_list[i].read() 238 if len(data) != 64: 239 raise esptool.FatalError( 240 "Incorrect key file size %d, XTS_AES_256_KEY should be 64 bytes" % len(data) 241 ) 242 243 key_block_2 = get_next_key_block(efuses, block, block_name_list) 244 if not key_block_2: 245 raise esptool.FatalError("XTS_AES_256_KEY requires two free keyblocks") 246 247 keypurpose_list.append("XTS_AES_256_KEY_1") 248 datafile_list.append(io.BytesIO(data[:32])) 249 block_name_list.append(block_name) 250 251 keypurpose_list.append("XTS_AES_256_KEY_2") 252 datafile_list.append(io.BytesIO(data[32:])) 253 block_name_list.append(key_block_2.name) 254 255 keypurpose_list.pop(i) 256 datafile_list.pop(i) 257 block_name_list.pop(i) 258 259 260def burn_key(esp, efuses, args, digest=None): 261 if digest is None: 262 datafile_list = args.keyfile[ 263 0 : len([name for name in args.keyfile if name is not None]) : 264 ] 265 else: 266 datafile_list = digest[0 : len([name for name in digest if name is not None]) :] 267 efuses.force_write_always = args.force_write_always 268 block_name_list = args.block[ 269 0 : len([name for name in args.block if name is not None]) : 270 ] 271 keypurpose_list = args.keypurpose[ 272 0 : len([name for name in args.keypurpose if name is not None]) : 273 ] 274 275 if "XTS_AES_256_KEY" in keypurpose_list: 276 # XTS_AES_256_KEY is not an actual HW key purpose, needs to be split into 277 # XTS_AES_256_KEY_1 and XTS_AES_256_KEY_2 278 split_512_bit_key(efuses, block_name_list, datafile_list, keypurpose_list) 279 280 util.check_duplicate_name_in_list(block_name_list) 281 if len(block_name_list) != len(datafile_list) or len(block_name_list) != len( 282 keypurpose_list 283 ): 284 raise esptool.FatalError( 285 "The number of blocks (%d), datafile (%d) and keypurpose (%d) " 286 "should be the same." 287 % (len(block_name_list), len(datafile_list), len(keypurpose_list)) 288 ) 289 290 print("Burn keys to blocks:") 291 for block_name, datafile, keypurpose in zip( 292 block_name_list, datafile_list, keypurpose_list 293 ): 294 efuse = None 295 for block in efuses.blocks: 296 if block_name == block.name or block_name in block.alias: 297 efuse = efuses[block.name] 298 if efuse is None: 299 raise esptool.FatalError("Unknown block name - %s" % (block_name)) 300 num_bytes = efuse.bit_len // 8 301 302 block_num = efuses.get_index_block_by_name(block_name) 303 block = efuses.blocks[block_num] 304 305 if digest is None: 306 if keypurpose == "ECDSA_KEY": 307 sk = espsecure.load_ecdsa_signing_key(datafile) 308 data = sk.to_string() 309 if len(data) == 24: 310 # the private key is 24 bytes long for NIST192p, and 8 bytes of padding 311 data = b"\x00" * 8 + data 312 else: 313 data = datafile.read() 314 else: 315 data = datafile 316 317 print(" - %s" % (efuse.name), end=" ") 318 revers_msg = None 319 if efuses[block.key_purpose_name].need_reverse(keypurpose): 320 revers_msg = f"\tReversing byte order for {keypurpose} hardware peripheral" 321 data = data[::-1] 322 print( 323 "-> [{}]".format( 324 util.hexify(data, " ") 325 if args.show_sensitive_info 326 else " ".join(["??"] * len(data)) 327 ) 328 ) 329 if revers_msg: 330 print(revers_msg) 331 if len(data) != num_bytes: 332 raise esptool.FatalError( 333 "Incorrect key file size %d. Key file must be %d bytes (%d bits) " 334 "of raw binary key data." % (len(data), num_bytes, num_bytes * 8) 335 ) 336 337 if efuses[block.key_purpose_name].need_rd_protect(keypurpose): 338 read_protect = False if args.no_read_protect else True 339 else: 340 read_protect = False 341 write_protect = not args.no_write_protect 342 343 # using efuse instead of a block gives the advantage of checking it as the whole field. 344 efuse.save(data) 345 346 disable_wr_protect_key_purpose = False 347 if efuses[block.key_purpose_name].get() != keypurpose: 348 if efuses[block.key_purpose_name].is_writeable(): 349 print( 350 "\t'%s': '%s' -> '%s'." 351 % ( 352 block.key_purpose_name, 353 efuses[block.key_purpose_name].get(), 354 keypurpose, 355 ) 356 ) 357 efuses[block.key_purpose_name].save(keypurpose) 358 disable_wr_protect_key_purpose = True 359 else: 360 raise esptool.FatalError( 361 "It is not possible to change '%s' to '%s' " 362 "because write protection bit is set." 363 % (block.key_purpose_name, keypurpose) 364 ) 365 else: 366 print("\t'%s' is already '%s'." % (block.key_purpose_name, keypurpose)) 367 if efuses[block.key_purpose_name].is_writeable(): 368 disable_wr_protect_key_purpose = True 369 370 if disable_wr_protect_key_purpose: 371 print("\tDisabling write to '%s'." % block.key_purpose_name) 372 efuses[block.key_purpose_name].disable_write() 373 374 if read_protect: 375 print("\tDisabling read to key block") 376 efuse.disable_read() 377 378 if write_protect: 379 print("\tDisabling write to key block") 380 efuse.disable_write() 381 print("") 382 383 if not write_protect: 384 print("Keys will remain writeable (due to --no-write-protect)") 385 if args.no_read_protect: 386 print("Keys will remain readable (due to --no-read-protect)") 387 388 if not efuses.burn_all(check_batch_mode=True): 389 return 390 print("Successful") 391 392 393def burn_key_digest(esp, efuses, args): 394 digest_list = [] 395 datafile_list = args.keyfile[ 396 0 : len([name for name in args.keyfile if name is not None]) : 397 ] 398 block_list = args.block[ 399 0 : len([block for block in args.block if block is not None]) : 400 ] 401 for block_name, datafile in zip(block_list, datafile_list): 402 efuse = None 403 for block in efuses.blocks: 404 if block_name == block.name or block_name in block.alias: 405 efuse = efuses[block.name] 406 if efuse is None: 407 raise esptool.FatalError("Unknown block name - %s" % (block_name)) 408 num_bytes = efuse.bit_len // 8 409 digest = espsecure._digest_sbv2_public_key(datafile) 410 if len(digest) != num_bytes: 411 raise esptool.FatalError( 412 "Incorrect digest size %d. Digest must be %d bytes (%d bits) " 413 "of raw binary key data." % (len(digest), num_bytes, num_bytes * 8) 414 ) 415 digest_list.append(digest) 416 burn_key(esp, efuses, args, digest=digest_list) 417 418 419def espefuse(esp, efuses, args, command): 420 parser = argparse.ArgumentParser() 421 subparsers = parser.add_subparsers(dest="operation") 422 add_commands(subparsers, efuses) 423 try: 424 cmd_line_args = parser.parse_args(command.split()) 425 except SystemExit: 426 traceback.print_stack() 427 raise esptool.FatalError('"{}" - incorrect command'.format(command)) 428 if cmd_line_args.operation == "execute_scripts": 429 configfiles = cmd_line_args.configfiles 430 index = cmd_line_args.index 431 # copy arguments from args to cmd_line_args 432 vars(cmd_line_args).update(vars(args)) 433 if cmd_line_args.operation == "execute_scripts": 434 cmd_line_args.configfiles = configfiles 435 cmd_line_args.index = index 436 if cmd_line_args.operation is None: 437 parser.print_help() 438 parser.exit(1) 439 operation_func = globals()[cmd_line_args.operation] 440 # each 'operation' is a module-level function of the same name 441 operation_func(esp, efuses, cmd_line_args) 442 443 444def execute_scripts(esp, efuses, args): 445 efuses.batch_mode_cnt += 1 446 del args.operation 447 scripts = args.scripts 448 del args.scripts 449 450 for file in scripts: 451 with open(file.name, "r") as file: 452 exec(compile(file.read(), file.name, "exec")) 453 454 if args.debug: 455 for block in efuses.blocks: 456 data = block.get_bitstring(from_read=False) 457 block.print_block(data, "regs_for_burn", args.debug) 458 459 efuses.batch_mode_cnt -= 1 460 if not efuses.burn_all(check_batch_mode=True): 461 return 462 print("Successful") 463