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