diff --git a/components/bootloader/project_include.cmake b/components/bootloader/project_include.cmake index 121f6514f94..5a9448b7f28 100644 --- a/components/bootloader/project_include.cmake +++ b/components/bootloader/project_include.cmake @@ -76,6 +76,38 @@ if(CONFIG_SECURE_SIGNED_APPS) endif() else() add_custom_target(gen_secure_boot_signing_key) + + # Re-run CMake if the signing key file changes, so the key type check below is re-evaluated + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${secure_boot_signing_key}") + + # Check that the signing key type matches the selected app signing scheme, + # as the signing step derives the signature type from the key itself. + if(CONFIG_SECURE_SIGNED_APPS_RSA_SCHEME) + set(key_check_args --scheme rsa) + elseif(CONFIG_SECURE_SIGNED_APPS_ECDSA_SCHEME) + set(key_check_args --scheme ecdsa_v1) + elseif(CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME) + if(CONFIG_SECURE_BOOT_ECDSA_KEY_LEN_256_BITS) + set(key_check_args --scheme ecdsa_v2 --ecdsa-key-size 256) + elseif(CONFIG_SECURE_BOOT_ECDSA_KEY_LEN_384_BITS) + set(key_check_args --scheme ecdsa_v2 --ecdsa-key-size 384) + else() + set(key_check_args --scheme ecdsa_v2) + endif() + endif() + + if(key_check_args) + idf_build_get_property(python PYTHON) + execute_process( + COMMAND ${python} "${CMAKE_CURRENT_LIST_DIR}/scripts/check_secure_boot_signing_key.py" + ${key_check_args} "${secure_boot_signing_key}" + RESULT_VARIABLE key_check_result + ERROR_VARIABLE key_check_error + OUTPUT_QUIET) + if(NOT key_check_result EQUAL 0) + message(FATAL_ERROR "CONFIG_SECURE_BOOT_SIGNING_KEY check failed: ${key_check_error}") + endif() + endif() endif() set(SECURE_BOOT_SIGNING_KEY ${secure_boot_signing_key}) # needed by some other components diff --git a/components/bootloader/scripts/check_secure_boot_signing_key.py b/components/bootloader/scripts/check_secure_boot_signing_key.py new file mode 100644 index 00000000000..002c7370318 --- /dev/null +++ b/components/bootloader/scripts/check_secure_boot_signing_key.py @@ -0,0 +1,92 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# +# SPDX-License-Identifier: Apache-2.0 +# +# Check that the Secure Boot signing key matches the selected app signing +# scheme. The signing step (espsecure sign-data) derives the signature type +# from the key file itself, so a mismatching key would produce an image that +# only fails signature verification at boot. +# +# Invoked at configure time from components/bootloader/project_include.cmake. +# Exits non-zero with an explanatory message on a mismatch. +import argparse + +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.serialization import load_pem_private_key + +ECDSA_KEY_SIZE_TO_CURVE = { + '256': 'secp256r1', + '384': 'secp384r1', +} + + +def key_description(key: object) -> str: + if isinstance(key, rsa.RSAPrivateKey): + return f'an RSA {key.key_size}-bit key' + if isinstance(key, ec.EllipticCurvePrivateKey): + return f'an ECDSA key (curve {key.curve.name})' + return f'a {type(key).__name__} key' + + +def main() -> None: + parser = argparse.ArgumentParser( + description='Check that the secure boot signing key matches the selected app signing scheme' + ) + parser.add_argument( + '--scheme', + choices=['rsa', 'ecdsa_v1', 'ecdsa_v2'], + required=True, + help='App signing scheme selected in sdkconfig', + ) + parser.add_argument( + '--ecdsa-key-size', + choices=sorted(ECDSA_KEY_SIZE_TO_CURVE), + default=None, + help='ECDSA key size selected in sdkconfig (ECDSA V2 scheme only)', + ) + parser.add_argument('key_file', help='Path to the secure boot signing key') + args = parser.parse_args() + + try: + with open(args.key_file, 'rb') as f: + key = load_pem_private_key(f.read(), password=None) + except Exception: + raise SystemExit( + f'{args.key_file} could not be parsed as a PEM private key. ' + 'Secure boot signing keys must be unencrypted PEM private keys.' + ) + + if args.scheme == 'rsa': + if not isinstance(key, rsa.RSAPrivateKey): + raise SystemExit( + f'{args.key_file} is {key_description(key)}, but the selected app signing scheme ' + 'requires an RSA key. Update the App Signing Scheme choice or point ' + 'CONFIG_SECURE_BOOT_SIGNING_KEY to a matching key.' + ) + return + + if args.scheme in ('ecdsa_v1', 'ecdsa_v2'): + if not isinstance(key, ec.EllipticCurvePrivateKey): + raise SystemExit( + f'{args.key_file} is {key_description(key)}, but the selected app signing scheme ' + 'requires an ECDSA key. Update the App Signing Scheme choice or point ' + 'CONFIG_SECURE_BOOT_SIGNING_KEY to a matching key.' + ) + + if args.scheme == 'ecdsa_v1': + expected_curve = ECDSA_KEY_SIZE_TO_CURVE['256'] + else: + expected_curve = ECDSA_KEY_SIZE_TO_CURVE.get(args.ecdsa_key_size or '', '') + + if expected_curve and key.curve.name != expected_curve: + raise SystemExit( + f'{args.key_file} is {key_description(key)}, but the selected app signing scheme ' + f'requires curve {expected_curve}. Signature verification would fail at boot. ' + 'Update the ECDSA key size choice or point CONFIG_SECURE_BOOT_SIGNING_KEY to ' + 'a matching key.' + ) + + +if __name__ == '__main__': + main()