diff --git a/docs/en/security/secure-boot-v2.rst b/docs/en/security/secure-boot-v2.rst index 4f300215c7..086877fe54 100644 --- a/docs/en/security/secure-boot-v2.rst +++ b/docs/en/security/secure-boot-v2.rst @@ -654,24 +654,97 @@ Secure Boot Best Practices Technical Details ----------------- -The following sections contain low-level reference descriptions of various Secure Boot elements: - - -Manual Commands -~~~~~~~~~~~~~~~ +The following sections contain low-level reference descriptions of various Secure Boot elements. Secure Boot is integrated into the ESP-IDF build system, so ``idf.py build`` will sign an app image, and ``idf.py bootloader`` will produce a signed bootloader if :ref:`CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES` is enabled. -However, it is possible to use the ``idf.py`` tool to make standalone signatures and digests. +However, it is possible to use the ``idf.py`` or the ``openssl`` tool to generate standalone signatures and verify them. Using ``idf.py`` is recommended, but in case you need to generate or verify signatures in non-ESP-IDF environments, +you could also use the ``openssl`` commands as the Secure Boot V2 signature generation is compliant with the standard signing algorithms. -To sign a binary image: +Generating and Verifying signatures using ``idf.py`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. code-block:: +1. To sign a binary image: - idf.py secure-sign-data --keyfile ./my_signing_key.pem --output ./image_signed.bin image-unsigned.bin + .. code-block:: + + idf.py secure-sign-data --keyfile ./my_signing_key.pem --output ./image_signed.bin image-unsigned.bin Keyfile is the PEM file containing an {IDF_TARGET_SBV2_KEY} private signing key. +2. To verify a signed binary image: + + .. code-block:: + + idf.py secure-verify-signature --keyfile ./my_signing_key.pem image_signed.bin + +Keyfile is the PEM file containing an {IDF_TARGET_SBV2_KEY} public/\private signing key. + +Generating and Verifying signatures using OpenSSL +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is preferred to use the ``idf.py`` tool to generate and verify signatures, but in case you need to perform these operations using OpenSSL, following are the reference commands to do so: + +1. Generate digest of the image binary file whose signature needs to be calculated. + + .. code-block:: bash + + openssl dgst -sha256 -binary BINARY_FILE > DIGEST_BINARY_FILE + +2. Generate signature of the image using the above calculated digest. + + .. only:: SOC_SECURE_BOOT_V2_RSA + + For generating an RSA-PSS signature: + + .. code-block:: bash + + openssl pkeyutl -sign \ + -in DIGEST_BINARY_FILE \ + -inkey PRIVATE_SIGNING_KEY \ + -out SIGNATURE_FILE \ + -pkeyopt digest:sha256 \ + -pkeyopt rsa_padding_mode:pss \ + -pkeyopt rsa_pss_saltlen:32 + + .. only:: SOC_SECURE_BOOT_V2_ECC + + For generating an ECDSA signature: + + .. code-block:: bash + + openssl pkeyutl -sign \ + -in DIGEST_BINARY_FILE \ + -inkey PRIVATE_SIGNING_KEY \ + -out SIGNATURE_FILE + +3. Verify the generated signature. + + .. only:: SOC_SECURE_BOOT_V2_RSA + + For verifying an RSA-PSS signature: + + .. code-block:: bash + + openssl pkeyutl -verify \ + -in DIGEST_BINARY_FILE \ + -pubin -inkey PUBLIC_SIGNING_KEY \ + -sigfile SIGNATURE_FILE \ + -pkeyopt rsa_padding_mode:pss \ + -pkeyopt rsa_pss_saltlen:32 \ + -pkeyopt digest:sha256 + + .. only:: SOC_SECURE_BOOT_V2_ECC + + For verifying an ECDSA signature: + + .. code-block:: bash + + openssl pkeyutl -verify \ + -in DIGEST_BINARY_FILE \ + -pubin -inkey PUBLIC_SIGNING_KEY \ + -sigfile SIGNATURE_FILE + .. _secure-boot-v2-and-flash-encr: diff --git a/tools/idf_py_actions/serial_ext.py b/tools/idf_py_actions/serial_ext.py index 983731560f..37d2d304d8 100644 --- a/tools/idf_py_actions/serial_ext.py +++ b/tools/idf_py_actions/serial_ext.py @@ -436,6 +436,22 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict: sign_data_args += [extra_args['datafile']] RunTool('espsecure', sign_data_args, args.build_dir)() + def secure_verify_signature(action: str, + ctx: click.core.Context, + args: PropertyDict, + version: str, + keyfile: str, + **extra_args: str) -> None: + ensure_build_directory(args, ctx.info_name) + verify_signature_args = [PYTHON, '-m', 'espsecure', 'verify_signature'] + if version: + verify_signature_args += ['--version', version] + if keyfile: + verify_signature_args += ['--keyfile', keyfile] + if extra_args['datafile']: + verify_signature_args += [extra_args['datafile']] + RunTool('espsecure', verify_signature_args, args.build_dir)() + def _parse_efuse_args(ctx: click.core.Context, args: PropertyDict, extra_args: Dict) -> List: efuse_args = [] if args.port: @@ -792,6 +808,28 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict: }, ], }, + 'secure-verify-signature': { + 'callback': secure_verify_signature, + 'help': ('Verify a previously signed binary image, using the ECDSA (V1) or either RSA or ECDSA (V2) public key.'), + 'options': [ + { + 'names': ['--version', '-v'], + 'help': ('Version of the secure boot signing scheme to use.'), + 'type': click.Choice(['1', '2']), + 'default': '2', + }, + { + 'names': ['--keyfile', '-k'], + 'help': ('Public key file for verification. Can be private or public key in PEM format.'), + }, + ], + 'arguments': [ + { + 'names': ['datafile'], + 'nargs': 1, + }, + ], + }, 'efuse-burn': { 'callback': efuse_burn, 'help': 'Burn the eFuse with the specified name.', diff --git a/tools/test_idf_py/test_idf_py.py b/tools/test_idf_py/test_idf_py.py index 0868e56a89..9ccdfe0924 100755 --- a/tools/test_idf_py/test_idf_py.py +++ b/tools/test_idf_py/test_idf_py.py @@ -521,6 +521,19 @@ class TestSecureCommands(TestWrapperCommands): output = self.call_command(sign_command) self.assertIn('Signed', output) + def secure_verify_signature(self): + self.secure_sign_data() + sign_command = [sys.executable, + idf_py_path, + 'secure-verify-signature', + '--version', + '2', + '--keyfile', + f'../{self.signing_key}', + 'bootloader-signed.bin'] + output = self.call_command(sign_command) + self.assertIn('verification successful', output) + class TestMergeBinCommands(TestWrapperCommands): """