diff options
author | Tom Rini <trini@konsulko.com> | 2021-04-15 17:10:25 -0400 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2021-04-15 17:10:25 -0400 |
commit | a6232e065dd9e349bf5908c928734c6b5b018112 (patch) | |
tree | 49a54d23fc32194ce266f06cb8fae3eff4574339 | |
parent | 45b3cf88da24206a6cb847efe837fddc120af3e8 (diff) | |
parent | fbc777429fa35312a9ea5f106692172d3153e659 (diff) | |
download | u-boot-WIP/15Apr2021.zip u-boot-WIP/15Apr2021.tar.gz u-boot-WIP/15Apr2021.tar.bz2 |
Merge branch '2021-04-14-assorted-vboot-improvements'WIP/15Apr2021
- Add ECDSA support to FIT images
- Improve FIT image loadables (incl fpga) support
- Further FIT improvements with SPL
31 files changed, 955 insertions, 266 deletions
diff --git a/arch/arm/cpu/armv8/fsl-layerscape/spl.c b/arch/arm/cpu/armv8/fsl-layerscape/spl.c index 01dd6a3..5b43a2a 100644 --- a/arch/arm/cpu/armv8/fsl-layerscape/spl.c +++ b/arch/arm/cpu/armv8/fsl-layerscape/spl.c @@ -150,13 +150,4 @@ int spl_start_uboot(void) return 1; } #endif /* CONFIG_SPL_OS_BOOT */ -#ifdef CONFIG_SPL_LOAD_FIT -__weak int board_fit_config_name_match(const char *name) -{ - /* Just empty function now - can't decide what to choose */ - debug("%s: %s\n", __func__, name); - - return 0; -} -#endif #endif /* CONFIG_SPL_BUILD */ diff --git a/arch/arm/mach-rockchip/spl.c b/arch/arm/mach-rockchip/spl.c index 4b5c22d..02c40fb 100644 --- a/arch/arm/mach-rockchip/spl.c +++ b/arch/arm/mach-rockchip/spl.c @@ -151,13 +151,3 @@ void board_init_f(ulong dummy) #endif preloader_console_init(); } - -#ifdef CONFIG_SPL_LOAD_FIT -int __weak board_fit_config_name_match(const char *name) -{ - /* Just empty function now - can't decide what to choose */ - debug("%s: %s\n", __func__, name); - - return 0; -} -#endif diff --git a/common/Kconfig.boot b/common/Kconfig.boot index 9c335f4..5a18d62 100644 --- a/common/Kconfig.boot +++ b/common/Kconfig.boot @@ -202,6 +202,16 @@ config SPL_LOAD_FIT particular it can handle selecting from multiple device tree and passing the correct one to U-Boot. + This path has the following limitations: + + 1. "loadables" images, other than FTDs, which do not have a "load" + property will not be loaded. This limitation also applies to FPGA + images with the correct "compatible" string. + 2. For FPGA images, only the "compatible" = "u-boot,fpga-legacy" + loading method is supported. + 3. FDTs are only loaded for images with an "os" property of "u-boot". + "linux" images are also supported with Falcon boot mode. + config SPL_LOAD_FIT_ADDRESS hex "load address of fit image" depends on SPL_LOAD_FIT diff --git a/common/common_fit.c b/common/common_fit.c index 219674d..cde2dc4 100644 --- a/common/common_fit.c +++ b/common/common_fit.c @@ -22,6 +22,11 @@ ulong fdt_getprop_u32(const void *fdt, int node, const char *prop) return fdt32_to_cpu(*cell); } +__weak int board_fit_config_name_match(const char *name) +{ + return -EINVAL; +} + /* * Iterate over all /configurations subnodes and call a platform specific * function to find the matching configuration. diff --git a/common/image-fit-sig.c b/common/image-fit-sig.c index 34ebb8e..55ddf18 100644 --- a/common/image-fit-sig.c +++ b/common/image-fit-sig.c @@ -16,7 +16,7 @@ DECLARE_GLOBAL_DATA_PTR; #include <fdt_region.h> #include <image.h> #include <u-boot/rsa.h> -#include <u-boot/rsa-checksum.h> +#include <u-boot/hash-checksum.h> #define IMAGE_MAX_HASHED_NODES 100 diff --git a/common/image-fit.c b/common/image-fit.c index b972042..e614643 100644 --- a/common/image-fit.c +++ b/common/image-fit.c @@ -1959,6 +1959,8 @@ static const char *fit_get_image_type_property(int type) return FIT_FDT_PROP; case IH_TYPE_KERNEL: return FIT_KERNEL_PROP; + case IH_TYPE_FIRMWARE: + return FIT_FIRMWARE_PROP; case IH_TYPE_RAMDISK: return FIT_RAMDISK_PROP; case IH_TYPE_X86_SETUP: @@ -2091,6 +2093,7 @@ int fit_image_load(bootm_headers_t *images, ulong addr, bootstage_mark(bootstage_id + BOOTSTAGE_SUB_CHECK_ALL); type_ok = fit_image_check_type(fit, noffset, image_type) || fit_image_check_type(fit, noffset, IH_TYPE_FIRMWARE) || + fit_image_check_type(fit, noffset, IH_TYPE_TEE) || (image_type == IH_TYPE_KERNEL && fit_image_check_type(fit, noffset, IH_TYPE_KERNEL_NOLOAD)); @@ -2098,6 +2101,7 @@ int fit_image_load(bootm_headers_t *images, ulong addr, image_type == IH_TYPE_FPGA || fit_image_check_os(fit, noffset, IH_OS_LINUX) || fit_image_check_os(fit, noffset, IH_OS_U_BOOT) || + fit_image_check_os(fit, noffset, IH_OS_TEE) || fit_image_check_os(fit, noffset, IH_OS_OPENRTOS) || fit_image_check_os(fit, noffset, IH_OS_EFI) || fit_image_check_os(fit, noffset, IH_OS_VXWORKS); diff --git a/common/image-sig.c b/common/image-sig.c index 4abd3c0..0f8e592 100644 --- a/common/image-sig.c +++ b/common/image-sig.c @@ -16,8 +16,9 @@ DECLARE_GLOBAL_DATA_PTR; #endif /* !USE_HOSTCC*/ #include <image.h> +#include <u-boot/ecdsa.h> #include <u-boot/rsa.h> -#include <u-boot/rsa-checksum.h> +#include <u-boot/hash-checksum.h> #define IMAGE_MAX_HASHED_NODES 100 @@ -83,8 +84,14 @@ struct crypto_algo crypto_algos[] = { .sign = rsa_sign, .add_verify_data = rsa_add_verify_data, .verify = rsa_verify, - } - + }, + { + .name = "ecdsa256", + .key_len = ECDSA256_BYTES, + .sign = ecdsa_sign, + .add_verify_data = ecdsa_add_verify_data, + .verify = ecdsa_verify, + }, }; struct padding_algo padding_algos[] = { diff --git a/common/spl/spl.c b/common/spl/spl.c index 556a91a..8c4cd93 100644 --- a/common/spl/spl.c +++ b/common/spl/spl.c @@ -202,7 +202,7 @@ static int spl_load_fit_image(struct spl_image_info *spl_image, { bootm_headers_t images; const char *fit_uname_config = NULL; - const char *fit_uname_fdt = FIT_FDT_PROP; + uintptr_t fdt_hack; const char *uname; ulong fw_data = 0, dt_data = 0, img_data = 0; ulong fw_len = 0, dt_len = 0, img_len = 0; @@ -215,15 +215,33 @@ static int spl_load_fit_image(struct spl_image_info *spl_image, ret = fit_image_load(&images, (ulong)header, NULL, &fit_uname_config, IH_ARCH_DEFAULT, IH_TYPE_STANDALONE, -1, - FIT_LOAD_REQUIRED, &fw_data, &fw_len); + FIT_LOAD_OPTIONAL, &fw_data, &fw_len); + if (ret >= 0) { + printf("DEPRECATED: 'standalone = ' property."); + printf("Please use either 'firmware =' or 'kernel ='\n"); + } else { + ret = fit_image_load(&images, (ulong)header, NULL, + &fit_uname_config, IH_ARCH_DEFAULT, + IH_TYPE_FIRMWARE, -1, FIT_LOAD_OPTIONAL, + &fw_data, &fw_len); + } + + if (ret < 0) { + ret = fit_image_load(&images, (ulong)header, NULL, + &fit_uname_config, IH_ARCH_DEFAULT, + IH_TYPE_KERNEL, -1, FIT_LOAD_OPTIONAL, + &fw_data, &fw_len); + } + if (ret < 0) return ret; spl_image->size = fw_len; spl_image->entry_point = fw_data; spl_image->load_addr = fw_data; - spl_image->os = IH_OS_U_BOOT; - spl_image->name = "U-Boot"; + if (fit_image_get_os(header, ret, &spl_image->os)) + spl_image->os = IH_OS_INVALID; + spl_image->name = genimg_get_os_name(spl_image->os); debug(SPL_TPL_PROMPT "payload image: %32s load addr: 0x%lx size: %d\n", spl_image->name, spl_image->load_addr, spl_image->size); @@ -231,13 +249,21 @@ static int spl_load_fit_image(struct spl_image_info *spl_image, #ifdef CONFIG_SPL_FIT_SIGNATURE images.verify = 1; #endif - ret = fit_image_load(&images, (ulong)header, - &fit_uname_fdt, &fit_uname_config, + ret = fit_image_load(&images, (ulong)header, NULL, &fit_uname_config, IH_ARCH_DEFAULT, IH_TYPE_FLATDT, -1, FIT_LOAD_OPTIONAL, &dt_data, &dt_len); - if (ret >= 0) + if (ret >= 0) { spl_image->fdt_addr = (void *)dt_data; + if (spl_image->os == IH_OS_U_BOOT) { + /* HACK: U-boot expects FDT at a specific address */ + fdt_hack = spl_image->load_addr + spl_image->size; + fdt_hack = (fdt_hack + 3) & ~3; + debug("Relocating FDT to %p\n", spl_image->fdt_addr); + memcpy((void *)fdt_hack, spl_image->fdt_addr, dt_len); + } + } + conf_noffset = fit_conf_get_node((const void *)header, fit_uname_config); if (conf_noffset <= 0) diff --git a/common/spl/spl_fit.c b/common/spl/spl_fit.c index 49508fc..4288f57 100644 --- a/common/spl/spl_fit.c +++ b/common/spl/spl_fit.c @@ -224,7 +224,7 @@ static int get_aligned_image_size(struct spl_load_info *info, int data_size, * @image_info: will be filled with information about the loaded image * If the FIT node does not contain a "load" (address) property, * the image gets loaded to the address pointed to by the - * load_addr member in this struct. + * load_addr member in this struct, if load_addr is not 0 * * Return: 0 on success or a negative error number. */ @@ -259,8 +259,14 @@ static int spl_load_fit_image(struct spl_load_info *info, ulong sector, debug("%s ", genimg_get_comp_name(image_comp)); } - if (fit_image_get_load(fit, node, &load_addr)) + if (fit_image_get_load(fit, node, &load_addr)) { + if (!image_info->load_addr) { + printf("Can't load %s: No load address and no buffer\n", + fit_get_name(fit, node, NULL)); + return -ENOBUFS; + } load_addr = image_info->load_addr; + } if (!fit_image_get_data_position(fit, node, &offset)) { external_data = true; @@ -474,6 +480,20 @@ static int spl_fit_record_loadable(const struct spl_fit_info *ctx, int index, return ret; } +static int spl_fit_image_is_fpga(const void *fit, int node) +{ + const char *type; + + if (!IS_ENABLED(CONFIG_SPL_FPGA)) + return 0; + + type = fdt_getprop(fit, node, FIT_TYPE_PROP, NULL); + if (!type) + return 0; + + return !strcmp(type, "fpga"); +} + static int spl_fit_image_get_os(const void *fit, int noffset, uint8_t *os) { if (!CONFIG_IS_ENABLED(FIT_IMAGE_TINY) || CONFIG_IS_ENABLED(OS_BOOT)) @@ -523,6 +543,64 @@ __weak bool spl_load_simple_fit_skip_processing(void) return false; } +static void warn_deprecated(const char *msg) +{ + printf("DEPRECATED: %s\n", msg); + printf("\tSee doc/uImage.FIT/source_file_format.txt\n"); +} + +static int spl_fit_upload_fpga(struct spl_fit_info *ctx, int node, + struct spl_image_info *fpga_image) +{ + const char *compatible; + int ret; + + debug("FPGA bitstream at: %x, size: %x\n", + (u32)fpga_image->load_addr, fpga_image->size); + + compatible = fdt_getprop(ctx->fit, node, "compatible", NULL); + if (!compatible) + warn_deprecated("'fpga' image without 'compatible' property"); + else if (strcmp(compatible, "u-boot,fpga-legacy")) + printf("Ignoring compatible = %s property\n", compatible); + + ret = fpga_load(0, (void *)fpga_image->load_addr, fpga_image->size, + BIT_FULL); + if (ret) { + printf("%s: Cannot load the image to the FPGA\n", __func__); + return ret; + } + + puts("FPGA image loaded from FIT\n"); + + return 0; +} + +static int spl_fit_load_fpga(struct spl_fit_info *ctx, + struct spl_load_info *info, ulong sector) +{ + int node, ret; + + struct spl_image_info fpga_image = { + .load_addr = 0, + }; + + node = spl_fit_get_image_node(ctx, "fpga", 0); + if (node < 0) + return node; + + warn_deprecated("'fpga' property in config node. Use 'loadables'"); + + /* Load the image and set up the fpga_image structure */ + ret = spl_load_fit_image(info, sector, ctx, node, &fpga_image); + if (ret) { + printf("%s: Cannot load the FPGA: %i\n", __func__, ret); + return ret; + } + + return spl_fit_upload_fpga(ctx, node, &fpga_image); +} + static int spl_simple_fit_read(struct spl_fit_info *ctx, struct spl_load_info *info, ulong sector, const void *fit_header) @@ -606,31 +684,8 @@ int spl_load_simple_fit(struct spl_image_info *spl_image, if (ret < 0) return ret; -#ifdef CONFIG_SPL_FPGA - node = spl_fit_get_image_node(&ctx, "fpga", 0); - if (node >= 0) { - /* Load the image and set up the spl_image structure */ - ret = spl_load_fit_image(info, sector, &ctx, node, spl_image); - if (ret) { - printf("%s: Cannot load the FPGA: %i\n", __func__, ret); - return ret; - } - - debug("FPGA bitstream at: %x, size: %x\n", - (u32)spl_image->load_addr, spl_image->size); - - ret = fpga_load(0, (const void *)spl_image->load_addr, - spl_image->size, BIT_FULL); - if (ret) { - printf("%s: Cannot load the image to the FPGA\n", - __func__); - return ret; - } - - puts("FPGA image loaded from FIT\n"); - node = -1; - } -#endif + if (IS_ENABLED(CONFIG_SPL_FPGA)) + spl_fit_load_fpga(&ctx, info, sector); /* * Find the U-Boot image using the following search order: @@ -700,6 +755,7 @@ int spl_load_simple_fit(struct spl_image_info *spl_image, if (firmware_node == node) continue; + image_info.load_addr = 0; ret = spl_load_fit_image(info, sector, &ctx, node, &image_info); if (ret < 0) { printf("%s: can't load image loadables index %d (ret = %d)\n", @@ -707,6 +763,9 @@ int spl_load_simple_fit(struct spl_image_info *spl_image, return ret; } + if (spl_fit_image_is_fpga(ctx.fit, node)) + spl_fit_upload_fpga(&ctx, node, &image_info); + if (!spl_fit_image_get_os(ctx.fit, node, &os_type)) debug("Loadable is %s\n", genimg_get_os_name(os_type)); diff --git a/doc/uImage.FIT/multi-with-fpga.its b/doc/uImage.FIT/multi-with-fpga.its index 47ee576..021cbc7 100644 --- a/doc/uImage.FIT/multi-with-fpga.its +++ b/doc/uImage.FIT/multi-with-fpga.its @@ -29,6 +29,7 @@ arch = "arm"; compression = "none"; load = <0x30000000>; + compatible = "u-boot,fpga-legacy" hash-1 { algo = "md5"; }; @@ -61,7 +62,7 @@ description = "Linux with fpga"; kernel = "linux_kernel"; fdt = "fdt-1"; - fpga = "fpga"; + loadables = "fpga"; }; }; }; diff --git a/doc/uImage.FIT/signature.txt b/doc/uImage.FIT/signature.txt index a345588..d9a9121 100644 --- a/doc/uImage.FIT/signature.txt +++ b/doc/uImage.FIT/signature.txt @@ -142,7 +142,7 @@ public key in U-Boot's control FDT (using CONFIG_OF_CONTROL). Public keys should be stored as sub-nodes in a /signature node. Required properties are: -- algo: Algorithm name (e.g. "sha1,rsa2048") +- algo: Algorithm name (e.g. "sha1,rsa2048" or "sha256,ecdsa256") Optional properties are: @@ -167,6 +167,11 @@ For RSA the following are mandatory: - rsa,r-squared: (2^num-bits)^2 as a big-endian multi-word integer - rsa,n0-inverse: -1 / modulus[0] mod 2^32 +For ECDSA the following are mandatory: +- ecdsa,curve: Name of ECDSA curve (e.g. "prime256v1") +- ecdsa,x-point: Public key X coordinate as a big-endian multi-word integer +- ecdsa,y-point: Public key Y coordinate as a big-endian multi-word integer + These parameters can be added to a binary device tree using parameter -K of the mkimage command:: @@ -467,6 +472,19 @@ Test Verified Boot Run: signed config with bad hash: OK Test passed +Software signing: keydir vs keyfile +----------------------------------- + +In the simplest case, signing is done by giving mkimage the 'keyfile'. This is +the path to a file containing the signing key. + +The alternative is to pass the 'keydir' argument. In this case the filename of +the key is derived from the 'keydir' and the "key-name-hint" property in the +FIT. In this case the "key-name-hint" property is mandatory, and the key must +exist in "<keydir>/<key-name-hint>.<ext>" Here the extension "ext" is +specific to the signing algorithm. + + Hardware Signing with PKCS#11 or with HSM ----------------------------------------- diff --git a/doc/uImage.FIT/source_file_format.txt b/doc/uImage.FIT/source_file_format.txt index 00ed3eb..f93ac6d 100644 --- a/doc/uImage.FIT/source_file_format.txt +++ b/doc/uImage.FIT/source_file_format.txt @@ -184,6 +184,7 @@ the '/images' node should have the following layout: Mandatory for types: "firmware", and "kernel". - compatible : compatible method for loading image. Mandatory for types: "fpga", and images that do not specify a load address. + To use the generic fpga loading routine, use "u-boot,fpga-legacy". Optional nodes: - hash-1 : Each hash sub-node represents separate hash or checksum diff --git a/include/image.h b/include/image.h index aeb0d37..3ff3c03 100644 --- a/include/image.h +++ b/include/image.h @@ -1136,9 +1136,10 @@ int fit_cipher_data(const char *keydir, void *keydest, void *fit, * 0, on success * libfdt error code, on failure */ -int fit_add_verification_data(const char *keydir, void *keydest, void *fit, - const char *comment, int require_keys, - const char *engine_id, const char *cmdname); +int fit_add_verification_data(const char *keydir, const char *keyfile, + void *keydest, void *fit, const char *comment, + int require_keys, const char *engine_id, + const char *cmdname); int fit_image_verify_with_data(const void *fit, int image_noffset, const void *data, size_t size); @@ -1224,16 +1225,19 @@ int calculate_hash(const void *data, int data_len, const char *algo, # if defined(CONFIG_FIT_SIGNATURE) # define IMAGE_ENABLE_SIGN 1 # define IMAGE_ENABLE_VERIFY 1 +# define IMAGE_ENABLE_VERIFY_ECDSA 1 # define FIT_IMAGE_ENABLE_VERIFY 1 # include <openssl/evp.h> # else # define IMAGE_ENABLE_SIGN 0 # define IMAGE_ENABLE_VERIFY 0 +# define IMAGE_ENABLE_VERIFY_ECDSA 0 # define FIT_IMAGE_ENABLE_VERIFY 0 # endif #else # define IMAGE_ENABLE_SIGN 0 # define IMAGE_ENABLE_VERIFY CONFIG_IS_ENABLED(RSA_VERIFY) +# define IMAGE_ENABLE_VERIFY_ECDSA 0 # define FIT_IMAGE_ENABLE_VERIFY CONFIG_IS_ENABLED(FIT_SIGNATURE) #endif @@ -1253,10 +1257,17 @@ void image_set_host_blob(void *host_blob); #endif #endif /* IMAGE_ENABLE_FIT */ -/* Information passed to the signing routines */ +/* + * Information passed to the signing routines + * + * Either 'keydir', 'keyname', or 'keyfile' can be NULL. However, either + * 'keyfile', or both 'keydir' and 'keyname' should have valid values. If + * neither are valid, some operations might fail with EINVAL. + */ struct image_sign_info { const char *keydir; /* Directory conaining keys */ const char *keyname; /* Name of key to use */ + const char *keyfile; /* Filename of private or public key */ void *fit; /* Pointer to FIT blob */ int node_offset; /* Offset of signature node */ const char *name; /* Algorithm name */ @@ -1283,7 +1294,7 @@ struct image_region { }; #if IMAGE_ENABLE_VERIFY -# include <u-boot/rsa-checksum.h> +# include <u-boot/hash-checksum.h> #endif struct checksum_algo { const char *name; diff --git a/include/u-boot/ecdsa.h b/include/u-boot/ecdsa.h new file mode 100644 index 0000000..979690d --- /dev/null +++ b/include/u-boot/ecdsa.h @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2020, Alexandru Gagniuc <mr.nuke.me@gmail.com>. + */ + +#ifndef _ECDSA_H +#define _ECDSA_H + +#include <errno.h> +#include <image.h> +#include <linux/kconfig.h> + +/** + * crypto_algo API impementation for ECDSA; + * @see "struct crypto_algo" + * @{ + */ +#if IMAGE_ENABLE_SIGN +/** + * sign() - calculate and return signature for given input data + * + * @info: Specifies key and FIT information + * @data: Pointer to the input data + * @data_len: Data length + * @sigp: Set to an allocated buffer holding the signature + * @sig_len: Set to length of the calculated hash + * + * This computes input data signature according to selected algorithm. + * Resulting signature value is placed in an allocated buffer, the + * pointer is returned as *sigp. The length of the calculated + * signature is returned via the sig_len pointer argument. The caller + * should free *sigp. + * + * @return: 0, on success, -ve on error + */ +int ecdsa_sign(struct image_sign_info *info, const struct image_region region[], + int region_count, uint8_t **sigp, uint *sig_len); + +/** + * add_verify_data() - Add verification information to FDT + * + * Add public key information to the FDT node, suitable for + * verification at run-time. The information added depends on the + * algorithm being used. I just copypasted this from rsa.h. + * + * @info: Specifies key and FIT information + * @keydest: Destination FDT blob for public key data + * @return: 0, on success, -ENOSPC if the keydest FDT blob ran out of space, + * other -ve value on error + */ +int ecdsa_add_verify_data(struct image_sign_info *info, void *keydest); +#else +static inline +int ecdsa_sign(struct image_sign_info *info, const struct image_region region[], + int region_count, uint8_t **sigp, uint *sig_len) +{ + return -ENXIO; +} + +static inline +int ecdsa_add_verify_data(struct image_sign_info *info, void *keydest) +{ + return -ENXIO; +} +#endif + +#if IMAGE_ENABLE_VERIFY_ECDSA +/** + * verify() - Verify a signature against some data + * + * @info: Specifies key and FIT information + * @data: Pointer to the input data + * @data_len: Data length + * @sig: Signature + * @sig_len: Number of bytes in signature + * @return 0 if verified, -ve on error + */ +int ecdsa_verify(struct image_sign_info *info, + const struct image_region region[], int region_count, + uint8_t *sig, uint sig_len); +#else +static inline +int ecdsa_verify(struct image_sign_info *info, + const struct image_region region[], int region_count, + uint8_t *sig, uint sig_len) +{ + return -ENXIO; +} +#endif +/** @} */ + +#define ECDSA256_BYTES (256 / 8) + +#endif diff --git a/include/u-boot/fdt-libcrypto.h b/include/u-boot/fdt-libcrypto.h new file mode 100644 index 0000000..5142f37 --- /dev/null +++ b/include/u-boot/fdt-libcrypto.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2020, Alexandru Gagniuc <mr.nuke.me@gmail.com> + * Copyright (c) 2013, Google Inc. + */ + +#ifndef _FDT_LIBCRYPTO_H +#define _FDT_LIBCRYPTO_H + +#include <openssl/bn.h> + +/** + * fdt_add_bignum() - Write a libcrypto BIGNUM as an FDT property + * + * Convert a libcrypto BIGNUM * into a big endian array of integers. + * + * @blob: FDT blob to modify + * @noffset: Offset of the FDT node + * @prop_name: What to call the property in the FDT + * @num: pointer to a libcrypto big number + * @num_bits: How big is 'num' in bits? + * @return 0 if all good all working, -ve on horror + */ +int fdt_add_bignum(void *blob, int noffset, const char *prop_name, + BIGNUM *num, int num_bits); + +#endif /* _FDT_LIBCRYPTO_H */ diff --git a/include/u-boot/rsa-checksum.h b/include/u-boot/hash-checksum.h index 54e6a73..54e6a73 100644 --- a/include/u-boot/rsa-checksum.h +++ b/include/u-boot/hash-checksum.h diff --git a/lib/Makefile b/lib/Makefile index c42d4e1..6825671 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -61,6 +61,7 @@ endif obj-$(CONFIG_$(SPL_)ACPIGEN) += acpi/ obj-$(CONFIG_$(SPL_)MD5) += md5.o obj-$(CONFIG_$(SPL_)RSA) += rsa/ +obj-$(CONFIG_FIT_SIGNATURE) += hash-checksum.o obj-$(CONFIG_SHA1) += sha1.o obj-$(CONFIG_SHA256) += sha256.o obj-$(CONFIG_SHA512_ALGO) += sha512.o diff --git a/lib/crypto/pkcs7_verify.c b/lib/crypto/pkcs7_verify.c index 58683ef..82c5c74 100644 --- a/lib/crypto/pkcs7_verify.c +++ b/lib/crypto/pkcs7_verify.c @@ -15,7 +15,7 @@ #include <linux/bitops.h> #include <linux/compat.h> #include <linux/asn1.h> -#include <u-boot/rsa-checksum.h> +#include <u-boot/hash-checksum.h> #include <crypto/public_key.h> #include <crypto/pkcs7_parser.h> #else diff --git a/lib/crypto/x509_public_key.c b/lib/crypto/x509_public_key.c index 91810a8..d557ab2 100644 --- a/lib/crypto/x509_public_key.c +++ b/lib/crypto/x509_public_key.c @@ -19,7 +19,7 @@ #include <linux/kernel.h> #ifdef __UBOOT__ #include <crypto/x509_parser.h> -#include <u-boot/rsa-checksum.h> +#include <u-boot/hash-checksum.h> #else #include <linux/slab.h> #include <keys/asymmetric-subtype.h> diff --git a/lib/ecdsa/ecdsa-libcrypto.c b/lib/ecdsa/ecdsa-libcrypto.c new file mode 100644 index 0000000..1757a14 --- /dev/null +++ b/lib/ecdsa/ecdsa-libcrypto.c @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ECDSA image signing implementation using libcrypto backend + * + * The signature is a binary representation of the (R, S) points, padded to the + * key size. The signature will be (2 * key_size_bits) / 8 bytes. + * + * Deviations from behavior of RSA equivalent: + * - Verification uses private key. This is not technically required, but a + * limitation on how clumsy the openssl API is to use. + * - Handling of keys and key paths: + * - The '-K' key directory option must contain path to the key file, + * instead of the key directory. + * - No assumptions are made about the file extension of the key + * - The 'key-name-hint' property is only used for naming devicetree nodes, + * but is not used for looking up keys on the filesystem. + * + * Copyright (c) 2020,2021, Alexandru Gagniuc <mr.nuke.me@gmail.com> + */ + +#include <u-boot/ecdsa.h> +#include <u-boot/fdt-libcrypto.h> +#include <openssl/ssl.h> +#include <openssl/ec.h> +#include <openssl/bn.h> + +/* Image signing context for openssl-libcrypto */ +struct signer { + EVP_PKEY *evp_key; /* Pointer to EVP_PKEY object */ + EC_KEY *ecdsa_key; /* Pointer to EC_KEY object */ + void *hash; /* Pointer to hash used for verification */ + void *signature; /* Pointer to output signature. Do not free()!*/ +}; + +static int alloc_ctx(struct signer *ctx, const struct image_sign_info *info) +{ + memset(ctx, 0, sizeof(*ctx)); + + if (!OPENSSL_init_ssl(0, NULL)) { + fprintf(stderr, "Failure to init SSL library\n"); + return -1; + } + + ctx->hash = malloc(info->checksum->checksum_len); + ctx->signature = malloc(info->crypto->key_len * 2); + + if (!ctx->hash || !ctx->signature) + return -ENOMEM; + + return 0; +} + +static void free_ctx(struct signer *ctx) +{ + if (ctx->ecdsa_key) + EC_KEY_free(ctx->ecdsa_key); + + if (ctx->evp_key) + EVP_PKEY_free(ctx->evp_key); + + if (ctx->hash) + free(ctx->hash); +} + +/* + * Convert an ECDSA signature to raw format + * + * openssl DER-encodes 'binary' signatures. We want the signature in a raw + * (R, S) point pair. So we have to dance a bit. + */ +static void ecdsa_sig_encode_raw(void *buf, const ECDSA_SIG *sig, size_t order) +{ + int point_bytes = order; + const BIGNUM *r, *s; + uintptr_t s_buf; + + ECDSA_SIG_get0(sig, &r, &s); + s_buf = (uintptr_t)buf + point_bytes; + BN_bn2binpad(r, buf, point_bytes); + BN_bn2binpad(s, (void *)s_buf, point_bytes); +} + +/* Get a signature from a raw encoding */ +static ECDSA_SIG *ecdsa_sig_from_raw(void *buf, size_t order) +{ + int point_bytes = order; + uintptr_t s_buf; + ECDSA_SIG *sig; + BIGNUM *r, *s; + + sig = ECDSA_SIG_new(); + if (!sig) + return NULL; + + s_buf = (uintptr_t)buf + point_bytes; + r = BN_bin2bn(buf, point_bytes, NULL); + s = BN_bin2bn((void *)s_buf, point_bytes, NULL); + ECDSA_SIG_set0(sig, r, s); + + return sig; +} + +/* ECDSA key size in bytes */ +static size_t ecdsa_key_size_bytes(const EC_KEY *key) +{ + const EC_GROUP *group; + + group = EC_KEY_get0_group(key); + return EC_GROUP_order_bits(group) / 8; +} + +static int read_key(struct signer *ctx, const char *key_name) +{ + FILE *f = fopen(key_name, "r"); + + if (!f) { + fprintf(stderr, "Can not get key file '%s'\n", key_name); + return -ENOENT; + } + + ctx->evp_key = PEM_read_PrivateKey(f, NULL, NULL, NULL); + fclose(f); + if (!ctx->evp_key) { + fprintf(stderr, "Can not read key from '%s'\n", key_name); + return -EIO; + } + + if (EVP_PKEY_id(ctx->evp_key) != EVP_PKEY_EC) { + fprintf(stderr, "'%s' is not an ECDSA key\n", key_name); + return -EINVAL; + } + + ctx->ecdsa_key = EVP_PKEY_get1_EC_KEY(ctx->evp_key); + if (!ctx->ecdsa_key) + fprintf(stderr, "Can not extract ECDSA key\n"); + + return (ctx->ecdsa_key) ? 0 : -EINVAL; +} + +/* Prepare a 'signer' context that's ready to sign and verify. */ +static int prepare_ctx(struct signer *ctx, const struct image_sign_info *info) +{ + int key_len_bytes, ret; + char kname[1024]; + + memset(ctx, 0, sizeof(*ctx)); + + if (info->keyfile) { + snprintf(kname, sizeof(kname), "%s", info->keyfile); + } else if (info->keydir && info->keyname) { + snprintf(kname, sizeof(kname), "%s/%s.pem", info->keydir, + info->keyname); + } else { + fprintf(stderr, "keyfile, keyname, or key-name-hint missing\n"); + return -EINVAL; + } + + ret = alloc_ctx(ctx, info); + if (ret) + return ret; + + ret = read_key(ctx, kname); + if (ret) + return ret; + + key_len_bytes = ecdsa_key_size_bytes(ctx->ecdsa_key); + if (key_len_bytes != info->crypto->key_len) { + fprintf(stderr, "Expected a %u-bit key, got %u-bit key\n", + info->crypto->key_len * 8, key_len_bytes * 8); + return -EINVAL; + } + + return 0; +} + +static int do_sign(struct signer *ctx, struct image_sign_info *info, + const struct image_region region[], int region_count) +{ + const struct checksum_algo *algo = info->checksum; + ECDSA_SIG *sig; + + algo->calculate(algo->name, region, region_count, ctx->hash); + sig = ECDSA_do_sign(ctx->hash, algo->checksum_len, ctx->ecdsa_key); + + ecdsa_sig_encode_raw(ctx->signature, sig, info->crypto->key_len); + + return 0; +} + +static int ecdsa_check_signature(struct signer *ctx, struct image_sign_info *info) +{ + ECDSA_SIG *sig; + int okay; + + sig = ecdsa_sig_from_raw(ctx->signature, info->crypto->key_len); + if (!sig) + return -ENOMEM; + + okay = ECDSA_do_verify(ctx->hash, info->checksum->checksum_len, + sig, ctx->ecdsa_key); + if (!okay) + fprintf(stderr, "WARNING: Signature is fake news!\n"); + + ECDSA_SIG_free(sig); + return !okay; +} + +static int do_verify(struct signer *ctx, struct image_sign_info *info, + const struct image_region region[], int region_count, + uint8_t *raw_sig, uint sig_len) +{ + const struct checksum_algo *algo = info->checksum; + + if (sig_len != info->crypto->key_len * 2) { + fprintf(stderr, "Signature has wrong length\n"); + return -EINVAL; + } + + memcpy(ctx->signature, raw_sig, sig_len); + algo->calculate(algo->name, region, region_count, ctx->hash); + + return ecdsa_check_signature(ctx, info); +} + +int ecdsa_sign(struct image_sign_info *info, const struct image_region region[], + int region_count, uint8_t **sigp, uint *sig_len) +{ + struct signer ctx; + int ret; + + ret = prepare_ctx(&ctx, info); + if (ret >= 0) { + do_sign(&ctx, info, region, region_count); + *sigp = ctx.signature; + *sig_len = info->crypto->key_len * 2; + + ret = ecdsa_check_signature(&ctx, info); + } + + free_ctx(&ctx); + return ret; +} + +int ecdsa_verify(struct image_sign_info *info, + const struct image_region region[], int region_count, + uint8_t *sig, uint sig_len) +{ + struct signer ctx; + int ret; + + ret = prepare_ctx(&ctx, info); + if (ret >= 0) + ret = do_verify(&ctx, info, region, region_count, sig, sig_len); + + free_ctx(&ctx); + return ret; +} + +static int do_add(struct signer *ctx, void *fdt, const char *key_node_name) +{ + int signature_node, key_node, ret, key_bits; + const char *curve_name; + const EC_GROUP *group; + const EC_POINT *point; + BIGNUM *x, *y; + + signature_node = fdt_subnode_offset(fdt, 0, FIT_SIG_NODENAME); + if (signature_node < 0) { + fprintf(stderr, "Could not find 'signature node: %s\n", + fdt_strerror(signature_node)); + return signature_node; + } + + key_node = fdt_add_subnode(fdt, signature_node, key_node_name); + if (key_node < 0) { + fprintf(stderr, "Could not create '%s' node: %s\n", + key_node_name, fdt_strerror(key_node)); + return key_node; + } + + group = EC_KEY_get0_group(ctx->ecdsa_key); + key_bits = EC_GROUP_order_bits(group); + curve_name = OBJ_nid2sn(EC_GROUP_get_curve_name(group)); + /* Let 'x' and 'y' memory leak by not BN_free()'ing them. */ + x = BN_new(); + y = BN_new(); + point = EC_KEY_get0_public_key(ctx->ecdsa_key); + EC_POINT_get_affine_coordinates(group, point, x, y, NULL); + + ret = fdt_setprop_string(fdt, key_node, "ecdsa,curve", curve_name); + if (ret < 0) + return ret; + + ret = fdt_add_bignum(fdt, key_node, "ecdsa,x-point", x, key_bits); + if (ret < 0) + return ret; + + ret = fdt_add_bignum(fdt, key_node, "ecdsa,y-point", y, key_bits); + if (ret < 0) + return ret; + + return 0; +} + +int ecdsa_add_verify_data(struct image_sign_info *info, void *fdt) +{ + const char *fdt_key_name; + struct signer ctx; + int ret; + + fdt_key_name = info->keyname ? info->keyname : "default-key"; + ret = prepare_ctx(&ctx, info); + if (ret >= 0) + do_add(&ctx, fdt, fdt_key_name); + + free_ctx(&ctx); + return ret; +} diff --git a/lib/fdt-libcrypto.c b/lib/fdt-libcrypto.c new file mode 100644 index 0000000..ecb0344 --- /dev/null +++ b/lib/fdt-libcrypto.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2020, Alexandru Gagniuc <mr.nuke.me@gmail.com> + * Copyright (c) 2013, Google Inc. + */ + +#include <libfdt.h> +#include <u-boot/fdt-libcrypto.h> + +int fdt_add_bignum(void *blob, int noffset, const char *prop_name, + BIGNUM *num, int num_bits) +{ + int nwords = num_bits / 32; + int size; + uint32_t *buf, *ptr; + BIGNUM *tmp, *big2, *big32, *big2_32; + BN_CTX *ctx; + int ret; + + tmp = BN_new(); + big2 = BN_new(); + big32 = BN_new(); + big2_32 = BN_new(); + + /* + * Note: This code assumes that all of the above succeed, or all fail. + * In practice memory allocations generally do not fail (unless the + * process is killed), so it does not seem worth handling each of these + * as a separate case. Technicaly this could leak memory on failure, + * but a) it won't happen in practice, and b) it doesn't matter as we + * will immediately exit with a failure code. + */ + if (!tmp || !big2 || !big32 || !big2_32) { + fprintf(stderr, "Out of memory (bignum)\n"); + return -ENOMEM; + } + ctx = BN_CTX_new(); + if (!ctx) { + fprintf(stderr, "Out of memory (bignum context)\n"); + return -ENOMEM; + } + BN_set_word(big2, 2L); + BN_set_word(big32, 32L); + BN_exp(big2_32, big2, big32, ctx); /* B = 2^32 */ + + size = nwords * sizeof(uint32_t); + buf = malloc(size); + if (!buf) { + fprintf(stderr, "Out of memory (%d bytes)\n", size); + return -ENOMEM; + } + + /* Write out modulus as big endian array of integers */ + for (ptr = buf + nwords - 1; ptr >= buf; ptr--) { + BN_mod(tmp, num, big2_32, ctx); /* n = N mod B */ + *ptr = cpu_to_fdt32(BN_get_word(tmp)); + BN_rshift(num, num, 32); /* N = N/B */ + } + + /* + * We try signing with successively increasing size values, so this + * might fail several times + */ + ret = fdt_setprop(blob, noffset, prop_name, buf, size); + free(buf); + BN_free(tmp); + BN_free(big2); + BN_free(big32); + BN_free(big2_32); + + return ret ? -FDT_ERR_NOSPACE : 0; +} diff --git a/lib/rsa/rsa-checksum.c b/lib/hash-checksum.c index e60debb..d732ecc 100644 --- a/lib/rsa/rsa-checksum.c +++ b/lib/hash-checksum.c @@ -13,7 +13,8 @@ #else #include "fdt_host.h" #endif -#include <u-boot/rsa.h> +#include <hash.h> +#include <image.h> int hash_calculate(const char *name, const struct image_region region[], diff --git a/lib/rsa/Makefile b/lib/rsa/Makefile index 8b75d41..c9ac72c 100644 --- a/lib/rsa/Makefile +++ b/lib/rsa/Makefile @@ -5,6 +5,6 @@ # (C) Copyright 2000-2007 # Wolfgang Denk, DENX Software Engineering, wd@denx.de. -obj-$(CONFIG_$(SPL_TPL_)RSA_VERIFY) += rsa-verify.o rsa-checksum.o +obj-$(CONFIG_$(SPL_TPL_)RSA_VERIFY) += rsa-verify.o obj-$(CONFIG_$(SPL_TPL_)RSA_VERIFY_WITH_PKEY) += rsa-keyprop.o obj-$(CONFIG_RSA_SOFTWARE_EXP) += rsa-mod-exp.o diff --git a/lib/rsa/rsa-sign.c b/lib/rsa/rsa-sign.c index 1f0d81b..5a1583b 100644 --- a/lib/rsa/rsa-sign.c +++ b/lib/rsa/rsa-sign.c @@ -9,7 +9,9 @@ #include <string.h> #include <image.h> #include <time.h> +#include <u-boot/fdt-libcrypto.h> #include <openssl/bn.h> +#include <openssl/ec.h> #include <openssl/rsa.h> #include <openssl/pem.h> #include <openssl/err.h> @@ -51,19 +53,21 @@ static int rsa_err(const char *msg) * * @keydir: Directory containins the key * @name Name of key file (will have a .crt extension) - * @rsap Returns RSA object, or NULL on failure - * @return 0 if ok, -ve on error (in which case *rsap will be set to NULL) + * @evpp Returns EVP_PKEY object, or NULL on failure + * @return 0 if ok, -ve on error (in which case *evpp will be set to NULL) */ -static int rsa_pem_get_pub_key(const char *keydir, const char *name, RSA **rsap) +static int rsa_pem_get_pub_key(const char *keydir, const char *name, EVP_PKEY **evpp) { char path[1024]; - EVP_PKEY *key; + EVP_PKEY *key = NULL; X509 *cert; - RSA *rsa; FILE *f; int ret; - *rsap = NULL; + if (!evpp) + return -EINVAL; + + *evpp = NULL; snprintf(path, sizeof(path), "%s/%s.crt", keydir, name); f = fopen(path, "r"); if (!f) { @@ -88,22 +92,12 @@ static int rsa_pem_get_pub_key(const char *keydir, const char *name, RSA **rsap) goto err_pubkey; } - /* Convert to a RSA_style key. */ - rsa = EVP_PKEY_get1_RSA(key); - if (!rsa) { - rsa_err("Couldn't convert to a RSA style key"); - ret = -EINVAL; - goto err_rsa; - } fclose(f); - EVP_PKEY_free(key); + *evpp = key; X509_free(cert); - *rsap = rsa; return 0; -err_rsa: - EVP_PKEY_free(key); err_pubkey: X509_free(cert); err_cert: @@ -117,19 +111,20 @@ err_cert: * @keydir: Key prefix * @name Name of key * @engine Engine to use - * @rsap Returns RSA object, or NULL on failure - * @return 0 if ok, -ve on error (in which case *rsap will be set to NULL) + * @evpp Returns EVP_PKEY object, or NULL on failure + * @return 0 if ok, -ve on error (in which case *evpp will be set to NULL) */ static int rsa_engine_get_pub_key(const char *keydir, const char *name, - ENGINE *engine, RSA **rsap) + ENGINE *engine, EVP_PKEY **evpp) { const char *engine_id; char key_id[1024]; - EVP_PKEY *key; - RSA *rsa; - int ret; + EVP_PKEY *key = NULL; + + if (!evpp) + return -EINVAL; - *rsap = NULL; + *evpp = NULL; engine_id = ENGINE_get_id(engine); @@ -165,22 +160,9 @@ static int rsa_engine_get_pub_key(const char *keydir, const char *name, if (!key) return rsa_err("Failure loading public key from engine"); - /* Convert to a RSA_style key. */ - rsa = EVP_PKEY_get1_RSA(key); - if (!rsa) { - rsa_err("Couldn't convert to a RSA style key"); - ret = -EINVAL; - goto err_rsa; - } - - EVP_PKEY_free(key); - *rsap = rsa; + *evpp = key; return 0; - -err_rsa: - EVP_PKEY_free(key); - return ret; } /** @@ -189,15 +171,15 @@ err_rsa: * @keydir: Directory containing the key (PEM file) or key prefix (engine) * @name Name of key file (will have a .crt extension) * @engine Engine to use - * @rsap Returns RSA object, or NULL on failure - * @return 0 if ok, -ve on error (in which case *rsap will be set to NULL) + * @evpp Returns EVP_PKEY object, or NULL on failure + * @return 0 if ok, -ve on error (in which case *evpp will be set to NULL) */ static int rsa_get_pub_key(const char *keydir, const char *name, - ENGINE *engine, RSA **rsap) + ENGINE *engine, EVP_PKEY **evpp) { if (engine) - return rsa_engine_get_pub_key(keydir, name, engine, rsap); - return rsa_pem_get_pub_key(keydir, name, rsap); + return rsa_engine_get_pub_key(keydir, name, engine, evpp); + return rsa_pem_get_pub_key(keydir, name, evpp); } /** @@ -205,18 +187,26 @@ static int rsa_get_pub_key(const char *keydir, const char *name, * * @keydir: Directory containing the key * @name Name of key file (will have a .key extension) - * @rsap Returns RSA object, or NULL on failure - * @return 0 if ok, -ve on error (in which case *rsap will be set to NULL) + * @evpp Returns EVP_PKEY object, or NULL on failure + * @return 0 if ok, -ve on error (in which case *evpp will be set to NULL) */ static int rsa_pem_get_priv_key(const char *keydir, const char *name, - RSA **rsap) + const char *keyfile, EVP_PKEY **evpp) { - char path[1024]; - RSA *rsa; - FILE *f; + char path[1024] = {0}; + FILE *f = NULL; + + if (!evpp) + return -EINVAL; + + *evpp = NULL; + if (keydir && name) + snprintf(path, sizeof(path), "%s/%s.key", keydir, name); + else if (keyfile) + snprintf(path, sizeof(path), "%s", keyfile); + else + return -EINVAL; - *rsap = NULL; - snprintf(path, sizeof(path), "%s/%s.key", keydir, name); f = fopen(path, "r"); if (!f) { fprintf(stderr, "Couldn't open RSA private key: '%s': %s\n", @@ -224,14 +214,12 @@ static int rsa_pem_get_priv_key(const char *keydir, const char *name, return -ENOENT; } - rsa = PEM_read_RSAPrivateKey(f, 0, NULL, path); - if (!rsa) { + if (!PEM_read_PrivateKey(f, evpp, NULL, path)) { rsa_err("Failure reading private key"); fclose(f); return -EPROTO; } fclose(f); - *rsap = rsa; return 0; } @@ -242,23 +230,27 @@ static int rsa_pem_get_priv_key(const char *keydir, const char *name, * @keydir: Key prefix * @name Name of key * @engine Engine to use - * @rsap Returns RSA object, or NULL on failure - * @return 0 if ok, -ve on error (in which case *rsap will be set to NULL) + * @evpp Returns EVP_PKEY object, or NULL on failure + * @return 0 if ok, -ve on error (in which case *evpp will be set to NULL) */ static int rsa_engine_get_priv_key(const char *keydir, const char *name, - ENGINE *engine, RSA **rsap) + const char *keyfile, + ENGINE *engine, EVP_PKEY **evpp) { const char *engine_id; char key_id[1024]; - EVP_PKEY *key; - RSA *rsa; - int ret; + EVP_PKEY *key = NULL; - *rsap = NULL; + if (!evpp) + return -EINVAL; engine_id = ENGINE_get_id(engine); if (engine_id && !strcmp(engine_id, "pkcs11")) { + if (!keydir && !name) { + fprintf(stderr, "Please use 'keydir' with PKCS11\n"); + return -EINVAL; + } if (keydir) if (strstr(keydir, "object=")) snprintf(key_id, sizeof(key_id), @@ -273,14 +265,19 @@ static int rsa_engine_get_priv_key(const char *keydir, const char *name, "pkcs11:object=%s;type=private", name); } else if (engine_id) { - if (keydir) + if (keydir && name) snprintf(key_id, sizeof(key_id), "%s%s", keydir, name); - else + else if (keydir) snprintf(key_id, sizeof(key_id), "%s", name); + else if (keyfile) + snprintf(key_id, sizeof(key_id), "%s", keyfile); + else + return -EINVAL; + } else { fprintf(stderr, "Engine not supported\n"); return -ENOTSUP; @@ -290,22 +287,9 @@ static int rsa_engine_get_priv_key(const char *keydir, const char *name, if (!key) return rsa_err("Failure loading private key from engine"); - /* Convert to a RSA_style key. */ - rsa = EVP_PKEY_get1_RSA(key); - if (!rsa) { - rsa_err("Couldn't convert to a RSA style key"); - ret = -EINVAL; - goto err_rsa; - } - - EVP_PKEY_free(key); - *rsap = rsa; + *evpp = key; return 0; - -err_rsa: - EVP_PKEY_free(key); - return ret; } /** @@ -314,15 +298,16 @@ err_rsa: * @keydir: Directory containing the key (PEM file) or key prefix (engine) * @name Name of key * @engine Engine to use for signing - * @rsap Returns RSA object, or NULL on failure - * @return 0 if ok, -ve on error (in which case *rsap will be set to NULL) + * @evpp Returns EVP_PKEY object, or NULL on failure + * @return 0 if ok, -ve on error (in which case *evpp will be set to NULL) */ static int rsa_get_priv_key(const char *keydir, const char *name, - ENGINE *engine, RSA **rsap) + const char *keyfile, ENGINE *engine, EVP_PKEY **evpp) { if (engine) - return rsa_engine_get_priv_key(keydir, name, engine, rsap); - return rsa_pem_get_priv_key(keydir, name, rsap); + return rsa_engine_get_priv_key(keydir, name, keyfile, engine, + evpp); + return rsa_pem_get_priv_key(keydir, name, keyfile, evpp); } static int rsa_init(void) @@ -416,12 +401,11 @@ static void rsa_engine_remove(ENGINE *e) } } -static int rsa_sign_with_key(RSA *rsa, struct padding_algo *padding_algo, +static int rsa_sign_with_key(EVP_PKEY *pkey, struct padding_algo *padding_algo, struct checksum_algo *checksum_algo, const struct image_region region[], int region_count, uint8_t **sigp, uint *sig_size) { - EVP_PKEY *key; EVP_PKEY_CTX *ckey; EVP_MD_CTX *context; int ret = 0; @@ -429,16 +413,7 @@ static int rsa_sign_with_key(RSA *rsa, struct padding_algo *padding_algo, uint8_t *sig; int i; - key = EVP_PKEY_new(); - if (!key) - return rsa_err("EVP_PKEY object creation failed"); - - if (!EVP_PKEY_set1_RSA(key, rsa)) { - ret = rsa_err("EVP key setup failed"); - goto err_set; - } - - size = EVP_PKEY_size(key); + size = EVP_PKEY_size(pkey); sig = malloc(size); if (!sig) { fprintf(stderr, "Out of memory for signature (%zu bytes)\n", @@ -454,7 +429,7 @@ static int rsa_sign_with_key(RSA *rsa, struct padding_algo *padding_algo, } EVP_MD_CTX_init(context); - ckey = EVP_PKEY_CTX_new(key, NULL); + ckey = EVP_PKEY_CTX_new(pkey, NULL); if (!ckey) { ret = rsa_err("EVP key context creation failed"); goto err_create; @@ -462,7 +437,7 @@ static int rsa_sign_with_key(RSA *rsa, struct padding_algo *padding_algo, if (EVP_DigestSignInit(context, &ckey, checksum_algo->calculate_sign(), - NULL, key) <= 0) { + NULL, pkey) <= 0) { ret = rsa_err("Signer setup failed"); goto err_sign; } @@ -497,7 +472,6 @@ static int rsa_sign_with_key(RSA *rsa, struct padding_algo *padding_algo, EVP_MD_CTX_reset(context); #endif EVP_MD_CTX_destroy(context); - EVP_PKEY_free(key); debug("Got signature: %d bytes, expected %zu\n", *sig_size, size); *sigp = sig; @@ -510,8 +484,6 @@ err_sign: err_create: free(sig); err_alloc: -err_set: - EVP_PKEY_free(key); return ret; } @@ -519,7 +491,7 @@ int rsa_sign(struct image_sign_info *info, const struct image_region region[], int region_count, uint8_t **sigp, uint *sig_len) { - RSA *rsa; + EVP_PKEY *pkey = NULL; ENGINE *e = NULL; int ret; @@ -533,15 +505,16 @@ int rsa_sign(struct image_sign_info *info, goto err_engine; } - ret = rsa_get_priv_key(info->keydir, info->keyname, e, &rsa); + ret = rsa_get_priv_key(info->keydir, info->keyname, info->keyfile, + e, &pkey); if (ret) goto err_priv; - ret = rsa_sign_with_key(rsa, info->padding, info->checksum, region, + ret = rsa_sign_with_key(pkey, info->padding, info->checksum, region, region_count, sigp, sig_len); if (ret) goto err_sign; - RSA_free(rsa); + EVP_PKEY_free(pkey); if (info->engine_id) rsa_engine_remove(e); rsa_remove(); @@ -549,7 +522,7 @@ int rsa_sign(struct image_sign_info *info, return ret; err_sign: - RSA_free(rsa); + EVP_PKEY_free(pkey); err_priv: if (info->engine_id) rsa_engine_remove(e); @@ -680,70 +653,6 @@ int rsa_get_params(RSA *key, uint64_t *exponent, uint32_t *n0_invp, return ret; } -static int fdt_add_bignum(void *blob, int noffset, const char *prop_name, - BIGNUM *num, int num_bits) -{ - int nwords = num_bits / 32; - int size; - uint32_t *buf, *ptr; - BIGNUM *tmp, *big2, *big32, *big2_32; - BN_CTX *ctx; - int ret; - - tmp = BN_new(); - big2 = BN_new(); - big32 = BN_new(); - big2_32 = BN_new(); - - /* - * Note: This code assumes that all of the above succeed, or all fail. - * In practice memory allocations generally do not fail (unless the - * process is killed), so it does not seem worth handling each of these - * as a separate case. Technicaly this could leak memory on failure, - * but a) it won't happen in practice, and b) it doesn't matter as we - * will immediately exit with a failure code. - */ - if (!tmp || !big2 || !big32 || !big2_32) { - fprintf(stderr, "Out of memory (bignum)\n"); - return -ENOMEM; - } - ctx = BN_CTX_new(); - if (!ctx) { - fprintf(stderr, "Out of memory (bignum context)\n"); - return -ENOMEM; - } - BN_set_word(big2, 2L); - BN_set_word(big32, 32L); - BN_exp(big2_32, big2, big32, ctx); /* B = 2^32 */ - - size = nwords * sizeof(uint32_t); - buf = malloc(size); - if (!buf) { - fprintf(stderr, "Out of memory (%d bytes)\n", size); - return -ENOMEM; - } - - /* Write out modulus as big endian array of integers */ - for (ptr = buf + nwords - 1; ptr >= buf; ptr--) { - BN_mod(tmp, num, big2_32, ctx); /* n = N mod B */ - *ptr = cpu_to_fdt32(BN_get_word(tmp)); - BN_rshift(num, num, 32); /* N = N/B */ - } - - /* - * We try signing with successively increasing size values, so this - * might fail several times - */ - ret = fdt_setprop(blob, noffset, prop_name, buf, size); - free(buf); - BN_free(tmp); - BN_free(big2); - BN_free(big32); - BN_free(big2_32); - - return ret ? -FDT_ERR_NOSPACE : 0; -} - int rsa_add_verify_data(struct image_sign_info *info, void *keydest) { BIGNUM *modulus, *r_squared; @@ -754,6 +663,7 @@ int rsa_add_verify_data(struct image_sign_info *info, void *keydest) int ret; int bits; RSA *rsa; + EVP_PKEY *pkey = NULL; ENGINE *e = NULL; debug("%s: Getting verification data\n", __func__); @@ -762,9 +672,15 @@ int rsa_add_verify_data(struct image_sign_info *info, void *keydest) if (ret) return ret; } - ret = rsa_get_pub_key(info->keydir, info->keyname, e, &rsa); + ret = rsa_get_pub_key(info->keydir, info->keyname, e, &pkey); if (ret) goto err_get_pub_key; +#if OPENSSL_VERSION_NUMBER < 0x10100000L || \ + (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x02070000fL) + rsa = EVP_PKEY_get1_RSA(pkey); +#else + rsa = EVP_PKEY_get0_RSA(pkey); +#endif ret = rsa_get_params(rsa, &exponent, &n0_inv, &modulus, &r_squared); if (ret) goto err_get_params; @@ -834,7 +750,11 @@ done: if (ret) ret = ret == -FDT_ERR_NOSPACE ? -ENOSPC : -EIO; err_get_params: +#if OPENSSL_VERSION_NUMBER < 0x10100000L || \ + (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x02070000fL) RSA_free(rsa); +#endif + EVP_PKEY_free(pkey); err_get_pub_key: if (info->engine_id) rsa_engine_remove(e); diff --git a/test/py/requirements.txt b/test/py/requirements.txt index 89ca259..9c346b4 100644 --- a/test/py/requirements.txt +++ b/test/py/requirements.txt @@ -10,6 +10,7 @@ packaging==19.2 pbr==5.4.3 pluggy==0.13.0 py==1.8.0 +pycryptodomex==3.9.8 pyelftools==0.27 pygit2==0.28.2 pyparsing==2.4.2 diff --git a/test/py/tests/test_fit_ecdsa.py b/test/py/tests/test_fit_ecdsa.py new file mode 100644 index 0000000..87b6081 --- /dev/null +++ b/test/py/tests/test_fit_ecdsa.py @@ -0,0 +1,111 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright (c) 2020,2021 Alexandru Gagniuc <mr.nuke.me@gmail.com> + +""" +Test ECDSA signing of FIT images + +This test uses mkimage to sign an existing FIT image with an ECDSA key. The +signature is then extracted, and verified against pyCryptodome. +This test doesn't run the sandbox. It only checks the host tool 'mkimage' +""" + +import pytest +import u_boot_utils as util +from Cryptodome.Hash import SHA256 +from Cryptodome.PublicKey import ECC +from Cryptodome.Signature import DSS + +class SignableFitImage(object): + """ Helper to manipulate a FIT image on disk """ + def __init__(self, cons, file_name): + self.fit = file_name + self.cons = cons + self.signable_nodes = set() + + def __fdt_list(self, path): + return util.run_and_log(self.cons, f'fdtget -l {self.fit} {path}') + + def __fdt_set(self, node, **prop_value): + for prop, value in prop_value.items(): + util.run_and_log(self.cons, f'fdtput -ts {self.fit} {node} {prop} {value}') + + def __fdt_get_binary(self, node, prop): + numbers = util.run_and_log(self.cons, f'fdtget -tbi {self.fit} {node} {prop}') + + bignum = bytearray() + for little_num in numbers.split(): + bignum.append(int(little_num)) + + return bignum + + def find_signable_image_nodes(self): + for node in self.__fdt_list('/images').split(): + image = f'/images/{node}' + if 'signature' in self.__fdt_list(image): + self.signable_nodes.add(image) + + return self.signable_nodes + + def change_signature_algo_to_ecdsa(self): + for image in self.signable_nodes: + self.__fdt_set(f'{image}/signature', algo='sha256,ecdsa256') + + def sign(self, mkimage, key_file): + util.run_and_log(self.cons, [mkimage, '-F', self.fit, f'-G{key_file}']) + + def check_signatures(self, key): + for image in self.signable_nodes: + raw_sig = self.__fdt_get_binary(f'{image}/signature', 'value') + raw_bin = self.__fdt_get_binary(image, 'data') + + sha = SHA256.new(raw_bin) + verifier = DSS.new(key, 'fips-186-3') + verifier.verify(sha, bytes(raw_sig)) + + +@pytest.mark.buildconfigspec('fit_signature') +@pytest.mark.requiredtool('dtc') +@pytest.mark.requiredtool('fdtget') +@pytest.mark.requiredtool('fdtput') +def test_fit_ecdsa(u_boot_console): + """ Test that signatures generated by mkimage are legible. """ + def generate_ecdsa_key(): + return ECC.generate(curve='prime256v1') + + def assemble_fit_image(dest_fit, its, destdir): + dtc_args = f'-I dts -O dtb -i {destdir}' + util.run_and_log(cons, [mkimage, '-D', dtc_args, '-f', its, dest_fit]) + + def dtc(dts): + dtb = dts.replace('.dts', '.dtb') + util.run_and_log(cons, f'dtc {datadir}/{dts} -O dtb -o {tempdir}/{dtb}') + + cons = u_boot_console + mkimage = cons.config.build_dir + '/tools/mkimage' + datadir = cons.config.source_dir + '/test/py/tests/vboot/' + tempdir = cons.config.result_dir + key_file = f'{tempdir}/ecdsa-test-key.pem' + fit_file = f'{tempdir}/test.fit' + dtc('sandbox-kernel.dts') + + key = generate_ecdsa_key() + + # Create a fake kernel image -- zeroes will do just fine + with open(f'{tempdir}/test-kernel.bin', 'w') as fd: + fd.write(500 * chr(0)) + + # invocations of mkimage expect to read the key from disk + with open(key_file, 'w') as f: + f.write(key.export_key(format='PEM')) + + assemble_fit_image(fit_file, f'{datadir}/sign-images-sha256.its', tempdir) + + fit = SignableFitImage(cons, fit_file) + nodes = fit.find_signable_image_nodes() + if len(nodes) == 0: + raise ValueError('FIT image has no "/image" nodes with "signature"') + + fit.change_signature_algo_to_ecdsa() + fit.sign(mkimage, key_file) + fit.check_signatures(key) diff --git a/tools/Makefile b/tools/Makefile index 62de7e6..d020c55 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -67,12 +67,18 @@ LIBFDT_OBJS := $(addprefix libfdt/, fdt.o fdt_ro.o fdt_wip.o fdt_sw.o fdt_rw.o \ fdt_strerror.o fdt_empty_tree.o fdt_addresses.o fdt_overlay.o) RSA_OBJS-$(CONFIG_FIT_SIGNATURE) := $(addprefix lib/rsa/, \ - rsa-sign.o rsa-verify.o rsa-checksum.o \ + rsa-sign.o rsa-verify.o \ rsa-mod-exp.o) +ECDSA_OBJS-$(CONFIG_FIT_SIGNATURE) := $(addprefix lib/ecdsa/, ecdsa-libcrypto.o) + AES_OBJS-$(CONFIG_FIT_CIPHER) := $(addprefix lib/aes/, \ aes-encrypt.o aes-decrypt.o) +# Cryptographic helpers that depend on openssl/libcrypto +LIBCRYPTO_OBJS-$(CONFIG_FIT_SIGNATURE) := $(addprefix lib/, \ + fdt-libcrypto.o) + ROCKCHIP_OBS = lib/rc4.o rkcommon.o rkimage.o rksd.o rkspi.o # common objs for dumpimage and mkimage @@ -106,6 +112,7 @@ dumpimage-mkimage-objs := aisimage.o \ socfpgaimage.o \ sunxi_egon.o \ lib/crc16.o \ + lib/hash-checksum.o \ lib/sha1.o \ lib/sha256.o \ lib/sha512.o \ @@ -114,10 +121,12 @@ dumpimage-mkimage-objs := aisimage.o \ zynqimage.o \ zynqmpimage.o \ zynqmpbif.o \ + $(LIBCRYPTO_OBJS-y) \ $(LIBFDT_OBJS) \ gpimage.o \ gpimage-common.o \ mtk_image.o \ + $(ECDSA_OBJS-y) \ $(RSA_OBJS-y) \ $(AES_OBJS-y) diff --git a/tools/fit_image.c b/tools/fit_image.c index d440d14..ae30f80 100644 --- a/tools/fit_image.c +++ b/tools/fit_image.c @@ -68,7 +68,8 @@ static int fit_add_file_data(struct image_tool_params *params, size_t size_inc, } if (!ret) { - ret = fit_add_verification_data(params->keydir, dest_blob, ptr, + ret = fit_add_verification_data(params->keydir, + params->keyfile, dest_blob, ptr, params->comment, params->require_keys, params->engine_id, diff --git a/tools/image-host.c b/tools/image-host.c index 33a22412..270d36f 100644 --- a/tools/image-host.c +++ b/tools/image-host.c @@ -153,8 +153,9 @@ static int fit_image_write_sig(void *fit, int noffset, uint8_t *value, } static int fit_image_setup_sig(struct image_sign_info *info, - const char *keydir, void *fit, const char *image_name, - int noffset, const char *require_keys, const char *engine_id) + const char *keydir, const char *keyfile, void *fit, + const char *image_name, int noffset, const char *require_keys, + const char *engine_id) { const char *node_name; char *algo_name; @@ -171,6 +172,7 @@ static int fit_image_setup_sig(struct image_sign_info *info, memset(info, '\0', sizeof(*info)); info->keydir = keydir; + info->keyfile = keyfile; info->keyname = fdt_getprop(fit, noffset, FIT_KEY_HINT, NULL); info->fit = fit; info->node_offset = noffset; @@ -207,8 +209,8 @@ static int fit_image_setup_sig(struct image_sign_info *info, * @engine_id: Engine to use for signing * @return 0 if ok, -1 on error */ -static int fit_image_process_sig(const char *keydir, void *keydest, - void *fit, const char *image_name, +static int fit_image_process_sig(const char *keydir, const char *keyfile, + void *keydest, void *fit, const char *image_name, int noffset, const void *data, size_t size, const char *comment, int require_keys, const char *engine_id, const char *cmdname) @@ -220,8 +222,9 @@ static int fit_image_process_sig(const char *keydir, void *keydest, uint value_len; int ret; - if (fit_image_setup_sig(&info, keydir, fit, image_name, noffset, - require_keys ? "image" : NULL, engine_id)) + if (fit_image_setup_sig(&info, keydir, keyfile, fit, image_name, + noffset, require_keys ? "image" : NULL, + engine_id)) return -1; node_name = fit_get_name(fit, noffset, NULL); @@ -598,9 +601,10 @@ int fit_image_cipher_data(const char *keydir, void *keydest, * @engine_id: Engine to use for signing * @return: 0 on success, <0 on failure */ -int fit_image_add_verification_data(const char *keydir, void *keydest, - void *fit, int image_noffset, const char *comment, - int require_keys, const char *engine_id, const char *cmdname) +int fit_image_add_verification_data(const char *keydir, const char *keyfile, + void *keydest, void *fit, int image_noffset, + const char *comment, int require_keys, const char *engine_id, + const char *cmdname) { const char *image_name; const void *data; @@ -632,10 +636,10 @@ int fit_image_add_verification_data(const char *keydir, void *keydest, strlen(FIT_HASH_NODENAME))) { ret = fit_image_process_hash(fit, image_name, noffset, data, size); - } else if (IMAGE_ENABLE_SIGN && keydir && + } else if (IMAGE_ENABLE_SIGN && (keydir || keyfile) && !strncmp(node_name, FIT_SIG_NODENAME, strlen(FIT_SIG_NODENAME))) { - ret = fit_image_process_sig(keydir, keydest, + ret = fit_image_process_sig(keydir, keyfile, keydest, fit, image_name, noffset, data, size, comment, require_keys, engine_id, cmdname); } @@ -918,10 +922,10 @@ static int fit_config_get_data(void *fit, int conf_noffset, int noffset, return 0; } -static int fit_config_process_sig(const char *keydir, void *keydest, - void *fit, const char *conf_name, int conf_noffset, - int noffset, const char *comment, int require_keys, - const char *engine_id, const char *cmdname) +static int fit_config_process_sig(const char *keydir, const char *keyfile, + void *keydest, void *fit, const char *conf_name, + int conf_noffset, int noffset, const char *comment, + int require_keys, const char *engine_id, const char *cmdname) { struct image_sign_info info; const char *node_name; @@ -938,7 +942,7 @@ static int fit_config_process_sig(const char *keydir, void *keydest, ®ion_count, ®ion_prop, ®ion_proplen)) return -1; - if (fit_image_setup_sig(&info, keydir, fit, conf_name, noffset, + if (fit_image_setup_sig(&info, keydir, keyfile, fit, conf_name, noffset, require_keys ? "conf" : NULL, engine_id)) return -1; @@ -983,9 +987,10 @@ static int fit_config_process_sig(const char *keydir, void *keydest, return 0; } -static int fit_config_add_verification_data(const char *keydir, void *keydest, - void *fit, int conf_noffset, const char *comment, - int require_keys, const char *engine_id, const char *cmdname) +static int fit_config_add_verification_data(const char *keydir, + const char *keyfile, void *keydest, void *fit, int conf_noffset, + const char *comment, int require_keys, const char *engine_id, + const char *cmdname) { const char *conf_name; int noffset; @@ -1002,7 +1007,7 @@ static int fit_config_add_verification_data(const char *keydir, void *keydest, node_name = fit_get_name(fit, noffset, NULL); if (!strncmp(node_name, FIT_SIG_NODENAME, strlen(FIT_SIG_NODENAME))) { - ret = fit_config_process_sig(keydir, keydest, + ret = fit_config_process_sig(keydir, keyfile, keydest, fit, conf_name, conf_noffset, noffset, comment, require_keys, engine_id, cmdname); } @@ -1048,9 +1053,10 @@ int fit_cipher_data(const char *keydir, void *keydest, void *fit, return 0; } -int fit_add_verification_data(const char *keydir, void *keydest, void *fit, - const char *comment, int require_keys, - const char *engine_id, const char *cmdname) +int fit_add_verification_data(const char *keydir, const char *keyfile, + void *keydest, void *fit, const char *comment, + int require_keys, const char *engine_id, + const char *cmdname) { int images_noffset, confs_noffset; int noffset; @@ -1072,7 +1078,7 @@ int fit_add_verification_data(const char *keydir, void *keydest, void *fit, * Direct child node of the images parent node, * i.e. component image node. */ - ret = fit_image_add_verification_data(keydir, keydest, + ret = fit_image_add_verification_data(keydir, keyfile, keydest, fit, noffset, comment, require_keys, engine_id, cmdname); if (ret) @@ -1080,7 +1086,7 @@ int fit_add_verification_data(const char *keydir, void *keydest, void *fit, } /* If there are no keys, we can't sign configurations */ - if (!IMAGE_ENABLE_SIGN || !keydir) + if (!IMAGE_ENABLE_SIGN || !(keydir || keyfile)) return 0; /* Find configurations parent node offset */ @@ -1095,7 +1101,7 @@ int fit_add_verification_data(const char *keydir, void *keydest, void *fit, for (noffset = fdt_first_subnode(fit, confs_noffset); noffset >= 0; noffset = fdt_next_subnode(fit, noffset)) { - ret = fit_config_add_verification_data(keydir, keydest, + ret = fit_config_add_verification_data(keydir, keyfile, keydest, fit, noffset, comment, require_keys, engine_id, cmdname); diff --git a/tools/imagetool.h b/tools/imagetool.h index 2801ea9..e229a34 100644 --- a/tools/imagetool.h +++ b/tools/imagetool.h @@ -67,6 +67,7 @@ struct image_tool_params { const char *outfile; /* Output filename */ const char *keydir; /* Directory holding private keys */ const char *keydest; /* Destination .dtb for public key */ + const char *keyfile; /* Filename of private or public key */ const char *comment; /* Comment to add to signature node */ int require_keys; /* 1 to mark signing keys as 'required' */ int file_size; /* Total size of output file */ diff --git a/tools/mkimage.c b/tools/mkimage.c index 68d5206..cc7b242 100644 --- a/tools/mkimage.c +++ b/tools/mkimage.c @@ -108,6 +108,7 @@ static void usage(const char *msg) "Signing / verified boot options: [-k keydir] [-K dtb] [ -c <comment>] [-p addr] [-r] [-N engine]\n" " -k => set directory containing private keys\n" " -K => write public keys to this .dtb file\n" + " -G => use this signing key (in lieu of -k)\n" " -c => add comment in signature node\n" " -F => re-sign existing FIT image\n" " -p => place external data at a static position\n" @@ -151,7 +152,7 @@ static void process_args(int argc, char **argv) int opt; while ((opt = getopt(argc, argv, - "a:A:b:B:c:C:d:D:e:Ef:Fk:i:K:ln:N:p:O:rR:qstT:vVx")) != -1) { + "a:A:b:B:c:C:d:D:e:Ef:FG:k:i:K:ln:N:p:O:rR:qstT:vVx")) != -1) { switch (opt) { case 'a': params.addr = strtoull(optarg, &ptr, 16); @@ -226,6 +227,9 @@ static void process_args(int argc, char **argv) params.type = IH_TYPE_FLATDT; params.fflag = 1; break; + case 'G': + params.keyfile = optarg; + break; case 'i': params.fit_ramdisk = optarg; break; |