Skip to content

Commit cc61acf

Browse files
feat(espefuse): Support burning ECDSA_384 keys
1 parent dc220df commit cc61acf

File tree

17 files changed

+293
-450
lines changed

17 files changed

+293
-450
lines changed

docs/en/espefuse/burn-key-cmd.rst

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,14 @@ Optional arguments:
6363

6464
- USER.
6565
- RESERVED.
66-
:esp32c5 or esp32c61 or esp32p4 or esp32s2 or esp32s3: - XTS_AES_256_KEY_1. The first 256 bits of 512bit flash encryption key.
67-
:esp32c5 or esp32c61 or esp32p4 or esp32s2 or esp32s3: - XTS_AES_256_KEY_2. The second 256 bits of 512bit flash encryption key.
68-
::esp32c5 or esp32c61 or esp32h2 or esp32h21 or esp32h4 or esp32p4: - ECDSA_KEY. It can be ECDSA private keys based on NIST192p or NIST256p curve. The private key is extracted from the given file and written into a eFuse block with write and read protection enabled. This private key shall be used by ECDSA accelerator for the signing purpose.
66+
:esp32c5 or esp32p4 or esp32s2 or esp32s3: - XTS_AES_256_KEY_1. The first 256 bits of 512bit flash encryption key.
67+
:esp32c5 or esp32p4 or esp32s2 or esp32s3: - XTS_AES_256_KEY_2. The second 256 bits of 512bit flash encryption key.
68+
:esp32c5 or esp32c61 or esp32h2 or esp32h21 or esp32h4 or esp32p4: - ECDSA_KEY. It can be ECDSA private keys based on NIST192p or NIST256p curve. The private key is extracted from the given file and written into a eFuse block with write and read protection enabled. This private key shall be used by ECDSA accelerator for the signing purpose.
69+
:esp32c5: - ECDSA_KEY_P192. ECDSA private keys based on NIST192p curve.
70+
:esp32c5: - ECDSA_KEY_P256. ECDSA private keys based on NIST256p curve.
71+
:esp32c5: - ECDSA_KEY_P384. ECDSA private keys based on NIST384p curve. This allows you to write a whole 48-byte key into two blocks with ``ECDSA_KEY_P384_H`` and ``ECDSA_KEY_P384_L`` purposes.
72+
:esp32c5: - ECDSA_KEY_P384_H. Upper 32 bytes of the 48-byte ECDSA_P384 key (last 16 bytes of key + 16 padding bytes).
73+
:esp32c5: - ECDSA_KEY_P384_L. Lower 32 bytes of the 48-byte ECDSA_P384 key.
6974
- XTS_AES_128_KEY. 256 bit flash encryption key.
7075
- HMAC_DOWN_ALL.
7176
- HMAC_DOWN_JTAG.
@@ -74,17 +79,21 @@ Optional arguments:
7479
- SECURE_BOOT_DIGEST0. 1 secure boot key.
7580
- SECURE_BOOT_DIGEST1. 2 secure boot key.
7681
- SECURE_BOOT_DIGEST2. 3 secure boot key.
77-
:esp32c5 or esp32c61 or esp32p4 or esp32s2 or esp32s3: - XTS_AES_256_KEY. This is a virtual key purpose for flash encryption key. This allows you to write a whole 512-bit key into two blocks with ``XTS_AES_256_KEY_1`` and ``XTS_AES_256_KEY_2`` purposes without splitting the key file.
82+
:esp32c5 or esp32p4 or esp32s2 or esp32s3: - XTS_AES_256_KEY. This is a virtual key purpose for flash encryption key. This allows you to write a whole 512-bit key into two blocks with ``XTS_AES_256_KEY_1`` and ``XTS_AES_256_KEY_2`` purposes without splitting the key file.
7883
:esp32c5: - XTS_AES_256_PSRAM_KEY. This is a virtual key purpose for psram encryption key. This allows you to write a whole 512-bit key into two blocks with ``XTS_AES_256_PSRAM_KEY_1`` and ``XTS_AES_256_PSRAM_KEY_2`` purposes without splitting the key file.
7984
:esp32c5: - XTS_AES_256_PSRAM_KEY_1. The first 256 bits of 512bit psram encryption key.
8085
:esp32c5: - XTS_AES_256_PSRAM_KEY_2. The second 256 bits of 512bit psram encryption key.
8186
:esp32c5 or esp32h4 or esp32p4: - KM_INIT_KEY. This is a key that is used for the generation of AES/ECDSA keys by the key manager.
8287

