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