1# This file includes the operations with eFuses for ESP32-H2 chip
2#
3# SPDX-FileCopyrightText: 2022 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. This means GPIO45 can be high or low "
159        "at reset without changing the flash voltage.",
160    )
161    p.add_argument("voltage", help="Voltage selection", choices=["1.8V", "3.3V", "OFF"])
162
163    p = subparsers.add_parser(
164        "burn_custom_mac", help="Burn a 48-bit Custom MAC Address to EFUSE BLOCK3."
165    )
166    p.add_argument(
167        "mac",
168        help="Custom MAC Address to burn given in hexadecimal format with bytes "
169        "separated by colons (e.g. AA:CD:EF:01:02:03).",
170        type=fields.base_fields.CheckArgValue(efuses, "CUSTOM_MAC"),
171    )
172    add_force_write_always(p)
173
174    p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.")
175
176
177def burn_custom_mac(esp, efuses, args):
178    efuses["CUSTOM_MAC"].save(args.mac)
179    if not efuses.burn_all(check_batch_mode=True):
180        return
181    get_custom_mac(esp, efuses, args)
182    print("Successful")
183
184
185def get_custom_mac(esp, efuses, args):
186    print("Custom MAC Address: {}".format(efuses["CUSTOM_MAC"].get()))
187
188
189def set_flash_voltage(esp, efuses, args):
190    raise esptool.FatalError("set_flash_voltage is not supported!")
191
192
193def adc_info(esp, efuses, args):
194    print("")
195    # fmt: off
196    if efuses["BLK_VERSION_MINOR"].get() == 2:
197        print("Temperature Sensor Calibration = {}C".format(efuses["TEMP_CALIB"].get()))
198        print("")
199        print("ADC1:")
200        print("AVE_INITCODE_ATTEN0      = ", efuses["ADC1_AVE_INITCODE_ATTEN0"].get())
201        print("AVE_INITCODE_ATTEN1      = ", efuses["ADC1_AVE_INITCODE_ATTEN1"].get())
202        print("AVE_INITCODE_ATTEN2      = ", efuses["ADC1_AVE_INITCODE_ATTEN2"].get())
203        print("AVE_INITCODE_ATTEN3      = ", efuses["ADC1_AVE_INITCODE_ATTEN3"].get())
204        print("HI_DOUT_ATTEN0           = ", efuses["ADC1_HI_DOUT_ATTEN0"].get())
205        print("HI_DOUT_ATTEN1           = ", efuses["ADC1_HI_DOUT_ATTEN1"].get())
206        print("HI_DOUT_ATTEN2           = ", efuses["ADC1_HI_DOUT_ATTEN2"].get())
207        print("HI_DOUT_ATTEN3           = ", efuses["ADC1_HI_DOUT_ATTEN3"].get())
208        print("CH0_ATTEN0_INITCODE_DIFF = ", efuses["ADC1_CH0_ATTEN0_INITCODE_DIFF"].get())
209        print("CH1_ATTEN0_INITCODE_DIFF = ", efuses["ADC1_CH1_ATTEN0_INITCODE_DIFF"].get())
210        print("CH2_ATTEN0_INITCODE_DIFF = ", efuses["ADC1_CH2_ATTEN0_INITCODE_DIFF"].get())
211        print("CH3_ATTEN0_INITCODE_DIFF = ", efuses["ADC1_CH3_ATTEN0_INITCODE_DIFF"].get())
212        print("CH4_ATTEN0_INITCODE_DIFF = ", efuses["ADC1_CH4_ATTEN0_INITCODE_DIFF"].get())
213    else:
214        print("BLK_VERSION_MINOR = {}".format(efuses["BLK_VERSION_MINOR"].get()))
215    # fmt: on
216
217
218def burn_key(esp, efuses, args, digest=None):
219    if digest is None:
220        datafile_list = args.keyfile[
221            0 : len([name for name in args.keyfile if name is not None]) :
222        ]
223    else:
224        datafile_list = digest[0 : len([name for name in digest if name is not None]) :]
225    efuses.force_write_always = args.force_write_always
226    block_name_list = args.block[
227        0 : len([name for name in args.block if name is not None]) :
228    ]
229    keypurpose_list = args.keypurpose[
230        0 : len([name for name in args.keypurpose if name is not None]) :
231    ]
232
233    util.check_duplicate_name_in_list(block_name_list)
234    if len(block_name_list) != len(datafile_list) or len(block_name_list) != len(
235        keypurpose_list
236    ):
237        raise esptool.FatalError(
238            "The number of blocks (%d), datafile (%d) and keypurpose (%d) "
239            "should be the same."
240            % (len(block_name_list), len(datafile_list), len(keypurpose_list))
241        )
242
243    print("Burn keys to blocks:")
244    for block_name, datafile, keypurpose in zip(
245        block_name_list, datafile_list, keypurpose_list
246    ):
247        efuse = None
248        for block in efuses.blocks:
249            if block_name == block.name or block_name in block.alias:
250                efuse = efuses[block.name]
251        if efuse is None:
252            raise esptool.FatalError("Unknown block name - %s" % (block_name))
253        num_bytes = efuse.bit_len // 8
254
255        block_num = efuses.get_index_block_by_name(block_name)
256        block = efuses.blocks[block_num]
257
258        if digest is None:
259            if keypurpose == "ECDSA_KEY":
260                sk = espsecure.load_ecdsa_signing_key(datafile)
261                data = sk.to_string()
262                if len(data) == 24:
263                    # the private key is 24 bytes long for NIST192p, add 8 bytes of padding
264                    data = b"\x00" * 8 + data
265            else:
266                data = datafile.read()
267        else:
268            data = datafile
269
270        print(" - %s" % (efuse.name), end=" ")
271        revers_msg = None
272        if efuses[block.key_purpose_name].need_reverse(keypurpose):
273            revers_msg = f"\tReversing byte order for {keypurpose} hardware peripheral"
274            data = data[::-1]
275        print(
276            "-> [{}]".format(
277                util.hexify(data, " ")
278                if args.show_sensitive_info
279                else " ".join(["??"] * len(data))
280            )
281        )
282        if revers_msg:
283            print(revers_msg)
284        if len(data) != num_bytes:
285            raise esptool.FatalError(
286                "Incorrect key file size %d. Key file must be %d bytes (%d bits) "
287                "of raw binary key data." % (len(data), num_bytes, num_bytes * 8)
288            )
289
290        if efuses[block.key_purpose_name].need_rd_protect(keypurpose):
291            read_protect = False if args.no_read_protect else True
292        else:
293            read_protect = False
294        write_protect = not args.no_write_protect
295
296        # using efuse instead of a block gives the advantage of checking it as the whole field.
297        efuse.save(data)
298
299        disable_wr_protect_key_purpose = False
300        if efuses[block.key_purpose_name].get() != keypurpose:
301            if efuses[block.key_purpose_name].is_writeable():
302                print(
303                    "\t'%s': '%s' -> '%s'."
304                    % (
305                        block.key_purpose_name,
306                        efuses[block.key_purpose_name].get(),
307                        keypurpose,
308                    )
309                )
310                efuses[block.key_purpose_name].save(keypurpose)
311                disable_wr_protect_key_purpose = True
312            else:
313                raise esptool.FatalError(
314                    "It is not possible to change '%s' to '%s' "
315                    "because write protection bit is set."
316                    % (block.key_purpose_name, keypurpose)
317                )
318        else:
319            print("\t'%s' is already '%s'." % (block.key_purpose_name, keypurpose))
320            if efuses[block.key_purpose_name].is_writeable():
321                disable_wr_protect_key_purpose = True
322
323        if keypurpose == "ECDSA_KEY":
324            if efuses["ECDSA_FORCE_USE_HARDWARE_K"].get() == 0:
325                # For ECDSA key purpose block permanently enable
326                # the hardware TRNG supplied k mode (most secure mode)
327                print("\tECDSA_FORCE_USE_HARDWARE_K: 0 -> 1")
328                efuses["ECDSA_FORCE_USE_HARDWARE_K"].save(1)
329            else:
330                print("\tECDSA_FORCE_USE_HARDWARE_K is already '1'")
331
332        if disable_wr_protect_key_purpose:
333            print("\tDisabling write to '%s'." % block.key_purpose_name)
334            efuses[block.key_purpose_name].disable_write()
335
336        if read_protect:
337            print("\tDisabling read to key block")
338            efuse.disable_read()
339
340        if write_protect:
341            print("\tDisabling write to key block")
342            efuse.disable_write()
343        print("")
344
345    if not write_protect:
346        print("Keys will remain writeable (due to --no-write-protect)")
347    if args.no_read_protect:
348        print("Keys will remain readable (due to --no-read-protect)")
349
350    if not efuses.burn_all(check_batch_mode=True):
351        return
352    print("Successful")
353
354
355def burn_key_digest(esp, efuses, args):
356    digest_list = []
357    datafile_list = args.keyfile[
358        0 : len([name for name in args.keyfile if name is not None]) :
359    ]
360    block_list = args.block[
361        0 : len([block for block in args.block if block is not None]) :
362    ]
363    for block_name, datafile in zip(block_list, datafile_list):
364        efuse = None
365        for block in efuses.blocks:
366            if block_name == block.name or block_name in block.alias:
367                efuse = efuses[block.name]
368        if efuse is None:
369            raise esptool.FatalError("Unknown block name - %s" % (block_name))
370        num_bytes = efuse.bit_len // 8
371        digest = espsecure._digest_sbv2_public_key(datafile)
372        if len(digest) != num_bytes:
373            raise esptool.FatalError(
374                "Incorrect digest size %d. Digest must be %d bytes (%d bits) "
375                "of raw binary key data." % (len(digest), num_bytes, num_bytes * 8)
376            )
377        digest_list.append(digest)
378    burn_key(esp, efuses, args, digest=digest_list)
379
380
381def espefuse(esp, efuses, args, command):
382    parser = argparse.ArgumentParser()
383    subparsers = parser.add_subparsers(dest="operation")
384    add_commands(subparsers, efuses)
385    try:
386        cmd_line_args = parser.parse_args(command.split())
387    except SystemExit:
388        traceback.print_stack()
389        raise esptool.FatalError('"{}" - incorrect command'.format(command))
390    if cmd_line_args.operation == "execute_scripts":
391        configfiles = cmd_line_args.configfiles
392        index = cmd_line_args.index
393    # copy arguments from args to cmd_line_args
394    vars(cmd_line_args).update(vars(args))
395    if cmd_line_args.operation == "execute_scripts":
396        cmd_line_args.configfiles = configfiles
397        cmd_line_args.index = index
398    if cmd_line_args.operation is None:
399        parser.print_help()
400        parser.exit(1)
401    operation_func = globals()[cmd_line_args.operation]
402    # each 'operation' is a module-level function of the same name
403    operation_func(esp, efuses, cmd_line_args)
404
405
406def execute_scripts(esp, efuses, args):
407    efuses.batch_mode_cnt += 1
408    del args.operation
409    scripts = args.scripts
410    del args.scripts
411
412    for file in scripts:
413        with open(file.name, "r") as file:
414            exec(compile(file.read(), file.name, "exec"))
415
416    if args.debug:
417        for block in efuses.blocks:
418            data = block.get_bitstring(from_read=False)
419            block.print_block(data, "regs_for_burn", args.debug)
420
421    efuses.batch_mode_cnt -= 1
422    if not efuses.burn_all(check_batch_mode=True):
423        return
424    print("Successful")
425