83-
.. only:: esp32h2
88+
.. only:: esp32c5 or esp32c61 or esp32h2 or esp32h21 or esp32h4 or esp32p4
8489

85-
{IDF_TARGET_NAME} has the ECDSA accelerator for signature purposes and supports private keys based on the NIST192p or NIST256p curve. These two commands below can be used to generate such keys (``PEM`` file). The ``burn_key`` command with the ``ECDSA_KEY`` purpose takes the ``PEM`` file and writes the private key into a eFuse block. The key is written to the block in reverse byte order.
90+
{IDF_TARGET_NAME} has the ECDSA accelerator for signature purposes and supports private keys based on the NIST192p or NIST256p curve (some chips support NIST384p). These two commands below can be used to generate such keys (``PEM`` file). The ``burn-key`` command with the ``ECDSA_KEY`` purpose takes the ``PEM`` file and writes the private key into a eFuse block. The key is written to the block in reverse byte order.
8691

87-
For NIST192p, the private key is 192 bits long, so 8 padding bytes ("0x00") are added.
92+
.. list::
93+
94+
- For NIST192p, the private key is 192 bits long, so 8 padding bytes ("0x00") are added.
95+
- For NIST256p, the private key is 256 bits long.
96+
- For NIST384p, the private key is 384 bits long, so 16 padding bytes ("0x00") are added.
8897

8998
.. code-block:: none
9099
@@ -347,3 +356,44 @@ Usage
347356
BURN BLOCK3 - OK (write block == read block)
348357
BURN BLOCK0 - OK (write block == read block)
349358
Reading updated efuses...
359+
360+
.. only:: esp32c5
361+
362+
.. code-block:: none
363+
364+
> espefuse -c esp32c2 BLOCK_KEY0 secure_images/ecdsa384_secure_boot_signing_key.pem ECDSA_KEY_P384 --no-read-protect --show-sensitive-info
365+
366+
=== Run "burn-key" command ===
367+
Burn keys to blocks:
368+
- BLOCK_KEY0 -> [0e d2 8e c6 86 f0 f6 af 50 51 c3 5c 41 2b c7 48 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00]
369+
Reversing byte order for ECDSA_KEY_P384_H hardware peripheral...
370+
'KEY_PURPOSE_0': 'USER' -> 'ECDSA_KEY_P384_H'.
371+
Disabling write to 'KEY_PURPOSE_0'...
372+
Disabling write to key block...
373+
374+
- BLOCK_KEY1 -> [65 ca a4 5b 5f 67 5c fe 34 89 f3 4a 57 d1 5a 41 d6 1c 7d ea 7a 3f cd 34 79 f2 94 c2 ad cb 94 7d]
375+
Reversing byte order for ECDSA_KEY_P384_L hardware peripheral...
376+
'KEY_PURPOSE_1': 'USER' -> 'ECDSA_KEY_P384_L'.
377+
Disabling write to 'KEY_PURPOSE_1'...
378+
Disabling write to key block...
379+
380+
Keys will remain readable (due to --no-read-protect).
381+
382+
Check all blocks for burn...
383+
idx, BLOCK_NAME, Conclusion
384+
[00] BLOCK0 is empty, will burn the new value
385+
[04] BLOCK_KEY0 is empty, will burn the new value
386+
[05] BLOCK_KEY1 is empty, will burn the new value
387+
.
388+
This is an irreversible operation!
389+
Type 'BURN' (all capitals) to continue.
390+
BURN
391+
BURN BLOCK5 - OK (write block == read block)
392+
BURN BLOCK4 - OK (write block == read block)
393+
BURN BLOCK0 - OK (write block == read block)
394+
Reading updated eFuses...
395+
Successful.
396+
397+
.. note::
398+
399+
The flags ``--no-read-protect`` and ``--show-sensitive-info`` in this command are used for demonstration purposes only, to show the key byte order. The ECDSA_KEY keys is always written in reverse byte order. The 48 bytes of the key are extracted from the provided PEM file, and 16 padding bytes are added to form a total of 64 bytes for two eFuse blocks. Due to the required reverse byte order, the last 16 bytes of the key plus 16 padding bytes are written to BLOCK_KEY0 with the key purpose ``ECDSA_KEY_P384_H``, and the remaining 32 bytes are written to the next available eFuse block (here, BLOCK_KEY1) with the key purpose ``ECDSA_KEY_P384_L``.

