diff options
author | Tom Rini <trini@konsulko.com> | 2025-02-21 08:34:34 -0600 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2025-02-21 11:37:27 -0600 |
commit | c8750efe02c20725388dd4279896aaf306acfad4 (patch) | |
tree | 54c9405d7019e51e7654d4ef4c4c1026fba8f02a /tools | |
parent | 8c6cf8aeea7e57ca686de8b765e4baf3a7ef1fa7 (diff) | |
parent | a9842ac6347e2e0e7f6f8b66b5fe254739cdd298 (diff) |
Merge patch series "Add preload_check_sign tool"
Paul HENRYS <paul.henrys_ext@softathome.com> says:
This serie of patches adds a new tool to authenticate files signed
with a preload header.
This tool is also used in the tests to actually verify the
authenticity of the file signed with such a preload header.
Link: https://lore.kernel.org/r/20250212093126.3722186-1-paul.henrys_ext@softathome.com
Diffstat (limited to 'tools')
-rw-r--r-- | tools/.gitignore | 1 | ||||
-rw-r--r-- | tools/Kconfig | 5 | ||||
-rw-r--r-- | tools/Makefile | 5 | ||||
-rw-r--r-- | tools/binman/ftest.py | 15 | ||||
-rw-r--r-- | tools/image-host.c | 141 | ||||
-rw-r--r-- | tools/mkimage.h | 4 | ||||
-rw-r--r-- | tools/preload_check_sign.c | 161 |
7 files changed, 332 insertions, 0 deletions
diff --git a/tools/.gitignore b/tools/.gitignore index 0108c567309..6a5c613f772 100644 --- a/tools/.gitignore +++ b/tools/.gitignore @@ -29,6 +29,7 @@ /mxsboot /ncb /prelink-riscv +/preload_check_sign /printinitialenv /proftool /relocate-rela diff --git a/tools/Kconfig b/tools/Kconfig index 01ff0fcf748..8e272ee99a8 100644 --- a/tools/Kconfig +++ b/tools/Kconfig @@ -9,6 +9,11 @@ config MKIMAGE_DTC_PATH some cases the system dtc may not support all required features and the path to a different version should be given here. +config TOOLS_IMAGE_PRE_LOAD + def_bool y + help + Enable pre-load signature support in the tools builds. + config TOOLS_CRC16 def_bool y help diff --git a/tools/Makefile b/tools/Makefile index 237fa900a24..e5f5eea47c7 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -66,6 +66,7 @@ mkenvimage-objs := mkenvimage.o os_support.o generated/lib/crc32.o hostprogs-y += dumpimage mkimage hostprogs-$(CONFIG_TOOLS_LIBCRYPTO) += fit_info fit_check_sign hostprogs-$(CONFIG_TOOLS_LIBCRYPTO) += fdt_add_pubkey +hostprogs-$(CONFIG_TOOLS_LIBCRYPTO) += preload_check_sign ifneq ($(CONFIG_CMD_BOOTEFI_SELFTEST)$(CONFIG_FWU_MDATA_GPT_BLK),) hostprogs-y += file2include @@ -89,6 +90,8 @@ ECDSA_OBJS-$(CONFIG_TOOLS_LIBCRYPTO) := $(addprefix generated/lib/ecdsa/, ecdsa- AES_OBJS-$(CONFIG_TOOLS_LIBCRYPTO) := $(addprefix generated/lib/aes/, \ aes-encrypt.o aes-decrypt.o) +PRELOAD_OBJS-$(CONFIG_TOOLS_LIBCRYPTO) := generated/boot/image-pre-load.o + # Cryptographic helpers and image types that depend on openssl/libcrypto LIBCRYPTO_OBJS-$(CONFIG_TOOLS_LIBCRYPTO) := \ generated/lib/fdt-libcrypto.o \ @@ -158,6 +161,7 @@ fit_info-objs := $(dumpimage-mkimage-objs) fit_info.o fit_check_sign-objs := $(dumpimage-mkimage-objs) fit_check_sign.o fdt_add_pubkey-objs := $(dumpimage-mkimage-objs) fdt_add_pubkey.o file2include-objs := file2include.o +preload_check_sign-objs := $(dumpimage-mkimage-objs) $(PRELOAD_OBJS-y) preload_check_sign.o ifneq ($(CONFIG_MX23)$(CONFIG_MX28)$(CONFIG_TOOLS_LIBCRYPTO),) # Add CFG_MXS into host CFLAGS, so we can check whether or not register @@ -195,6 +199,7 @@ HOSTLDLIBS_dumpimage := $(HOSTLDLIBS_mkimage) HOSTLDLIBS_fit_info := $(HOSTLDLIBS_mkimage) HOSTLDLIBS_fit_check_sign := $(HOSTLDLIBS_mkimage) HOSTLDLIBS_fdt_add_pubkey := $(HOSTLDLIBS_mkimage) +HOSTLDLIBS_preload_check_sign := $(HOSTLDLIBS_mkimage) hostprogs-$(CONFIG_EXYNOS5250) += mkexynosspl hostprogs-$(CONFIG_EXYNOS5420) += mkexynosspl diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index d2802f67e2d..e0685884cef 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -762,6 +762,16 @@ class TestFunctional(unittest.TestCase): return False return True + def _CheckPreload(self, image, key, algo="sha256,rsa2048", + padding="pkcs-1.5"): + try: + tools.run('preload_check_sign', '-k', key, '-a', algo, '-p', + padding, '-f', image) + except: + self.fail('Expected image signed with a pre-load') + return False + return True + def testRun(self): """Test a basic run with valid args""" result = self._RunBinman('-h') @@ -5781,9 +5791,14 @@ fdt fdtmap Extract the devicetree blob from the fdtmap data = self._DoReadFileDtb( '230_pre_load.dts', entry_args=entry_args, extra_indirs=[os.path.join(self._binman_dir, 'test')])[0] + + image_fname = tools.get_output_filename('image.bin') + is_signed = self._CheckPreload(image_fname, self.TestFile("dev.key")) + self.assertEqual(PRE_LOAD_MAGIC, data[:len(PRE_LOAD_MAGIC)]) self.assertEqual(PRE_LOAD_VERSION, data[4:4 + len(PRE_LOAD_VERSION)]) self.assertEqual(PRE_LOAD_HDR_SIZE, data[8:8 + len(PRE_LOAD_HDR_SIZE)]) + self.assertEqual(is_signed, True) def testPreLoadNoKey(self): """Test an image with a pre-load heade0r with missing key""" diff --git a/tools/image-host.c b/tools/image-host.c index 05d8c898209..a071a244e07 100644 --- a/tools/image-host.c +++ b/tools/image-host.c @@ -19,6 +19,11 @@ #include <openssl/evp.h> #endif +#if CONFIG_IS_ENABLED(IMAGE_PRE_LOAD) +#include <openssl/rsa.h> +#include <openssl/err.h> +#endif + /** * fit_set_hash_value - set hash value in requested has node * @fit: pointer to the FIT format image header @@ -1397,3 +1402,139 @@ int fit_check_sign(const void *fit, const void *key, return ret; } #endif + +#if CONFIG_IS_ENABLED(IMAGE_PRE_LOAD) +/** + * rsa_verify_openssl() - Verify a signature against some data with openssl API + * + * Verify a RSA PKCS1.5/PSS signature against an expected hash. + * + * @info: Specifies the key and algorithms + * @region: Pointer to the input data + * @region_count: Number of region + * @sig: Signature + * @sig_len: Number of bytes in the signature + * Return: 0 if verified, -ve on error + */ +int rsa_verify_openssl(struct image_sign_info *info, + const struct image_region region[], int region_count, + uint8_t *sig, uint sig_len) +{ + EVP_PKEY *pkey = NULL; + EVP_PKEY_CTX *ckey = NULL; + EVP_MD_CTX *ctx = NULL; + int pad; + int size; + int i; + int ret = 0; + + if (!info) { + fprintf(stderr, "No info provided\n"); + ret = -EINVAL; + goto out; + } + + if (!info->key) { + fprintf(stderr, "No key provided\n"); + ret = -EINVAL; + goto out; + } + + if (!info->checksum) { + fprintf(stderr, "No checksum information\n"); + ret = -EINVAL; + goto out; + } + + if (!info->padding) { + fprintf(stderr, "No padding information\n"); + ret = -EINVAL; + goto out; + } + + if (region_count < 1) { + fprintf(stderr, "Invalid value for region_count: %d\n", region_count); + ret = -EINVAL; + goto out; + } + + pkey = (EVP_PKEY *)info->key; + + ckey = EVP_PKEY_CTX_new(pkey, NULL); + if (!ckey) { + ret = -ENOMEM; + fprintf(stderr, "EVK key context setup failed: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + size = EVP_PKEY_size(pkey); + if (size > sig_len) { + fprintf(stderr, "Invalid signature size (%d bytes)\n", + size); + ret = -EINVAL; + goto out; + } + + ctx = EVP_MD_CTX_new(); + if (!ctx) { + ret = -ENOMEM; + fprintf(stderr, "EVP context creation failed: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + EVP_MD_CTX_init(ctx); + + if (EVP_DigestVerifyInit(ctx, &ckey, + EVP_get_digestbyname(info->checksum->name), + NULL, pkey) <= 0) { + ret = -EINVAL; + fprintf(stderr, "Verifier setup failed: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + if (!strcmp(info->padding->name, "pkcs-1.5")) { + pad = RSA_PKCS1_PADDING; + } else if (!strcmp(info->padding->name, "pss")) { + pad = RSA_PKCS1_PSS_PADDING; + } else { + ret = -ENOMSG; + fprintf(stderr, "Unsupported padding: %s\n", + info->padding->name); + goto out; + } + + if (EVP_PKEY_CTX_set_rsa_padding(ckey, pad) <= 0) { + ret = -EINVAL; + fprintf(stderr, "padding setup has failed: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + for (i = 0; i < region_count; ++i) { + if (EVP_DigestVerifyUpdate(ctx, region[i].data, + region[i].size) <= 0) { + ret = -EINVAL; + fprintf(stderr, "Hashing data failed: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + } + + if (EVP_DigestVerifyFinal(ctx, sig, sig_len) <= 0) { + ret = -EINVAL; + fprintf(stderr, "Verifying digest failed: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } +out: + if (ctx) + EVP_MD_CTX_free(ctx); + + if (ret) + fprintf(stderr, "Failed to verify signature\n"); + + return ret; +} +#endif diff --git a/tools/mkimage.h b/tools/mkimage.h index 15741f250fd..5d6bcc9301a 100644 --- a/tools/mkimage.h +++ b/tools/mkimage.h @@ -37,6 +37,10 @@ static inline void *map_sysmem(ulong paddr, unsigned long len) return (void *)(uintptr_t)paddr; } +static inline void unmap_sysmem(const void *vaddr) +{ +} + static inline ulong map_to_sysmem(const void *ptr) { return (ulong)(uintptr_t)ptr; diff --git a/tools/preload_check_sign.c b/tools/preload_check_sign.c new file mode 100644 index 00000000000..63a778203f0 --- /dev/null +++ b/tools/preload_check_sign.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Check a file including a preload header including a signature + * + * Copyright (c) 2025 Paul HENRYS <paul.henrys_ext@softathome.com> + * + * Binman makes it possible to generate a preload header signing part or the + * complete file. The tool preload_check_sign allows to verify and authenticate + * a file starting with a preload header. + */ +#include <stdio.h> +#include <unistd.h> +#include <openssl/pem.h> +#include <openssl/evp.h> +#include <openssl/core_names.h> +#include <openssl/err.h> +#include <image.h> + +extern void image_pre_load_sig_set_info(struct image_sig_info *info); +extern int image_pre_load_sig(ulong addr); + +static void usage(char *cmdname) +{ + fprintf(stderr, "Usage: %s -f file -k PEM key file\n" + " -f ==> set file which should be checked\n" + " -k ==> PEM key file\n" + " -a ==> algo (default: sha256,rsa2048)\n" + " -p ==> padding (default: pkcs-1.5)\n" + " -h ==> help\n", + cmdname); + exit(EXIT_FAILURE); +} + +int main(int argc, char **argv) +{ + int ret = 0; + char cmdname[256]; + char *file = NULL; + char *keyfile = NULL; + int c; + FILE *fp = NULL; + FILE *fp_key = NULL; + size_t bytes; + long filesize; + void *buffer = NULL; + EVP_PKEY *pkey = NULL; + char *algo = "sha256,rsa2048"; + char *padding = "pkcs-1.5"; + struct image_sig_info info = {0}; + + strncpy(cmdname, *argv, sizeof(cmdname) - 1); + cmdname[sizeof(cmdname) - 1] = '\0'; + while ((c = getopt(argc, argv, "f:k:a:p:h")) != -1) + switch (c) { + case 'f': + file = optarg; + break; + case 'k': + keyfile = optarg; + break; + case 'a': + algo = optarg; + break; + case 'p': + padding = optarg; + break; + default: + usage(cmdname); + break; + } + + if (!file) { + fprintf(stderr, "%s: Missing file\n", *argv); + usage(*argv); + } + + if (!keyfile) { + fprintf(stderr, "%s: Missing key file\n", *argv); + usage(*argv); + } + + fp = fopen(file, "r"); + if (!fp) { + fprintf(stderr, "Error opening file: %s\n", file); + ret = EXIT_FAILURE; + goto out; + } + + fseek(fp, 0, SEEK_END); + filesize = ftell(fp); + rewind(fp); + + buffer = malloc(filesize); + if (!buffer) { + fprintf(stderr, "Memory allocation failed"); + ret = EXIT_FAILURE; + goto out; + } + + bytes = fread(buffer, 1, filesize, fp); + if (bytes != filesize) { + fprintf(stderr, "Error reading file\n"); + ret = EXIT_FAILURE; + goto out; + } + + fp_key = fopen(keyfile, "r"); + if (!fp_key) { + fprintf(stderr, "Error opening file: %s\n", keyfile); + ret = EXIT_FAILURE; + goto out; + } + + /* Attempt to read the private key */ + pkey = PEM_read_PrivateKey(fp_key, NULL, NULL, NULL); + if (!pkey) { + /* If private key reading fails, try reading as a public key */ + fseek(fp_key, 0, SEEK_SET); + pkey = PEM_read_PUBKEY(fp_key, NULL, NULL, NULL); + } + if (!pkey) { + fprintf(stderr, "Unable to retrieve the public key: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + ret = EXIT_FAILURE; + goto out; + } + + info.algo_name = algo; + info.padding_name = padding; + info.key = (uint8_t *)pkey; + info.mandatory = 1; + info.sig_size = (EVP_PKEY_get_bits(pkey) + 7) / 8; + if (info.sig_size < 0) { + fprintf(stderr, "Fail to retrieve the signature size: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + ret = EXIT_FAILURE; + goto out; + } + + /* Compute signature information */ + info.sig_info.name = info.algo_name; + info.sig_info.padding = image_get_padding_algo(info.padding_name); + info.sig_info.checksum = image_get_checksum_algo(info.sig_info.name); + info.sig_info.crypto = image_get_crypto_algo(info.sig_info.name); + info.sig_info.key = info.key; + info.sig_info.keylen = info.key_len; + + /* Check the signature */ + image_pre_load_sig_set_info(&info); + ret = image_pre_load_sig((ulong)buffer); +out: + if (fp) + fclose(fp); + if (fp_key) + fclose(fp_key); + if (info.key) + EVP_PKEY_free(pkey); + free(buffer); + + exit(ret); +} |