Merge branch 'refactor/normalize_tee_pycli_script_output' into 'master'

refactor(esp_tee): Normalize the `esp_tee` python tools CLI output

Closes IDF-15329

See merge request espressif/esp-idf!49274
This commit is contained in:
Mahavir Jain
2026-07-03 08:55:22 +05:30
4 changed files with 126 additions and 91 deletions
+10 -2
View File
@@ -124,18 +124,26 @@ if(CONFIG_SECURE_ENABLE_TEE AND NOT esp_tee_build)
list(APPEND exclude_srv "attestation") list(APPEND exclude_srv "attestation")
endif() endif()
# The parser uses OptionEatAll, so each flag consumes all following values and
# the CMake lists can be passed directly. --exclude behaves like nargs='+', so
# omit it entirely when empty (a bare --exclude would error / swallow --wrap).
set(exclude_args)
if(exclude_srv)
set(exclude_args "--exclude" ${exclude_srv})
endif()
idf_build_get_property(secure_service_headers_dir SECURE_SERVICE_HEADERS_DIR) idf_build_get_property(secure_service_headers_dir SECURE_SERVICE_HEADERS_DIR)
execute_process( execute_process(
COMMAND python ${secure_service_yml_parser_py} COMMAND python ${secure_service_yml_parser_py}
"--sec_srv" ${secure_service_yml} "--sec_srv" ${secure_service_yml}
"--exclude" ${exclude_srv} ${exclude_args}
WORKING_DIRECTORY ${secure_service_headers_dir} WORKING_DIRECTORY ${secure_service_headers_dir}
) )
execute_process( execute_process(
COMMAND python ${secure_service_yml_parser_py} COMMAND python ${secure_service_yml_parser_py}
"--sec_srv" ${secure_service_yml} "--sec_srv" ${secure_service_yml}
"--exclude" ${exclude_srv} "--wrap" ${exclude_args} "--wrap"
OUTPUT_VARIABLE wrap_list OUTPUT_VARIABLE wrap_list
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_STRIP_TRAILING_WHITESPACE
@@ -7,17 +7,18 @@
``` ```
$ python esp_tee_sec_stg_keygen.py --help $ python esp_tee_sec_stg_keygen.py --help
usage: esp_tee_sec_stg_keygen.py [-h] -k {aes256,ecdsa_p256,ecdsa_p384} -o OUTPUT [-i INPUT] [--write-once]
Generate or import a cryptographic key structure for secure storage Usage: esp_tee_sec_stg_keygen.py [OPTIONS]
options: Generate or import a cryptographic key structure for secure storage.
-h, --help show this help message and exit
-k, --key-type {aes256,ecdsa_p256,ecdsa_p384} Options:
key type to be processed -k, --key-type [aes256|ecdsa_p256|ecdsa_p384]
-o, --output OUTPUT output binary file name key type to be processed [required]
-i, --input INPUT input key file (.pem for ecdsa, .bin for aes) -o, --output TEXT output binary file name [required]
-i, --input TEXT input key file (.pem for ecdsa, .bin for aes)
--write-once make key persistent - cannot be modified or deleted once written --write-once make key persistent - cannot be modified or deleted once written
-h, --help Show this message and exit.
``` ```
### ECDSA Keys ### ECDSA Keys
@@ -2,16 +2,16 @@
# SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
import argparse
import os import os
import struct import struct
from enum import Enum from enum import Enum
from enum import IntFlag from enum import IntFlag
from typing import Any
import rich_click as click
from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric import ec
from esp_pylib.logger import log
# === Constants === # === Constants ===
SEC_STG_KEY_DATA_SZ = 256 SEC_STG_KEY_DATA_SZ = 256
@@ -86,56 +86,63 @@ def generate_key_data(key_type: KeyType, flags: Flags, input_file: str | None) -
# === CLI === # === CLI ===
def parse_args() -> argparse.Namespace: @click.command(context_settings=dict(help_option_names=['-h', '--help']))
parser = argparse.ArgumentParser(description='Generate or import a cryptographic key structure for secure storage') @click.option(
parser.add_argument( '-k',
'-k', '--key-type',
'--key-type', 'key_type',
type=str, type=click.Choice([e.name.lower() for e in KeyType], case_sensitive=False),
choices=[e.name.lower() for e in KeyType], required=True,
required=True, help='key type to be processed',
help='key type to be processed', )
) @click.option(
parser.add_argument( '-o',
'-o', '--output',
'--output', required=True,
required=True, help='output binary file name',
help='output binary file name', )
) @click.option(
parser.add_argument( '-i',
'-i', '--input',
'--input', 'input_file',
help='input key file (.pem for ecdsa, .bin for aes)', default=None,
) help='input key file (.pem for ecdsa, .bin for aes)',
parser.add_argument( )
'--write-once', @click.option(
action='store_true', '--write-once',
help='make key persistent - cannot be modified or deleted once written', is_flag=True,
) default=False,
return parser.parse_args() help='make key persistent - cannot be modified or deleted once written',
)
def main(key_type: str, output: str, input_file: str | None, write_once: bool) -> None:
def main() -> None: """Generate or import a cryptographic key structure for secure storage."""
args: Any = parse_args() selected_type = KeyType[key_type.upper()]
key_type = KeyType[args.key_type.upper()]
flags = Flags.NONE flags = Flags.NONE
if args.write_once: if write_once:
flags |= Flags.WRITE_ONCE flags |= Flags.WRITE_ONCE
print(f'[+] Generating key of type: {key_type.name} (value: {key_type.value})') log.print(
if args.input: f'[+] Generating key of type: {selected_type.name} (value: {selected_type.value})',
print(f'[+] Using user-provided key file: {args.input}') markup=False,
if args.write_once: soft_wrap=True,
print('[+] WRITE_ONCE flag is set') )
if input_file:
log.print(f'[+] Using user-provided key file: {input_file}', markup=False, soft_wrap=True)
if write_once:
log.print('[+] WRITE_ONCE flag is set', markup=False, soft_wrap=True)
key_data = generate_key_data(key_type, flags, args.input) try:
key_data = generate_key_data(selected_type, flags, input_file)
with open(output, 'wb') as f:
f.write(key_data)
except (ValueError, OSError) as e:
raise click.ClickException(str(e))
with open(args.output, 'wb') as f: log.print(f'[✓] Key written to {output}', markup=False, soft_wrap=True)
f.write(key_data)
print(f'[✓] Key written to {args.output}')
if __name__ == '__main__': if __name__ == '__main__':
from esp_pylib.excepthook import install_exception_reporting
install_exception_reporting()
main() main()
@@ -1,11 +1,9 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
import argparse
from typing import List
from typing import Set
from typing import Tuple
import rich_click as click
import yaml import yaml
from esp_pylib.cli_options import OptionEatAll
SEC_SRV_TABLE_SPLIT_ID = 30 SEC_SRV_TABLE_SPLIT_ID = 30
OUTPUT_HEADERS = [ OUTPUT_HEADERS = [
@@ -16,11 +14,11 @@ OUTPUT_HEADERS = [
] ]
def parse_services(yml_files: List[str], excluded_fam: Set[str]) -> List[Tuple[int, str, int]]: def parse_services(yml_files: list[str], excluded_fam: set[str]) -> list[tuple[int, str, int]]:
services, service_ids = [], set() services, service_ids = [], set()
for yml_file in yml_files: for yml_file in yml_files:
with open(yml_file, 'r') as f: with open(yml_file) as f:
data = yaml.safe_load(f) data = yaml.safe_load(f)
for family in data.get('secure_services', []): for family in data.get('secure_services', []):
family_name = family.get('family', '') family_name = family.get('family', '')
@@ -36,8 +34,8 @@ def parse_services(yml_files: List[str], excluded_fam: Set[str]) -> List[Tuple[i
return sorted(services, key=lambda x: x[0]) return sorted(services, key=lambda x: x[0])
def generate_num_header(services: List[Tuple[int, str, int]], output_file: str) -> None: def generate_num_header(services: list[tuple[int, str, int]], output_file: str) -> None:
header = '''/** header = """/**
* THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT! * THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT!
*/ */
@@ -47,21 +45,21 @@ def generate_num_header(services: List[Tuple[int, str, int]], output_file: str)
extern "C" { extern "C" {
#endif #endif
''' """
body = '\n'.join(f'#define SS_{name.upper()}\t{nr}' for nr, name, _ in services) body = '\n'.join(f'#define SS_{name.upper()}\t{nr}' for nr, name, _ in services)
footer = f'\n#define MAX_SECURE_SERVICES_ID\t{services[-1][0] + 1 if services else 0}\n' footer = f'\n#define MAX_SECURE_SERVICES_ID\t{services[-1][0] + 1 if services else 0}\n'
footer += f'#define SECURE_SERVICES_NUM\t{len(services)}\n\n' footer += f'#define SECURE_SERVICES_NUM\t{len(services)}\n\n'
footer += f'#define SECURE_SERVICES_SPLIT_ID\t{SEC_SRV_TABLE_SPLIT_ID}\n\n' footer += f'#define SECURE_SERVICES_SPLIT_ID\t{SEC_SRV_TABLE_SPLIT_ID}\n\n'
footer += '''typedef void (*secure_service_t)(void); footer += """typedef void (*secure_service_t)(void);
typedef struct { secure_service_t func; int nargs; } secure_service_entry_t; typedef struct { secure_service_t func; int nargs; } secure_service_entry_t;
''' """
footer += '\n#ifdef __cplusplus\n}\n#endif\n' footer += '\n#ifdef __cplusplus\n}\n#endif\n'
with open(output_file, 'w') as f: with open(output_file, 'w') as f:
f.write(header + body + footer) f.write(header + body + footer)
def generate_dec_header(services: List[Tuple[int, str, int]], output_file: str) -> None: def generate_dec_header(services: list[tuple[int, str, int]], output_file: str) -> None:
header = '''/** header = """/**
* THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT! * THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT!
*/ */
@@ -70,20 +68,20 @@ def generate_dec_header(services: List[Tuple[int, str, int]], output_file: str)
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
''' """
body = '\n'.join(f'void _ss_{name}(void);' for _, name, _ in services) body = '\n'.join(f'void _ss_{name}(void);' for _, name, _ in services)
footer = '\n#ifdef __cplusplus\n}\n#endif\n' footer = '\n#ifdef __cplusplus\n}\n#endif\n'
with open(output_file, 'w') as f: with open(output_file, 'w') as f:
f.write(header + body + footer) f.write(header + body + footer)
def generate_table_split(services: List[Tuple[int, str, int]], output_file_1: str, output_file_2: str) -> None: def generate_table_split(services: list[tuple[int, str, int]], output_file_1: str, output_file_2: str) -> None:
header = '''/** header = """/**
* THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT! * THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT!
*/ */
#pragma once #pragma once
''' """
services_1 = [service for service in services if service[0] < SEC_SRV_TABLE_SPLIT_ID] services_1 = [service for service in services if service[0] < SEC_SRV_TABLE_SPLIT_ID]
services_2 = [service for service in services if service[0] >= SEC_SRV_TABLE_SPLIT_ID] services_2 = [service for service in services if service[0] >= SEC_SRV_TABLE_SPLIT_ID]
@@ -95,10 +93,10 @@ def generate_table_split(services: List[Tuple[int, str, int]], output_file_1: st
f2.write(header + body_2) f2.write(header + body_2)
def generate_wrap_list(yml_files: List[str], excluded_fam: Set[str]) -> None: def generate_wrap_list(yml_files: list[str], excluded_fam: set[str]) -> None:
wrap_list: list[str] = [] wrap_list: list[str] = []
for yml_file in yml_files: for yml_file in yml_files:
with open(yml_file, 'r') as f: with open(yml_file) as f:
data = yaml.safe_load(f) data = yaml.safe_load(f)
wrap_list.extend( wrap_list.extend(
f'-Wl,--wrap={entry["function"]}' f'-Wl,--wrap={entry["function"]}'
@@ -109,23 +107,44 @@ def generate_wrap_list(yml_files: List[str], excluded_fam: Set[str]) -> None:
print(' '.join(wrap_list), end='') print(' '.join(wrap_list), end='')
def main() -> None: @click.command(context_settings=dict(help_option_names=['-h', '--help']))
parser = argparse.ArgumentParser(description='Generate secure service outputs from YAML table') @click.option('--wrap', is_flag=True, default=False, help='Generate linker wrap options')
parser.add_argument('--wrap', action='store_true', help='Generate linker wrap options') @click.option(
parser.add_argument('-s', '--sec_srv', nargs='+', required=True, help='Secure service table(s) in YAML') '-s',
parser.add_argument('--exclude', nargs='*', default=[], help='List of API families to exclude from the output') '--sec_srv',
'sec_srv',
multiple=True,
required=True,
type=str,
cls=OptionEatAll,
help='Secure service table(s) in YAML (space- or flag-separated)',
)
@click.option(
'--exclude',
multiple=True,
type=str,
cls=OptionEatAll,
help='API families to exclude from the output (space- or flag-separated)',
)
def main(wrap: bool, sec_srv: tuple[str, ...], exclude: tuple[str, ...]) -> None:
"""Generate secure service outputs from YAML table."""
yml_files = list(sec_srv)
excluded_fam = set(exclude)
args = parser.parse_args() try:
excluded_fam = set(args.exclude) if wrap:
generate_wrap_list(yml_files, excluded_fam)
if args.wrap: else:
generate_wrap_list(args.sec_srv, excluded_fam) services = parse_services(yml_files, excluded_fam)
else: generate_num_header(services, OUTPUT_HEADERS[0])
services = parse_services(args.sec_srv, excluded_fam) generate_dec_header(services, OUTPUT_HEADERS[1])
generate_num_header(services, OUTPUT_HEADERS[0]) generate_table_split(services, OUTPUT_HEADERS[2], OUTPUT_HEADERS[3])
generate_dec_header(services, OUTPUT_HEADERS[1]) except (ValueError, OSError, yaml.YAMLError) as e:
generate_table_split(services, OUTPUT_HEADERS[2], OUTPUT_HEADERS[3]) raise click.ClickException(str(e))
if __name__ == '__main__': if __name__ == '__main__':
from esp_pylib.excepthook import install_exception_reporting
install_exception_reporting()
main() main()