feat(security): update idf.py extensions to support security feature application

This commit is contained in:
Ashish Sharma
2025-03-07 11:20:25 +08:00
parent fbecd65e2a
commit 4c23ba3c1f
2 changed files with 153 additions and 20 deletions

View File

@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import json
import os
@@ -290,7 +290,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
format: str,
md5_disable: str,
flash_offset: str,
fill_flash_size: str) -> None:
fill_flash_size: str,
merge_args: str) -> None:
ensure_build_directory(args, ctx.info_name)
project_desc = _get_project_desc(ctx, args)
merge_bin_args = [PYTHON, '-m', 'esptool']
@@ -320,7 +321,10 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
yellow_print('idf.py merge-bin: --fill-flash-size is only valid for RAW format, option will be ignored.')
else:
merge_bin_args += ['--fill-flash-size', fill_flash_size]
merge_bin_args += ['@flash_args']
if not merge_args:
merge_bin_args += ['@flash_args']
else:
merge_bin_args += ['@{}'.format(merge_args)]
print(f'Merged binary {output} will be created in the build directory...')
RunTool('merge_bin', merge_bin_args, args.build_dir, build_dir=args.build_dir, hints=not args.no_hints)()
@@ -424,6 +428,15 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
generate_signing_key_args += [extra_args['keyfile']]
RunTool('espsecure', generate_signing_key_args, args.project_dir)()
def secure_generate_key_digest(action: str, ctx: click.core.Context, args: PropertyDict, keyfile:str, output:str, **extra_args: str) -> None:
ensure_build_directory(args, ctx.info_name)
generate_key_digest_args = [PYTHON, '-m', 'espsecure', 'digest_sbv2_public_key']
if keyfile:
generate_key_digest_args += ['--keyfile', keyfile]
if output:
generate_key_digest_args += ['--output', output]
RunTool('espsecure', generate_key_digest_args, args.project_dir)()
def secure_sign_data(action: str,
ctx: click.core.Context,
args: PropertyDict,
@@ -468,6 +481,35 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
verify_signature_args += [extra_args['datafile']]
RunTool('espsecure', verify_signature_args, args.build_dir)()
def secure_generate_nvs_partition_key(action: str,
ctx: click.core.Context,
args: PropertyDict,
encryption_scheme: str,
keyfile: str,
hmac_keyfile: str,
**extra_args: str) -> None:
ensure_build_directory(args, ctx.info_name)
generate_nvs_partition_key_args = [PYTHON, '-m', 'esp_idf_nvs_partition_gen', 'generate-key']
if encryption_scheme == 'HMAC':
generate_nvs_partition_key_args += ['--key_protect_hmac']
generate_nvs_partition_key_args += ['--kp_hmac_keygen']
generate_nvs_partition_key_args += ['--kp_hmac_keyfile', hmac_keyfile]
generate_nvs_partition_key_args += ['--keyfile', keyfile]
RunTool('espsecure', generate_nvs_partition_key_args, args.project_dir)()
def secure_encrypt_nvs_partition(action: str, ctx: click.core.Context, args: PropertyDict, keyfile: str, **extra_args: str) -> None:
ensure_build_directory(args, ctx.info_name)
encrypt_nvs_partition_args = [PYTHON, '-m', 'esp_idf_nvs_partition_gen', 'encrypt']
encrypt_nvs_partition_args += ['--inputkey', keyfile]
if extra_args['input_file']:
encrypt_nvs_partition_args += [extra_args['input_file']]
if extra_args['output_file']:
encrypt_nvs_partition_args += [extra_args['output_file']]
if extra_args['partition_size']:
encrypt_nvs_partition_args += [extra_args['partition_size']]
RunTool('espsecure', encrypt_nvs_partition_args, args.project_dir)()
def _parse_efuse_args(ctx: click.core.Context, args: PropertyDict, extra_args: Dict) -> List:
efuse_args = []
if args.port:
@@ -639,6 +681,13 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
'help': ('[ONLY RAW] If set, the final binary file will be padded with FF bytes up to this flash size.'),
'type': click.Choice(['256KB', '512KB', '1MB', '2MB', '4MB', '8MB', '16MB', '32MB', '64MB', '128MB']),
},
{
'names': ['--merge-args'],
'help': (
'Filepath to specify which binaries should be merged with their respective addresses. '
'The file format should be the same as flash_args.'
),
}
],
'dependencies': ['all'], # all = build
},
@@ -781,6 +830,20 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
},
],
},
'secure-generate-key-digest': {
'callback': secure_generate_key_digest,
'help': ('Generate a digest of a puiblic key file for use with secure boot.'),
'options': [
{
'names': ['--keyfile', '-k'],
'help': ('Public key file for digest generation.'),
},
{
'names': ['--output', '-o'],
'help': ('Output file for key digest.'),
},
],
},
'secure-sign-data': {
'callback': secure_sign_data,
'help': ('Sign a data file for use with secure boot. Signing algorithm is deterministic ECDSA w/ SHA-512 (V1) or either RSA-'
@@ -851,6 +914,51 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
},
],
},
'secure-generate-nvs-partition-key': {
'callback': secure_generate_nvs_partition_key,
'help': 'Generate a key for NVS partition encryption.',
'options': [
{
'names': ['--keyfile', '-k'],
'help': 'File to store the generated key.',
},
{
'names': ['--encryption-scheme', '-s'],
'help': 'Encryption scheme to use.',
'type': click.Choice(['HMAC', 'Flash']),
'default': 'HMAC',
},
{
'names': ['--hmac-keyfile', '-l'],
'help': 'File to store the generated HMAC key.',
}
],
},
'secure-encrypt-nvs-partition': {
'callback': secure_encrypt_nvs_partition,
'help': 'Encrypt the NVS partition.',
'options': [
{
'names': ['--keyfile', '-k'],
'help': 'File with NVS partition key.',
}
],
'arguments': [
{
'names': ['input_file'],
'nargs': 1,
},
{
'names': ['output_file'],
'nargs': 1,
},
{
'names': ['partition_size'],
'nargs': 1,
}
]
},
'efuse-burn': {
'callback': efuse_burn,
'help': 'Burn the eFuse with the specified name.',
@@ -878,8 +986,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
'names': ['--force-write-always'],
'is_flag': True,
'help': (
"Write the eFuse even if it looks like it's already been written, or is write protected."
"Note that this option can't disable write protection, or clear any bit which has already been set."
'Write the eFuse even if it looks like it\'s already been written, or is write protected.'
'Note that this option can\'t disable write protection, or clear any bit which has already been set.'
),
},
{
@@ -977,7 +1085,7 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict:
'Baud rate for monitor. '
'If this option is not provided IDF_MONITOR_BAUD and MONITORBAUD '
'environment variables, global baud rate and project_description.json in build directory '
"(generated by CMake from project's sdkconfig) "
'(generated by CMake from project\'s sdkconfig) '
'will be checked for default value.'
),
},

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python
#
# SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import json
import os
@@ -409,6 +409,7 @@ class TestSecureCommands(TestWrapperCommands):
subprocess.run([sys.executable, idf_py_path, 'build'], stdout=subprocess.DEVNULL)
cls.flash_encryption_key = 'test_key.bin'
cls.signing_key = 'test_signing_key.pem'
cls.nvs_partition_key = 'nvs_partition_key.bin'
def secure_generate_flash_encryption_key(self):
generate_key_command = [sys.executable, idf_py_path, 'secure-generate-flash-encryption-key', self.flash_encryption_key]
@@ -449,19 +450,6 @@ class TestSecureCommands(TestWrapperCommands):
self.assertIn('Using 256-bit key', output)
self.assertIn('Done', output)
def secure_generate_signing_key(self):
generate_key_command = [sys.executable,
idf_py_path,
'secure-generate-signing-key',
'--version',
'2',
'--scheme',
'rsa3072',
self.signing_key]
output = self.call_command(generate_key_command)
self.assertIn(f'RSA 3072 private key in PEM format written to {self.signing_key}', output)
self.assertIn('Done', output)
def secure_sign_data(self):
self.secure_generate_signing_key()
sign_command = [sys.executable,
@@ -490,6 +478,43 @@ class TestSecureCommands(TestWrapperCommands):
output = self.call_command(sign_command)
self.assertIn('verification successful', output)
def secure_generate_signing_key(self):
generate_key_command = [sys.executable,
idf_py_path,
'secure-generate-signing-key',
'--version',
'2',
'--scheme',
'rsa3072',
self.signing_key]
output = self.call_command(generate_key_command)
self.assertIn(f'RSA 3072 private key in PEM format written to {self.signing_key}', output)
def test_secure_generate_key_digest(self):
self.secure_generate_signing_key()
digest_command = [sys.executable,
idf_py_path,
'secure-generate-key-digest',
'--keyfile',
f'{self.signing_key}',
'--output',
'key_digest.bin']
output = self.call_command(digest_command)
self.assertIn(f'Writing the public key digest of {self.signing_key} to key_digest.bin', output)
def test_secure_generate_nvs_partition_key(self):
generate_key_command = [sys.executable,
idf_py_path,
'secure-generate-nvs-partition-key',
'--keyfile',
f'{self.nvs_partition_key}',
'--encryption-scheme',
'HMAC',
'--hmac-keyfile',
'nvs_partition_key.bin']
output = self.call_command(generate_key_command)
self.assertIn('Created encryption keys:', output)
class TestMergeBinCommands(TestWrapperCommands):
"""