espefuse/efuse/base_operations.py

Lines changed: 96 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from bitstring import BitStream
1414

15+
import espsecure
1516
import esptool
1617

1718
from . import base_fields
@@ -839,12 +840,95 @@ def _get_next_key_block(efuses, current_key_block, block_name_list):
839840
return None
840841

841842

842-
def split_512_bit_key(
843+
def adjust_key_data_for_blocks(efuses, block_names, datafiles, keypurposes):
844+
"""Split a key that takes more than one efuse block into two blocks.
845+
It handles all key purposes that require splitting into two blocks.
846+
847+
This method checks if key purposes require splitting into two blocks,
848+
such as "XTS_AES_256_KEY", "XTS_AES_256_PSRAM_KEY", and "ECDSA_KEY_P384".
849+
850+
Args:
851+
block_names: List of block names.
852+
datafiles: List of BinaryIO objects containing key data.
853+
keypurposes: List of key purposes.
854+
855+
Returns:
856+
A tuple containing updated block names, datafiles, and keypurposes.
857+
"""
858+
keypurposes = list(keypurposes)
859+
datafiles = list(datafiles)
860+
block_names = list(block_names)
861+
862+
if "XTS_AES_256_KEY" in keypurposes:
863+
# XTS_AES_256_KEY is not an actual HW key purpose, needs to be split into
864+
# XTS_AES_256_KEY_1 and XTS_AES_256_KEY_2
865+
block_names, datafiles, keypurposes = _split_multiblock_key(
866+
efuses,
867+
block_names,
868+
datafiles, # type: ignore
869+
keypurposes,
870+
"XTS_AES_256_KEY",
871+
)
872+
873+
if "XTS_AES_256_PSRAM_KEY" in keypurposes:
874+
# XTS_AES_256_PSRAM_KEY -> XTS_AES_256_PSRAM_KEY_1 and ..._KEY_2
875+
block_names, datafiles, keypurposes = _split_multiblock_key(
876+
efuses,
877+
block_names,
878+
datafiles, # type: ignore
879+
keypurposes,
880+
"XTS_AES_256_PSRAM_KEY",
881+
)
882+
883+
# ECDSA keys can be present in a command multiple times
884+
i = 0
885+
while i < len(keypurposes):
886+
if "ECDSA_KEY" in keypurposes[i]:
887+
if keypurposes[i] not in ["ECDSA_KEY_P384_L", "ECDSA_KEY_P384_H"]:
888+
sk = espsecure.load_ecdsa_signing_key(datafiles[i]) # type: ignore
889+
data = sk.to_string()
890+
if "ECDSA_KEY_P384" == keypurposes[i]:
891+
assert (
892+
len(data) == 48
893+
), "NIST384p private key should be 48 bytes long"
894+
datafiles[i] = io.BytesIO(b"\x00" * 16 + data)
895+
# ECDSA_KEY_P384 -> ECDSA_KEY_P384_L and ECDSA_KEY_P384_H
896+
block_names, datafiles, keypurposes = _split_multiblock_key(
897+
efuses,
898+
block_names,
899+
datafiles, # type: ignore
900+
keypurposes,
901+
"ECDSA_KEY_P384",
902+
)
903+
else:
904+
# the private key is 24 bytes long for NIST192p,
905+
# and 8 bytes of padding
906+
datafiles[i] = (
907+
io.BytesIO(b"\x00" * 8 + data)
908+
if len(data) == 24
909+
else io.BytesIO(data)
910+
)
911+
912+
i += 1
913+
914+
# Check that all block names are unique
915+
util.check_duplicate_name_in_list(block_names)
916+
917+
# Check that the number of blocks, datafiles, and keypurposes is equal
918+
if len(block_names) != len(datafiles) or len(block_names) != len(keypurposes):
919+
raise esptool.FatalError(
920+
f"The number of blocks ({len(block_names)}), "
921+
f"datafile ({len(datafiles)}) and keypurpose ({len(keypurposes)}) "
922+
"should be the same."
923+
)
924+
925+
return block_names, datafiles, keypurposes
926+
927+
928+
def _split_multiblock_key(
843929
efuses, block_names, datafiles, keypurposes, base_keypurpose="XTS_AES_256_KEY"
844930
):
845931
"""Helper method to split 512-bit key into two 256-bit keys"""
846-
if base_keypurpose not in keypurposes:
847-
return block_names, datafiles, keypurposes
848932

849933
keypurpose_list = list(keypurposes)
850934
datafile_list = list(datafiles)
@@ -867,16 +951,15 @@ def split_512_bit_key(
867951
if not key_block_2:
868952
raise esptool.FatalError(f"{base_keypurpose} requires two free keyblocks")
869953

870-
keypurpose_list.append(f"{base_keypurpose}_1")
871-
datafile_list.append(io.BytesIO(data[:32]))
872-
block_name_list.append(block_name)
873-
874-
keypurpose_list.append(f"{base_keypurpose}_2")
875-
datafile_list.append(io.BytesIO(data[32:]))
876-
block_name_list.append(key_block_2.name)
954+
postfix = (
955+
["_1", "_2"] if base_keypurpose.startswith("XTS_AES_256") else ["_H", "_L"]
956+
)
957+
keypurpose_list[i] = f"{base_keypurpose}{postfix[0]}"
958+
datafile_list[i] = io.BytesIO(data[:32])
959+
block_name_list[i] = block_name
877960

878-
keypurpose_list.pop(i)
879-
datafile_list.pop(i)
880-
block_name_list.pop(i)
961+
keypurpose_list.insert(i + 1, f"{base_keypurpose}{postfix[1]}")
962+
datafile_list.insert(i + 1, io.BytesIO(data[32:]))
963+
block_name_list.insert(i + 1, key_block_2.name)
881964

882965
return block_name_list, datafile_list, keypurpose_list

espefuse/efuse/esp32c3/operations.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
read_protect_efuse,
2727
summary,
2828
write_protect_efuse,
29+
adjust_key_data_for_blocks,
2930
)
3031

3132

@@ -224,15 +225,12 @@ def burn_key(esp, efuses, args, digest=None):
224225
0 : len([name for name in args.keypurpose if name is not None]) :
225226
]
226227

