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