diff --git a/components/esp_tee/CMakeLists.txt b/components/esp_tee/CMakeLists.txt index dcba631e4d3..ee837c78fed 100644 --- a/components/esp_tee/CMakeLists.txt +++ b/components/esp_tee/CMakeLists.txt @@ -124,18 +124,26 @@ if(CONFIG_SECURE_ENABLE_TEE AND NOT esp_tee_build) list(APPEND exclude_srv "attestation") 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) execute_process( COMMAND python ${secure_service_yml_parser_py} "--sec_srv" ${secure_service_yml} - "--exclude" ${exclude_srv} + ${exclude_args} WORKING_DIRECTORY ${secure_service_headers_dir} ) execute_process( COMMAND python ${secure_service_yml_parser_py} "--sec_srv" ${secure_service_yml} - "--exclude" ${exclude_srv} "--wrap" + ${exclude_args} "--wrap" OUTPUT_VARIABLE wrap_list WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} OUTPUT_STRIP_TRAILING_WHITESPACE diff --git a/components/esp_tee/scripts/esp_tee_sec_stg_keygen/README.md b/components/esp_tee/scripts/esp_tee_sec_stg_keygen/README.md index 3e949fab7b2..01a3b31a27a 100644 --- a/components/esp_tee/scripts/esp_tee_sec_stg_keygen/README.md +++ b/components/esp_tee/scripts/esp_tee_sec_stg_keygen/README.md @@ -7,17 +7,18 @@ ``` $ 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: - -h, --help show this help message and exit - -k, --key-type {aes256,ecdsa_p256,ecdsa_p384} - key type to be processed - -o, --output OUTPUT output binary file name - -i, --input INPUT input key file (.pem for ecdsa, .bin for aes) + Generate or import a cryptographic key structure for secure storage. + +Options: + -k, --key-type [aes256|ecdsa_p256|ecdsa_p384] + key type to be processed [required] + -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 + -h, --help Show this message and exit. ``` ### ECDSA Keys diff --git a/components/esp_tee/scripts/esp_tee_sec_stg_keygen/esp_tee_sec_stg_keygen.py b/components/esp_tee/scripts/esp_tee_sec_stg_keygen/esp_tee_sec_stg_keygen.py index 49b3836a437..00bc993de90 100644 --- a/components/esp_tee/scripts/esp_tee_sec_stg_keygen/esp_tee_sec_stg_keygen.py +++ b/components/esp_tee/scripts/esp_tee_sec_stg_keygen/esp_tee_sec_stg_keygen.py @@ -2,16 +2,16 @@ # SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 -import argparse import os import struct from enum import Enum from enum import IntFlag -from typing import Any +import rich_click as click from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import ec +from esp_pylib.logger import log # === Constants === SEC_STG_KEY_DATA_SZ = 256 @@ -86,56 +86,63 @@ def generate_key_data(key_type: KeyType, flags: Flags, input_file: str | None) - # === CLI === -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser(description='Generate or import a cryptographic key structure for secure storage') - parser.add_argument( - '-k', - '--key-type', - type=str, - choices=[e.name.lower() for e in KeyType], - required=True, - help='key type to be processed', - ) - parser.add_argument( - '-o', - '--output', - required=True, - help='output binary file name', - ) - parser.add_argument( - '-i', - '--input', - help='input key file (.pem for ecdsa, .bin for aes)', - ) - parser.add_argument( - '--write-once', - action='store_true', - help='make key persistent - cannot be modified or deleted once written', - ) - return parser.parse_args() - - -def main() -> None: - args: Any = parse_args() - - key_type = KeyType[args.key_type.upper()] +@click.command(context_settings=dict(help_option_names=['-h', '--help'])) +@click.option( + '-k', + '--key-type', + 'key_type', + type=click.Choice([e.name.lower() for e in KeyType], case_sensitive=False), + required=True, + help='key type to be processed', +) +@click.option( + '-o', + '--output', + required=True, + help='output binary file name', +) +@click.option( + '-i', + '--input', + 'input_file', + default=None, + help='input key file (.pem for ecdsa, .bin for aes)', +) +@click.option( + '--write-once', + is_flag=True, + default=False, + 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: + """Generate or import a cryptographic key structure for secure storage.""" + selected_type = KeyType[key_type.upper()] flags = Flags.NONE - if args.write_once: + if write_once: flags |= Flags.WRITE_ONCE - print(f'[+] Generating key of type: {key_type.name} (value: {key_type.value})') - if args.input: - print(f'[+] Using user-provided key file: {args.input}') - if args.write_once: - print('[+] WRITE_ONCE flag is set') + log.print( + f'[+] Generating key of type: {selected_type.name} (value: {selected_type.value})', + markup=False, + soft_wrap=True, + ) + 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: - f.write(key_data) - - print(f'[✓] Key written to {args.output}') + log.print(f'[✓] Key written to {output}', markup=False, soft_wrap=True) if __name__ == '__main__': + from esp_pylib.excepthook import install_exception_reporting + + install_exception_reporting() main() diff --git a/components/esp_tee/scripts/secure_service_yml_parser.py b/components/esp_tee/scripts/secure_service_yml_parser.py index 8dc18e8aca3..e9a8b945b0d 100644 --- a/components/esp_tee/scripts/secure_service_yml_parser.py +++ b/components/esp_tee/scripts/secure_service_yml_parser.py @@ -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 -import argparse -from typing import List -from typing import Set -from typing import Tuple +import rich_click as click import yaml +from esp_pylib.cli_options import OptionEatAll SEC_SRV_TABLE_SPLIT_ID = 30 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() for yml_file in yml_files: - with open(yml_file, 'r') as f: + with open(yml_file) as f: data = yaml.safe_load(f) for family in data.get('secure_services', []): 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]) -def generate_num_header(services: List[Tuple[int, str, int]], output_file: str) -> None: - header = '''/** +def generate_num_header(services: list[tuple[int, str, int]], output_file: str) -> None: + header = """/** * 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" { #endif -''' +""" 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'#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 += '''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; -''' +""" footer += '\n#ifdef __cplusplus\n}\n#endif\n' with open(output_file, 'w') as f: f.write(header + body + footer) -def generate_dec_header(services: List[Tuple[int, str, int]], output_file: str) -> None: - header = '''/** +def generate_dec_header(services: list[tuple[int, str, int]], output_file: str) -> None: + header = """/** * 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 extern "C" { #endif -''' +""" body = '\n'.join(f'void _ss_{name}(void);' for _, name, _ in services) footer = '\n#ifdef __cplusplus\n}\n#endif\n' with open(output_file, 'w') as f: f.write(header + body + footer) -def generate_table_split(services: List[Tuple[int, str, int]], output_file_1: str, output_file_2: str) -> None: - header = '''/** +def generate_table_split(services: list[tuple[int, str, int]], output_file_1: str, output_file_2: str) -> None: + header = """/** * THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT! */ #pragma once -''' +""" 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] @@ -95,10 +93,10 @@ def generate_table_split(services: List[Tuple[int, str, int]], output_file_1: st 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] = [] for yml_file in yml_files: - with open(yml_file, 'r') as f: + with open(yml_file) as f: data = yaml.safe_load(f) wrap_list.extend( 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='') -def main() -> None: - parser = argparse.ArgumentParser(description='Generate secure service outputs from YAML table') - parser.add_argument('--wrap', action='store_true', help='Generate linker wrap options') - parser.add_argument('-s', '--sec_srv', nargs='+', required=True, help='Secure service table(s) in YAML') - parser.add_argument('--exclude', nargs='*', default=[], help='List of API families to exclude from the output') +@click.command(context_settings=dict(help_option_names=['-h', '--help'])) +@click.option('--wrap', is_flag=True, default=False, help='Generate linker wrap options') +@click.option( + '-s', + '--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() - excluded_fam = set(args.exclude) - - if args.wrap: - generate_wrap_list(args.sec_srv, excluded_fam) - else: - services = parse_services(args.sec_srv, excluded_fam) - generate_num_header(services, OUTPUT_HEADERS[0]) - generate_dec_header(services, OUTPUT_HEADERS[1]) - generate_table_split(services, OUTPUT_HEADERS[2], OUTPUT_HEADERS[3]) + try: + if wrap: + generate_wrap_list(yml_files, excluded_fam) + else: + services = parse_services(yml_files, excluded_fam) + generate_num_header(services, OUTPUT_HEADERS[0]) + generate_dec_header(services, OUTPUT_HEADERS[1]) + generate_table_split(services, OUTPUT_HEADERS[2], OUTPUT_HEADERS[3]) + except (ValueError, OSError, yaml.YAMLError) as e: + raise click.ClickException(str(e)) if __name__ == '__main__': + from esp_pylib.excepthook import install_exception_reporting + + install_exception_reporting() main()