227-
util.check_duplicate_name_in_list(block_name_list)
228-
if len(block_name_list) != len(datafile_list) or len(block_name_list) != len(
229-
keypurpose_list
230-
):
231-
raise esptool.FatalError(
232-
"The number of blocks (%d), datafile (%d) and keypurpose (%d) "
233-
"should be the same."
234-
% (len(block_name_list), len(datafile_list), len(keypurpose_list))
235-
)
228+
block_name_list, datafile_list, keypurpose_list = adjust_key_data_for_blocks(
229+
efuses,
230+
block_name_list,
231+
datafile_list,
232+
keypurpose_list,
233+
)
236234

237235
print("Burn keys to blocks:")
238236
for block_name, datafile, keypurpose in zip(

espefuse/efuse/esp32c5/fields.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,8 +405,8 @@ def print_field(e, new_value):
405405
class EfuseKeyPurposeField(EfuseField):
406406
KEY_PURPOSES = [
407407
("USER", 0, None, None, "no_need_rd_protect"), # User purposes (software-only use)
408-
("ECDSA_KEY", 1, None, "Reverse", "need_rd_protect"), # ECDSA key P256
409408
("ECDSA_KEY_P256", 1, None, "Reverse", "need_rd_protect"), # ECDSA key P256
409+
("ECDSA_KEY", 1, None, "Reverse", "need_rd_protect"), # ECDSA key P256
410410
("RESERVED", 1, None, None, "no_need_rd_protect"), # Reserved
411411
("XTS_AES_256_KEY_1", 2, None, "Reverse", "need_rd_protect"), # XTS_AES_256_KEY_1 (flash/PSRAM encryption)
412412
("XTS_AES_256_KEY_2", 3, None, "Reverse", "need_rd_protect"), # XTS_AES_256_KEY_2 (flash/PSRAM encryption)
@@ -427,6 +427,7 @@ class EfuseKeyPurposeField(EfuseField):
427427
("ECDSA_KEY_P192", 16, None, "Reverse", "need_rd_protect"), # ECDSA key P192
428428
("ECDSA_KEY_P384_L", 17, None, "Reverse", "need_rd_protect"), # ECDSA key P384 low
429429
("ECDSA_KEY_P384_H", 18, None, "Reverse", "need_rd_protect"), # ECDSA key P384 high
430+
("ECDSA_KEY_P384", -3, "VIRTUAL", None, "need_rd_protect"), # Virtual purpose splits to ECDSA_KEY_P384_L and ECDSA_KEY_P384_H
430431
]
431432
# fmt: on
432433
KEY_PURPOSES_NAME = [name[0] for name in KEY_PURPOSES]

espefuse/efuse/esp32c5/operations.py

Lines changed: 8 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
read_protect_efuse,
2727
summary,
2828
write_protect_efuse,
29-
split_512_bit_key,
29+
adjust_key_data_for_blocks,
3030
)
3131

