1# This file includes the operations with eFuses for ESP32-C5 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 os  # noqa: F401. It is used in IDF scripts
9import traceback
10
11import espsecure
12
13import esptool
14
15from . import fields
16from .. import util
17from ..base_operations import (
18    add_common_commands,
19    add_force_write_always,
20    add_show_sensitive_info_option,
21    burn_bit,
22    burn_block_data,
23    burn_efuse,
24    check_error,
25    dump,
26    read_protect_efuse,
27    summary,
28    write_protect_efuse,
29)
30
31
32def protect_options(p):
33    p.add_argument(
34        "--no-write-protect",
35        help="Disable write-protecting of the key. The key remains writable. "
36        "(The keys use the RS coding scheme that does not support "
37        "post-write data changes. Forced write can damage RS encoding bits.) "
38        "The write-protecting of keypurposes does not depend on the option, "
39        "it will be set anyway.",
40        action="store_true",
41    )
42    p.add_argument(
43        "--no-read-protect",
44        help="Disable read-protecting of the key. The key remains readable software."
45        "The key with keypurpose[USER, RESERVED and *_DIGEST] "
46        "will remain readable anyway. For the rest keypurposes the read-protection "
47        "will be defined the option (Read-protect by default).",
48        action="store_true",
49    )
50
51
52def add_commands(subparsers, efuses):
53    add_common_commands(subparsers, efuses)
54    burn_key = subparsers.add_parser(
55        "burn_key", help="Burn the key block with the specified name"
56    )
57    protect_options(burn_key)
58    add_force_write_always(burn_key)
59    add_show_sensitive_info_option(burn_key)
60    burn_key.add_argument(
61        "block",
62        help="Key block to burn",
63        action="append",
64        choices=efuses.BLOCKS_FOR_KEYS,
65    )
66    burn_key.add_argument(
67        "keyfile",
68        help="File containing 256 bits of binary key data. For the ECDSA_KEY purpose use PEM file.",
69        action="append",
70        type=argparse.FileType("rb"),
71    )
72    burn_key.add_argument(
73        "keypurpose",
74        help="Purpose to set.",
75        action="append",
76        choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
77    )
78    for _ in efuses.BLOCKS_FOR_KEYS:
79        burn_key.add_argument(
80            "block",
81            help="Key block to burn",
82            nargs="?",
83            action="append",
84            metavar="BLOCK",
85            choices=efuses.BLOCKS_FOR_KEYS,
86        )
87        burn_key.add_argument(
88            "keyfile",
89            help="File containing 256 bits of binary key data. For the ECDSA_KEY purpose use PEM file.",
90            nargs="?",
91            action="append",
92            metavar="KEYFILE",
93            type=argparse.FileType("rb"),
94        )
95        burn_key.add_argument(
96            "keypurpose",
97            help="Purpose to set.",
98            nargs="?",
99            action="append",
100            metavar="KEYPURPOSE",
101            choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
102        )
103
104    burn_key_digest = subparsers.add_parser(
105        "burn_key_digest",
106        help="Parse a RSA public key and burn the digest to key efuse block",
107    )
108    protect_options(burn_key_digest)
109    add_force_write_always(burn_key_digest)
110    add_show_sensitive_info_option(burn_key_digest)
111    burn_key_digest.add_argument(
112        "block",
113        help="Key block to burn",
114        action="append",
115        choices=efuses.BLOCKS_FOR_KEYS,
116    )
117    burn_key_digest.add_argument(
118        "keyfile",
119        help="Key file to digest (PEM format)",
120        action="append",
121        type=argparse.FileType("rb"),
122    )
123    burn_key_digest.add_argument(
124        "keypurpose",
125        help="Purpose to set.",
126        action="append",
127        choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES,
128    )
129    for _ in efuses.BLOCKS_FOR_KEYS:
130        burn_key_digest.add_argument(
131            "block",
132            help="Key block to burn",
133            nargs="?",
134            action="append",
135            metavar="BLOCK",
136            choices=efuses.BLOCKS_FOR_KEYS,
137        )
138        burn_key_digest.add_argument(
139            "keyfile",
140            help="Key file to digest (PEM format)",
141            nargs="?",
142            action="append",
143            metavar="KEYFILE",
144            type=argparse.FileType("rb"),
145        )
146        burn_key_digest.add_argument(
147            "keypurpose",
148            help="Purpose to set.",
149            nargs="?",
150            action="append",
151            metavar="KEYPURPOSE",
152            choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES,
153        )
154
155    p = subparsers.add_parser(
156        "set_flash_voltage",
157        help="Permanently set the internal flash voltage regulator "
158        "to either 1.8V, 3.3V or OFF. "
159        "This means GPIO45 can be high or low at reset without "
160        "changing the flash voltage.",
161    )
162    p.add_argument("voltage", help="Voltage selection", choices=["1.8V", "3.3V", "OFF"])
163
164    p = subparsers.add_parser(
165        "burn_custom_mac", help="Burn a 48-bit Custom MAC Address to EFUSE BLOCK3."
166    )
167    p.add_argument(
168        "mac",
169        help="Custom MAC Address to burn given in hexadecimal format with bytes "
170        "separated by colons (e.g. AA:CD:EF:01:02:03).",
171        type=fields.base_fields.CheckArgValue(efuses, "CUSTOM_MAC"),
172    )
173    add_force_write_always(p)
174
175    p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.")
176
177
178def burn_custom_mac(esp, efuses, args):
179    efuses["CUSTOM_MAC"].save(args.mac)
180    if not efuses.burn_all(check_batch_mode=True):
181        return
182    get_custom_mac(esp, efuses, args)
183    print("Successful")
184
185
186def get_custom_mac(esp, efuses, args):
187    print("Custom MAC Address: {}".format(efuses["CUSTOM_MAC"].get()))
188
189
190def set_flash_voltage(esp, efuses, args):
191    raise esptool.FatalError("set_flash_voltage is not supported!")
192
193
194def adc_info(esp, efuses, args):
195    print("not supported yet")
196
197
198def burn_key(esp, efuses, args, digest=None):
199    if digest is None:
200        datafile_list = args.keyfile[
201            0 : len([name for name in args.keyfile if name is not None]) :
202        ]
203    else:
204        datafile_list = digest[0 : len([name for name in digest if name is not None]) :]
205    efuses.force_write_always = args.force_write_always
206    block_name_list = args.block[
207        0 : len([name for name in args.block if name is not None]) :
208    ]
209    keypurpose_list = args.keypurpose[
210        0 : len([name for name in args.keypurpose if name is not None]) :
211    ]
212
213    util.check_duplicate_name_in_list(block_name_list)
214    if len(block_name_list) != len(datafile_list) or len(block_name_list) != len(
215        keypurpose_list
216    ):
217        raise esptool.FatalError(
218            "The number of blocks (%d), datafile (%d) and keypurpose (%d) "
219            "should be the same."
220            % (len(block_name_list), len(datafile_list), len(keypurpose_list))
221        )
222
223    print("Burn keys to blocks:")
224    for block_name, datafile, keypurpose in zip(
225        block_name_list, datafile_list, keypurpose_list
226    ):
227        efuse = None
228        for block in efuses.blocks:
229            if block_name == block.name or block_name in block.alias:
230                efuse = efuses[block.name]
231        if efuse is None:
232            raise esptool.FatalError("Unknown block name - %s" % (block_name))
233        num_bytes = efuse.bit_len // 8
234
235        block_num = efuses.get_index_block_by_name(block_name)
236        block = efuses.blocks[block_num]
237
238        if digest is None:
239            if keypurpose == "ECDSA_KEY":
240                sk = espsecure.load_ecdsa_signing_key(datafile)
241                data = sk.to_string()
242                if len(data) == 24:
243                    # the private key is 24 bytes long for NIST192p, and 8 bytes of padding
244                    data = b"\x00" * 8 + data
245            else:
246                data = datafile.read()
247        else:
248            data = datafile
249
250        print(" - %s" % (efuse.name), end=" ")
251        revers_msg = None
252        if efuses[block.key_purpose_name].need_reverse(keypurpose):
253            revers_msg = f"\tReversing byte order for {keypurpose} hardware peripheral"
254            data = data[::-1]
255        print(
256            "-> [{}]".format(
257                util.hexify(data, " ")
258                if args.show_sensitive_info
259                else " ".join(["??"] * len(data))
260            )
261        )
262        if revers_msg:
263            print(revers_msg)
264        if len(data) != num_bytes:
265            raise esptool.FatalError(
266                "Incorrect key file size %d. Key file must be %d bytes (%d bits) "
267                "of raw binary key data." % (len(data), num_bytes, num_bytes * 8)
268            )
269
270        if efuses[block.key_purpose_name].need_rd_protect(keypurpose):
271            read_protect = False if args.no_read_protect else True
272        else:
273            read_protect = False
274        write_protect = not args.no_write_protect
275
276        # using efuse instead of a block gives the advantage of checking it as the whole field.
277        efuse.save(data)
278
279        disable_wr_protect_key_purpose = False
280        if efuses[block.key_purpose_name].get() != keypurpose:
281            if efuses[block.key_purpose_name].is_writeable():
282                print(
283                    "\t'%s': '%s' -> '%s'."
284                    % (
285                        block.key_purpose_name,
286                        efuses[block.key_purpose_name].get(),
287                        keypurpose,
288                    )
289                )
290                efuses[block.key_purpose_name].save(keypurpose)
291                disable_wr_protect_key_purpose = True
292            else:
293                raise esptool.FatalError(
294                    "It is not possible to change '%s' to '%s' "
295                    "because write protection bit is set."
296                    % (block.key_purpose_name, keypurpose)
297                )
298        else:
299            print("\t'%s' is already '%s'." % (block.key_purpose_name, keypurpose))
300            if efuses[block.key_purpose_name].is_writeable():
301                disable_wr_protect_key_purpose = True
302
303        if disable_wr_protect_key_purpose:
304            print("\tDisabling write to '%s'." % block.key_purpose_name)
305            efuses[block.key_purpose_name].disable_write()
306
307        if read_protect:
308            print("\tDisabling read to key block")
309            efuse.disable_read()
310
311        if write_protect:
312            print("\tDisabling write to key block")
313            efuse.disable_write()
314        print("")
315
316    if not write_protect:
317        print("Keys will remain writeable (due to --no-write-protect)")
318    if args.no_read_protect:
319        print("Keys will remain readable (due to --no-read-protect)")
320
321    if not efuses.burn_all(check_batch_mode=True):
322        return
323    print("Successful")
324
325
326def burn_key_digest(esp, efuses, args):
327    digest_list = []
328    datafile_list = args.keyfile[
329        0 : len([name for name in args.keyfile if name is not None]) :
330    ]
331    block_list = args.block[
332        0 : len([block for block in args.block if block is not None]) :
333    ]
334    for block_name, datafile in zip(block_list, datafile_list):
335        efuse = None
336        for block in efuses.blocks:
337            if block_name == block.name or block_name in block.alias:
338                efuse = efuses[block.name]
339        if efuse is None:
340            raise esptool.FatalError("Unknown block name - %s" % (block_name))
341        num_bytes = efuse.bit_len // 8
342        digest = espsecure._digest_sbv2_public_key(datafile)
343        if len(digest) != num_bytes:
344            raise esptool.FatalError(
345                "Incorrect digest size %d. Digest must be %d bytes (%d bits) "
346                "of raw binary key data." % (len(digest), num_bytes, num_bytes * 8)
347            )
348        digest_list.append(digest)
349    burn_key(esp, efuses, args, digest=digest_list)
350
351
352def espefuse(esp, efuses, args, command):
353    parser = argparse.ArgumentParser()
354    subparsers = parser.add_subparsers(dest="operation")
355    add_commands(subparsers, efuses)
356    try:
357        cmd_line_args = parser.parse_args(command.split())
358    except SystemExit:
359        traceback.print_stack()
360        raise esptool.FatalError('"{}" - incorrect command'.format(command))
361    if cmd_line_args.operation == "execute_scripts":
362        configfiles = cmd_line_args.configfiles
363        index = cmd_line_args.index
364    # copy arguments from args to cmd_line_args
365    vars(cmd_line_args).update(vars(args))
366    if cmd_line_args.operation == "execute_scripts":
367        cmd_line_args.configfiles = configfiles
368        cmd_line_args.index = index
369    if cmd_line_args.operation is None:
370        parser.print_help()
371        parser.exit(1)
372    operation_func = globals()[cmd_line_args.operation]
373    # each 'operation' is a module-level function of the same name
374    operation_func(esp, efuses, cmd_line_args)
375
376
377def execute_scripts(esp, efuses, args):
378    efuses.batch_mode_cnt += 1
379    del args.operation
380    scripts = args.scripts
381    del args.scripts
382
383    for file in scripts:
384        with open(file.name, "r") as file:
385            exec(compile(file.read(), file.name, "exec"))
386
387    if args.debug:
388        for block in efuses.blocks:
389            data = block.get_bitstring(from_read=False)
390            block.print_block(data, "regs_for_burn", args.debug)
391
392    efuses.batch_mode_cnt -= 1
393    if not efuses.burn_all(check_batch_mode=True):
394        return
395    print("Successful")
396