1# This file includes the operations with eFuses for ESP32-C2 chip
2#
3# SPDX-FileCopyrightText: 2021-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        action="store_true",
46    )
47
48
49def add_commands(subparsers, efuses):
50    add_common_commands(subparsers, efuses)
51    burn_key = subparsers.add_parser(
52        "burn_key", help="Burn the key block with the specified name"
53    )
54    protect_options(burn_key)
55    add_force_write_always(burn_key)
56    add_show_sensitive_info_option(burn_key)
57    burn_key.add_argument(
58        "block",
59        help="Key block to burn",
60        action="append",
61        choices=efuses.BLOCKS_FOR_KEYS,
62    )
63    burn_key.add_argument(
64        "keyfile",
65        help="File containing 128/256 bits of binary key data",
66        action="append",
67        type=argparse.FileType("rb"),
68    )
69    burn_key.add_argument(
70        "keypurpose",
71        help="Purpose to set.",
72        action="append",
73        choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
74    )
75    for _ in range(1):
76        burn_key.add_argument(
77            "block",
78            help="Key block to burn",
79            nargs="?",
80            action="append",
81            metavar="BLOCK",
82            choices=efuses.BLOCKS_FOR_KEYS,
83        )
84        burn_key.add_argument(
85            "keyfile",
86            help="File containing 128/256 bits of binary key data",
87            nargs="?",
88            action="append",
89            metavar="KEYFILE",
90            type=argparse.FileType("rb"),
91        )
92        burn_key.add_argument(
93            "keypurpose",
94            help="Purpose to set.",
95            nargs="?",
96            action="append",
97            metavar="KEYPURPOSE",
98            choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
99        )
100
101    burn_key_digest = subparsers.add_parser(
102        "burn_key_digest",
103        help="Parse an ECDSA public key and burn the digest "
104        "to higher 128-bits of BLOCK_KEY0",
105    )
106    protect_options(burn_key_digest)
107    add_force_write_always(burn_key_digest)
108    add_show_sensitive_info_option(burn_key_digest)
109    burn_key_digest.add_argument(
110        "keyfile", help="Key file to digest (PEM format)", type=argparse.FileType("rb")
111    )
112
113    p = subparsers.add_parser(
114        "set_flash_voltage",
115        help="Permanently set the internal flash voltage regulator "
116        "to either 1.8V, 3.3V or OFF. This means GPIO45 can be high or low "
117        "at reset without changing the flash voltage.",
118    )
119    p.add_argument("voltage", help="Voltage selection", choices=["1.8V", "3.3V", "OFF"])
120
121    p = subparsers.add_parser(
122        "burn_custom_mac", help="Burn a 48-bit Custom MAC Address to EFUSE BLOCK1."
123    )
124    p.add_argument(
125        "mac",
126        help="Custom MAC Address to burn given in hexadecimal format "
127        "with bytes separated by colons (e.g. AA:CD:EF:01:02:03).",
128        type=fields.base_fields.CheckArgValue(efuses, "CUSTOM_MAC"),
129    )
130    add_force_write_always(p)
131
132    p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.")
133
134
135def burn_custom_mac(esp, efuses, args):
136    efuses["CUSTOM_MAC"].save(args.mac)
137    efuses["CUSTOM_MAC_USED"].save(1)
138    if not efuses.burn_all(check_batch_mode=True):
139        return
140    get_custom_mac(esp, efuses, args)
141    print("Successful")
142
143
144def get_custom_mac(esp, efuses, args):
145    print("Custom MAC Address: {}".format(efuses["CUSTOM_MAC"].get()))
146
147
148def set_flash_voltage(esp, efuses, args):
149    raise esptool.FatalError("set_flash_voltage is not supported!")
150
151
152def adc_info(esp, efuses, args):
153    print("")
154    # fmt: off
155    if efuses["BLK_VERSION_MINOR"].get() == 1:
156        print("Temperature Sensor Calibration = {}C".format(efuses["TEMP_CALIB"].get()))
157        print("ADC OCode        = ", efuses["OCODE"].get())
158        print("ADC1:")
159        print("INIT_CODE_ATTEN0 = ", efuses["ADC1_INIT_CODE_ATTEN0"].get())
160        print("INIT_CODE_ATTEN3 = ", efuses["ADC1_INIT_CODE_ATTEN3"].get())
161        print("CAL_VOL_ATTEN0   = ", efuses["ADC1_CAL_VOL_ATTEN0"].get())
162        print("CAL_VOL_ATTEN3   = ", efuses["ADC1_CAL_VOL_ATTEN3"].get())
163    else:
164        print("BLK_VERSION_MINOR = {}".format(efuses["BLK_VERSION_MINOR"].get_meaning()))
165    # fmt: on
166
167
168def burn_key(esp, efuses, args, digest=None):
169    if digest is None:
170        datafile_list = args.keyfile[
171            0 : len([name for name in args.keyfile if name is not None]) :
172        ]
173    else:
174        datafile_list = digest[0 : len([name for name in digest if name is not None]) :]
175    efuses.force_write_always = args.force_write_always
176    block_name_list = args.block[
177        0 : len([name for name in args.block if name is not None]) :
178    ]
179    keypurpose_list = args.keypurpose[
180        0 : len([name for name in args.keypurpose if name is not None]) :
181    ]
182
183    util.check_duplicate_name_in_list(keypurpose_list)
184    if len(block_name_list) != len(datafile_list) or len(block_name_list) != len(
185        keypurpose_list
186    ):
187        raise esptool.FatalError(
188            "The number of blocks (%d), datafile (%d) and "
189            "keypurpose (%d) should be the same."
190            % (len(block_name_list), len(datafile_list), len(keypurpose_list))
191        )
192
193    assert 1 <= len(block_name_list) <= 2, "Unexpected case"
194
195    if len(block_name_list) == 2:
196        incompatible = True if "XTS_AES_128_KEY" in keypurpose_list else False
197        permitted_purposes = [
198            "XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS",
199            "SECURE_BOOT_DIGEST",
200        ]
201        incompatible |= (
202            keypurpose_list[0] in permitted_purposes
203            and keypurpose_list[1] not in permitted_purposes
204        )
205        if incompatible:
206            raise esptool.FatalError(
207                "These keypurposes are incompatible %s" % (keypurpose_list)
208            )
209
210    print("Burn keys to blocks:")
211    for datafile, keypurpose in zip(datafile_list, keypurpose_list):
212        data = datafile if isinstance(datafile, bytes) else datafile.read()
213
214        if keypurpose == "XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS":
215            efuse = efuses["BLOCK_KEY0_LOW_128"]
216        elif keypurpose == "SECURE_BOOT_DIGEST":
217            efuse = efuses["BLOCK_KEY0_HI_128"]
218            if len(data) == 32:
219                print(
220                    "\tProgramming only left-most 128-bits from SHA256 hash of "
221                    "public key to highest 128-bits of BLOCK KEY0"
222                )
223                data = data[:16]
224            elif len(data) != efuse.bit_len // 8:
225                raise esptool.FatalError(
226                    "Wrong length of this file for SECURE_BOOT_DIGEST. "
227                    "Got %d (expected %d or %d)" % (len(data), 32, efuse.bit_len // 8)
228                )
229            assert len(data) == 16, "Only 16 bytes expected"
230        else:
231            efuse = efuses["BLOCK_KEY0"]
232
233        num_bytes = efuse.bit_len // 8
234
235        print(" - %s" % (efuse.name), end=" ")
236        revers_msg = None
237        if keypurpose.startswith("XTS_AES_"):
238            revers_msg = "\tReversing byte order for AES-XTS hardware peripheral"
239            data = data[::-1]
240        print(
241            "-> [{}]".format(
242                util.hexify(data, " ")
243                if args.show_sensitive_info
244                else " ".join(["??"] * len(data))
245            )
246        )
247        if revers_msg:
248            print(revers_msg)
249        if len(data) != num_bytes:
250            raise esptool.FatalError(
251                "Incorrect key file size %d. "
252                "Key file must be %d bytes (%d bits) of raw binary key data."
253                % (len(data), num_bytes, num_bytes * 8)
254            )
255
256        if keypurpose.startswith("XTS_AES_"):
257            read_protect = False if args.no_read_protect else True
258        else:
259            read_protect = False
260        write_protect = not args.no_write_protect
261
262        # using efuse instead of a block gives the advantage
263        # of checking it as the whole field.
264        efuse.save(data)
265
266        if keypurpose == "XTS_AES_128_KEY":
267            if efuses["XTS_KEY_LENGTH_256"].get():
268                print("\t'XTS_KEY_LENGTH_256' is already '1'")
269            else:
270                print("\tXTS_KEY_LENGTH_256 -> 1")
271                efuses["XTS_KEY_LENGTH_256"].save(1)
272
273        if read_protect:
274            print("\tDisabling read to key block")
275            efuse.disable_read()
276
277        if write_protect:
278            print("\tDisabling write to key block")
279            efuse.disable_write()
280        print("")
281
282    if not write_protect:
283        print("Keys will remain writeable (due to --no-write-protect)")
284    if args.no_read_protect:
285        print("Keys will remain readable (due to --no-read-protect)")
286
287    if not efuses.burn_all(check_batch_mode=True):
288        return
289    print("Successful")
290
291
292def burn_key_digest(esp, efuses, args):
293    datafile = args.keyfile
294    args.keypurpose = ["SECURE_BOOT_DIGEST"]
295    args.block = ["BLOCK_KEY0"]
296    digest = espsecure._digest_sbv2_public_key(datafile)
297    digest = digest[:16]
298    num_bytes = efuses["BLOCK_KEY0_HI_128"].bit_len // 8
299    if len(digest) != num_bytes:
300        raise esptool.FatalError(
301            "Incorrect digest size %d. "
302            "Digest must be %d bytes (%d bits) of raw binary key data."
303            % (len(digest), num_bytes, num_bytes * 8)
304        )
305    burn_key(esp, efuses, args, digest=[digest])
306
307
308def espefuse(esp, efuses, args, command):
309    parser = argparse.ArgumentParser()
310    subparsers = parser.add_subparsers(dest="operation")
311    add_commands(subparsers, efuses)
312    try:
313        cmd_line_args = parser.parse_args(command.split())
314    except SystemExit:
315        traceback.print_stack()
316        raise esptool.FatalError('"{}" - incorrect command'.format(command))
317    if cmd_line_args.operation == "execute_scripts":
318        configfiles = cmd_line_args.configfiles
319        index = cmd_line_args.index
320    # copy arguments from args to cmd_line_args
321    vars(cmd_line_args).update(vars(args))
322    if cmd_line_args.operation == "execute_scripts":
323        cmd_line_args.configfiles = configfiles
324        cmd_line_args.index = index
325    if cmd_line_args.operation is None:
326        parser.print_help()
327        parser.exit(1)
328    operation_func = globals()[cmd_line_args.operation]
329    # each 'operation' is a module-level function of the same name
330    operation_func(esp, efuses, cmd_line_args)
331
332
333def execute_scripts(esp, efuses, args):
334    efuses.batch_mode_cnt += 1
335    del args.operation
336    scripts = args.scripts
337    del args.scripts
338
339    for file in scripts:
340        with open(file.name, "r") as file:
341            exec(compile(file.read(), file.name, "exec"))
342
343    if args.debug:
344        for block in efuses.blocks:
345            data = block.get_bitstring(from_read=False)
346            block.print_block(data, "regs_for_burn", args.debug)
347
348    efuses.batch_mode_cnt -= 1
349    if not efuses.burn_all(check_batch_mode=True):
350        return
351    print("Successful")
352