3232

@@ -215,36 +215,12 @@ def burn_key(esp, efuses, args, digest=None):
215215
0 : len([name for name in args.keypurpose if name is not None]) :
216216
]
217217

218-
if "XTS_AES_256_KEY" in keypurpose_list:
219-
# XTS_AES_256_KEY is not an actual HW key purpose, needs to be split into
220-
# XTS_AES_256_KEY_1 and XTS_AES_256_KEY_2
221-
block_name_list, datafile_list, keypurpose_list = split_512_bit_key(
222-
efuses,
223-
block_name_list,
224-
datafile_list,
225-
keypurpose_list,
226-
"XTS_AES_256_KEY",
227-
)
228-
229-
if "XTS_AES_256_PSRAM_KEY" in keypurpose_list:
230-
# XTS_AES_256_PSRAM_KEY -> XTS_AES_256_PSRAM_KEY_1 and XTS_AES_256_PSRAM_KEY_2
231-
block_name_list, datafile_list, keypurpose_list = split_512_bit_key(
232-
efuses,
233-
block_name_list,
234-
datafile_list,
235-
keypurpose_list,
236-
"XTS_AES_256_PSRAM_KEY",
237-
)
238-
239-
util.check_duplicate_name_in_list(block_name_list)
240-
if len(block_name_list) != len(datafile_list) or len(block_name_list) != len(
241-
keypurpose_list
242-
):
243-
raise esptool.FatalError(
244-
"The number of blocks (%d), datafile (%d) and keypurpose (%d) "
245-
"should be the same."
246-
% (len(block_name_list), len(datafile_list), len(keypurpose_list))
247-
)
218+
block_name_list, datafile_list, keypurpose_list = adjust_key_data_for_blocks(
219+
efuses,
220+
block_name_list,
221+
datafile_list,
222+
keypurpose_list,
223+
)
248224

249225
print("Burn keys to blocks:")
250226
for block_name, datafile, keypurpose in zip(
@@ -262,14 +238,7 @@ def burn_key(esp, efuses, args, digest=None):
262238
block = efuses.blocks[block_num]
263239

264240
if digest is None:
265-
if keypurpose.startswith("ECDSA_KEY"):
266-
sk = espsecure.load_ecdsa_signing_key(datafile)
267-
data = sk.to_string()
268-
if len(data) == 24:
269-
# the private key is 24 bytes long for NIST192p, and 8 bytes of padding
270-
data = b"\x00" * 8 + data
271-
else:
272-
data = datafile.read()
241+
data = datafile.read()
273242
else:
274243
data = datafile
275244

espefuse/efuse/esp32c6/operations.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
read_protect_efuse,
2727
summary,
2828
write_protect_efuse,
29+
adjust_key_data_for_blocks,
2930
)
3031

3132

@@ -231,15 +232,12 @@ def burn_key(esp, efuses, args, digest=None):
231232
0 : len([name for name in args.keypurpose if name is not None]) :
232233
]
233234

234-
util.check_duplicate_name_in_list(block_name_list)
235-
if len(block_name_list) != len(datafile_list) or len(block_name_list) != len(
236-
keypurpose_list
237-
):
238-
raise esptool.FatalError(
239-
"The number of blocks (%d), datafile (%d) and keypurpose (%d) "
240-
"should be the same."
241-
% (len(block_name_list), len(datafile_list), len(keypurpose_list))
242-
)
235+
block_name_list, datafile_list, keypurpose_list = adjust_key_data_for_blocks(
236+
efuses,
237+
block_name_list,
238+
datafile_list,
239+
keypurpose_list,
240+
)
243241

244242
print("Burn keys to blocks:")
245243
for block_name, datafile, keypurpose in zip(

espefuse/efuse/esp32c61/fields.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -406,8 +406,6 @@ class EfuseKeyPurposeField(EfuseField):
406406
KEY_PURPOSES = [
407407
("USER", 0, None, None, "no_need_rd_protect"), # User purposes (software-only use)
408408
("ECDSA_KEY", 1, None, "Reverse", "need_rd_protect"), # ECDSA key
409-
("XTS_AES_256_KEY_1", 2, None, "Reverse", "need_rd_protect"), # XTS_AES_256_KEY_1 (flash/PSRAM encryption)
410-
("XTS_AES_256_KEY_2", 3, None, "Reverse", "need_rd_protect"), # XTS_AES_256_KEY_2 (flash/PSRAM encryption)
411409
("XTS_AES_128_KEY", 4, None, "Reverse", "need_rd_protect"), # XTS_AES_128_KEY (flash/PSRAM encryption)
412410
("HMAC_DOWN_ALL", 5, None, None, "need_rd_protect"), # HMAC Downstream mode
413411
("HMAC_DOWN_JTAG", 6, None, None, "need_rd_protect"), # JTAG soft enable key (uses HMAC Downstream mode)
@@ -416,7 +414,6 @@ class EfuseKeyPurposeField(EfuseField):
416414
("SECURE_BOOT_DIGEST0", 9, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST0 (Secure Boot key digest)
417415
("SECURE_BOOT_DIGEST1", 10, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST1 (Secure Boot key digest)
418416
("SECURE_BOOT_DIGEST2", 11, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST2 (Secure Boot key digest)
419-
("XTS_AES_256_KEY", -1, "VIRTUAL", None, "no_need_rd_protect"), # Virtual purpose splits to XTS_AES_256_KEY_1 and XTS_AES_256_KEY_2
420417
]
421418
# fmt: on
422419
KEY_PURPOSES_NAME = [name[0] for name in KEY_PURPOSES]

0 commit comments

Comments
 (0)