FIT cipher support currently allocates the output buffer inside the AES helper. SPL often needs to decrypt directly into a caller-selected buffer, for example a load buffer or a scratch buffer used before decompression.
Add a decrypt_to callback to the FIT cipher algorithm and wire it up for AES. The existing allocating decrypt path becomes a wrapper around the new helper. Validate the FIT cipher key length, IV length and unciphered-size property while preparing decryption, and build lib/aes/ by phase when FIT_CIPHER is enabled so the target-side decrypt helper is available to SPL builds. When DM_AES is enabled, try the first UCLASS_AES provider before using the software AES fallback in U-Boot proper. If a provider is present, ask the provider for the software-provided key slot before loading the FIT key, so hardware-specific reserved or preloaded slot policy stays in the provider. Return AES device errors to the caller instead of hiding them with a software fallback after the device has accepted the operation. For U-Boot proper, use decrypt_to for in-place decryption when the FIT payload is already in writable RAM. The encrypted data is no longer needed after hash verification, and this avoids a full-size allocation for encrypted payloads loaded into DRAM. Signed-off-by: James Hilliard <[email protected]> Reviewed-by: Simon Glass <[email protected]> --- Changes v2 -> v3: - Flip the image_aes_decrypt_to() host-tool guard (suggested by Simon Glass) - Let image_aes_decrypt_to() be the single length-validation path (suggested by Simon Glass) - Document that AES CBC decrypt providers must support in-place decrypt (suggested by Simon Glass) Changes v1 -> v2: - Explain FIT cipher validation (suggested by Simon Glass) - Explain phase-keyed lib/aes builds (suggested by Simon Glass) - Return -ENOSYS without decrypt support (suggested by Simon Glass) - Let AES providers choose the software-provided key slot - Use decrypt_to for U-Boot proper in-place decrypt --- boot/image-cipher.c | 38 ++++++++++++-- boot/image-fit.c | 33 +++++++++++-- drivers/crypto/aes/aes-sw.c | 6 +++ drivers/crypto/aes/aes-uclass.c | 15 ++++++ drivers/crypto/tegra/tegra_aes.c | 7 +++ include/image.h | 39 +++++++++++++-- include/u-boot/aes.h | 10 ++++ include/uboot_aes.h | 40 +++++++++++++-- lib/Makefile | 2 +- lib/aes/aes-decrypt.c | 104 +++++++++++++++++++++++++++++++++------ 10 files changed, 264 insertions(+), 30 deletions(-) diff --git a/boot/image-cipher.c b/boot/image-cipher.c index 9d389f26cea..93b3dd3c637 100644 --- a/boot/image-cipher.c +++ b/boot/image-cipher.c @@ -25,6 +25,7 @@ struct cipher_algo cipher_algos[] = { #endif .encrypt = image_aes_encrypt, .decrypt = image_aes_decrypt, + .decrypt_to = image_aes_decrypt_to, .add_cipher_data = image_aes_add_cipher_data }, { @@ -36,6 +37,7 @@ struct cipher_algo cipher_algos[] = { #endif .encrypt = image_aes_encrypt, .decrypt = image_aes_decrypt, + .decrypt_to = image_aes_decrypt_to, .add_cipher_data = image_aes_add_cipher_data }, { @@ -47,6 +49,7 @@ struct cipher_algo cipher_algos[] = { #endif .encrypt = image_aes_encrypt, .decrypt = image_aes_decrypt, + .decrypt_to = image_aes_decrypt_to, .add_cipher_data = image_aes_add_cipher_data } }; @@ -70,6 +73,7 @@ static int fit_image_setup_decrypt(struct image_cipher_info *info, int cipher_noffset) { const void *fdt = gd_fdt_blob(); + int key_len, iv_len; const char *node_name; char node_path[128]; int noffset; @@ -94,7 +98,7 @@ static int fit_image_setup_decrypt(struct image_cipher_info *info, return -1; } - info->iv = fdt_getprop(fit, cipher_noffset, "iv", NULL); + info->iv = fdt_getprop(fit, cipher_noffset, "iv", &iv_len); info->ivname = fdt_getprop(fit, cipher_noffset, "iv-name-hint", NULL); if (!info->iv && !info->ivname) { @@ -136,20 +140,28 @@ static int fit_image_setup_decrypt(struct image_cipher_info *info, } /* read key */ - info->key = fdt_getprop(fdt, noffset, "key", NULL); + info->key = fdt_getprop(fdt, noffset, "key", &key_len); if (!info->key) { printf("Can't get key in cipher node '%s'\n", node_path); return -1; } + if (key_len != info->cipher->key_len) { + printf("Bad key length in cipher node '%s'\n", node_path); + return -1; + } /* read iv */ if (!info->iv) { - info->iv = fdt_getprop(fdt, noffset, "iv", NULL); + info->iv = fdt_getprop(fdt, noffset, "iv", &iv_len); if (!info->iv) { printf("Can't get IV in cipher node '%s'\n", node_path); return -1; } } + if (iv_len != info->cipher->iv_len) { + printf("Bad IV length for cipher in image '%s'\n", node_name); + return -1; + } return 0; } @@ -173,3 +185,23 @@ int fit_image_decrypt_data(const void *fit, out: return ret; } + +int fit_image_decrypt_data_to(const void *fit, + int image_noffset, int cipher_noffset, + const void *data_ciphered, size_t size_ciphered, + void *data_unciphered, size_t *size_unciphered) +{ + struct image_cipher_info info; + int ret; + + ret = fit_image_setup_decrypt(&info, fit, image_noffset, + cipher_noffset); + if (ret < 0) + return ret; + + if (!info.cipher->decrypt_to) + return -ENOSYS; + + return info.cipher->decrypt_to(&info, data_ciphered, size_ciphered, + data_unciphered, size_unciphered); +} diff --git a/boot/image-fit.c b/boot/image-fit.c index 044a40e1910..a4192db5eb5 100644 --- a/boot/image-fit.c +++ b/boot/image-fit.c @@ -1030,7 +1030,7 @@ int fit_image_get_data_size(const void *fit, int noffset, int *data_size) * * @fit: pointer to the FIT image header * @noffset: component image node offset - * @data_size: holds the data-size property + * @data_size: holds the data-size-unciphered property * * returns: * 0, on success @@ -1040,10 +1040,13 @@ int fit_image_get_data_size_unciphered(const void *fit, int noffset, size_t *data_size) { const fdt32_t *val; + int len; - val = fdt_getprop(fit, noffset, "data-size-unciphered", NULL); + val = fdt_getprop(fit, noffset, "data-size-unciphered", &len); if (!val) return -ENOENT; + if (len != sizeof(*val)) + return -EINVAL; *data_size = (size_t)fdt32_to_cpu(*val); @@ -1568,15 +1571,37 @@ static int fit_image_uncipher(const void *fit, int image_noffset, if (cipher_noffset < 0) return 0; +#ifndef USE_HOSTCC + if (!tools_build()) { + ulong start = map_to_sysmem(*data); + ulong end = start + *size; + + /* + * Avoid a full-size allocation when the FIT payload is already + * in writable DRAM. The encrypted bytes are no longer needed + * after hash verification has completed. + */ + if (end >= start && start >= gd->ram_base && end <= gd->ram_top) { + ret = fit_image_decrypt_data_to(fit, image_noffset, + cipher_noffset, + *data, *size, *data, + &size_dst); + if (ret != -ENOSYS) + goto out; + } + } +#endif + ret = fit_image_decrypt_data(fit, image_noffset, cipher_noffset, *data, *size, &dst, &size_dst); if (ret) goto out; *data = dst; - *size = size_dst; +out: + if (!ret) + *size = size_dst; - out: return ret; } diff --git a/drivers/crypto/aes/aes-sw.c b/drivers/crypto/aes/aes-sw.c index a65200fb79b..3ce8999663b 100644 --- a/drivers/crypto/aes/aes-sw.c +++ b/drivers/crypto/aes/aes-sw.c @@ -39,6 +39,11 @@ static int sw_aes_ops_available_key_slots(struct udevice *dev) return SW_KEY_SLOTS; } +static int sw_aes_ops_get_software_key_slot(struct udevice *dev) +{ + return 0; +} + static int sw_aes_ops_select_key_slot(struct udevice *dev, u32 key_size, u8 slot) { struct sw_aes_priv *priv = dev_get_priv(dev); @@ -145,6 +150,7 @@ static int sw_aes_ops_aes_cbc_decrypt(struct udevice *dev, u8 *iv, u8 *src, static const struct aes_ops aes_ops_sw = { .available_key_slots = sw_aes_ops_available_key_slots, + .get_software_key_slot = sw_aes_ops_get_software_key_slot, .select_key_slot = sw_aes_ops_select_key_slot, .set_key_for_key_slot = sw_aes_ops_set_key_for_key_slot, .aes_ecb_encrypt = sw_aes_ops_aes_ecb_encrypt, diff --git a/drivers/crypto/aes/aes-uclass.c b/drivers/crypto/aes/aes-uclass.c index 5bdd3d736c4..2bfe3dc3fcc 100644 --- a/drivers/crypto/aes/aes-uclass.c +++ b/drivers/crypto/aes/aes-uclass.c @@ -23,6 +23,21 @@ int dm_aes_get_available_key_slots(struct udevice *dev) return ops->available_key_slots(dev); } +int dm_aes_get_software_key_slot(struct udevice *dev) +{ + const struct aes_ops *ops; + + if (!dev) + return -ENODEV; + + ops = aes_get_ops(dev); + + if (!ops->get_software_key_slot) + return -ENOSYS; + + return ops->get_software_key_slot(dev); +} + int dm_aes_select_key_slot(struct udevice *dev, u32 key_size, u8 slot) { const struct aes_ops *ops; diff --git a/drivers/crypto/tegra/tegra_aes.c b/drivers/crypto/tegra/tegra_aes.c index 55a4cec525b..348115ac5a7 100644 --- a/drivers/crypto/tegra/tegra_aes.c +++ b/drivers/crypto/tegra/tegra_aes.c @@ -321,6 +321,12 @@ static int tegra_aes_ops_available_key_slots(struct udevice *dev) return 4; /* 4 slots in Tegra20 and Tegra30 */ } +static int tegra_aes_ops_get_software_key_slot(struct udevice *dev) +{ + /* Avoid SBK slot 0, which may hold a preloaded key. */ + return TEGRA_AES_SLOT_SBK + 1; +} + static int tegra_aes_ops_select_key_slot(struct udevice *dev, u32 key_size, u8 slot) { struct tegra_aes_priv *priv = dev_get_priv(dev); @@ -565,6 +571,7 @@ static int tegra_aes_probe(struct udevice *dev) static const struct aes_ops tegra_aes_ops = { .available_key_slots = tegra_aes_ops_available_key_slots, + .get_software_key_slot = tegra_aes_ops_get_software_key_slot, .select_key_slot = tegra_aes_ops_select_key_slot, .set_key_for_key_slot = tegra_aes_ops_set_key_for_key_slot, .aes_ecb_encrypt = tegra_aes_ops_aes_ecb_encrypt, diff --git a/include/image.h b/include/image.h index 9c8a746d576..5edbf8fdc33 100644 --- a/include/image.h +++ b/include/image.h @@ -1862,11 +1862,40 @@ int fit_image_check_sig(const void *fit, int noffset, const void *data, size_t size, const void *key_blob, int required_keynode, char **err_msgp); -int fit_image_decrypt_data(const void *fit, - int image_noffset, int cipher_noffset, - const void *data, size_t size, +/** + * fit_image_decrypt_data() - Decrypt a FIT image payload + * + * @fit: FIT image + * @image_noffset: Offset of the image node to decrypt + * @cipher_noffset: Offset of the cipher node for the image + * @data: Encrypted image payload + * @size: Size of encrypted image payload + * @data_unciphered: Returns allocated decrypted payload + * @size_unciphered: Returns size of decrypted payload + * Return: 0 on success, <0 on error + */ +int fit_image_decrypt_data(const void *fit, int image_noffset, + int cipher_noffset, const void *data, size_t size, void **data_unciphered, size_t *size_unciphered); +/** + * fit_image_decrypt_data_to() - Decrypt a FIT image payload to a buffer + * + * @fit: FIT image + * @image_noffset: Offset of the image node to decrypt + * @cipher_noffset: Offset of the cipher node for the image + * @data: Encrypted image payload + * @size: Size of encrypted image payload + * @data_unciphered: Destination buffer for decrypted payload. The caller + * must provide at least @size bytes. + * @size_unciphered: Returns size of decrypted payload + * Return: 0 on success, <0 on error + */ +int fit_image_decrypt_data_to(const void *fit, + int image_noffset, int cipher_noffset, + const void *data, size_t size, + void *data_unciphered, size_t *size_unciphered); + /** * fit_region_make_list() - Make a list of regions to hash * @@ -1960,6 +1989,10 @@ struct cipher_algo { int (*decrypt)(struct image_cipher_info *info, const void *cipher, size_t cipher_len, void **data, size_t *data_len); + + int (*decrypt_to)(struct image_cipher_info *info, + const void *cipher, size_t cipher_len, + void *data, size_t *data_len); }; int fit_image_cipher_get_algo(const void *fit, int noffset, char **algo); diff --git a/include/u-boot/aes.h b/include/u-boot/aes.h index acbc50b9e6f..6d81af3780d 100644 --- a/include/u-boot/aes.h +++ b/include/u-boot/aes.h @@ -34,6 +34,9 @@ int image_aes_add_cipher_data(struct image_cipher_info *info, void *keydest, int image_aes_decrypt(struct image_cipher_info *info, const void *cipher, size_t cipher_len, void **data, size_t *size); +int image_aes_decrypt_to(struct image_cipher_info *info, + const void *cipher, size_t cipher_len, + void *data, size_t *size); #else int image_aes_decrypt(struct image_cipher_info *info, const void *cipher, size_t cipher_len, @@ -41,6 +44,13 @@ int image_aes_decrypt(struct image_cipher_info *info, { return -ENXIO; } + +int image_aes_decrypt_to(struct image_cipher_info *info, + const void *cipher, size_t cipher_len, + void *data, size_t *size) +{ + return -ENXIO; +} #endif /* IMAGE_ENABLE_DECRYPT */ #endif diff --git a/include/uboot_aes.h b/include/uboot_aes.h index 592b7dbee43..92a667badc1 100644 --- a/include/uboot_aes.h +++ b/include/uboot_aes.h @@ -128,11 +128,17 @@ struct udevice; * Note that some devices like Tegra AES engine may contain preloaded keys by bootrom, * thus in those cases the set_key_for_key_slot() may be skipped. * - * Sequence for a series of AES CBC encryption, one decryption and a CMAC hash example - * with 128bits key at slot 0 would be as follow: + * Generic callers which load a software-provided key should first ask the + * driver for a suitable software key slot. This lets hardware drivers avoid + * reserved or preloaded slots while keeping the slot policy local to the + * provider. * - * set_key_for_key_slot(DEV, 128, KEY, 0); - * select_key_slot(DEV, 128, 0); + * Sequence for a series of AES CBC encryption, one decryption and a CMAC hash + * example with a 128-bit software-provided key would be as follows: + * + * slot = get_software_key_slot(DEV); + * set_key_for_key_slot(DEV, 128, KEY, slot); + * select_key_slot(DEV, 128, slot); * aes_cbc_encrypt(DEV, IV1, SRC1, DST1, LEN1); * aes_cbc_encrypt(DEV, IV2, SRC2, DST2, LEN2); * aes_cbc_decrypt(DEV, IV3, SRC3, DST3, LEN3); @@ -146,6 +152,16 @@ struct aes_ops { */ int (*available_key_slots)(struct udevice *dev); + /** + * get_software_key_slot() - Get a slot for software-provided keys + * + * @dev The AES udevice + * @return Key slot to use for a software-provided key, + * 0 or positive on success, negative value on + * failure + */ + int (*get_software_key_slot)(struct udevice *dev); + /** * select_key_slot() - Selects the AES key slot to use for following operations * @@ -210,6 +226,7 @@ struct aes_ops { * @iv Initialization vector * @src Source data of length 'num_aes_blocks' blocks * @dst Destination data of length 'num_aes_blocks' blocks + * Must support dst == src for in-place decrypt * @num_aes_blocks Number of AES blocks to encrypt/decrypt * @return 0 on success, negative value on failure */ @@ -229,6 +246,15 @@ struct aes_ops { */ int dm_aes_get_available_key_slots(struct udevice *dev); +/** + * dm_aes_get_software_key_slot - Get a slot for software-provided keys + * + * @dev The AES udevice + * Return: Key slot to use for a software-provided key, + * 0 or positive on success, -ve on failure + */ +int dm_aes_get_software_key_slot(struct udevice *dev); + /** * dm_aes_select_key_slot - Selects the AES key slot to use for following operations * @@ -291,6 +317,7 @@ int dm_aes_cbc_encrypt(struct udevice *dev, u8 *iv, u8 *src, u8 *dst, u32 num_ae * @iv Initialization vector * @src Source data of length 'num_aes_blocks' blocks * @dst Destination data of length 'num_aes_blocks' blocks + * Must support dst == src for in-place decrypt * @num_aes_blocks Number of AES blocks to encrypt/decrypt * Return: 0 on success, negative value on failure */ @@ -316,6 +343,11 @@ static inline int dm_aes_get_available_key_slots(struct udevice *dev) return -ENOSYS; } +static inline int dm_aes_get_software_key_slot(struct udevice *dev) +{ + return -ENOSYS; +} + static inline int dm_aes_select_key_slot(struct udevice *dev, u32 key_size, u8 slot) { return -ENOSYS; diff --git a/lib/Makefile b/lib/Makefile index d0ffabc2b47..c69a928e6d8 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -19,7 +19,6 @@ obj-$(CONFIG_ARCH_AT91) += at91/ obj-$(CONFIG_OPTEE_LIB) += optee/ obj-$(CONFIG_AES) += aes.o -obj-$(CONFIG_AES) += aes/ obj-$(CONFIG_$(PHASE_)BINMAN_FDT) += binman.o obj-$(CONFIG_FW_LOADER) += fw_loader.o @@ -88,6 +87,7 @@ obj-$(CONFIG_$(PHASE_)ASN1_DECODER_LEGACY) += asn1_decoder.o obj-$(CONFIG_$(PHASE_)ZLIB) += zlib/ obj-$(CONFIG_$(PHASE_)ZSTD) += zstd/ +obj-$(CONFIG_$(PHASE_)FIT_CIPHER) += aes/ obj-$(CONFIG_$(PHASE_)GZIP) += gunzip.o obj-$(CONFIG_$(PHASE_)LZO) += lzo/ obj-$(CONFIG_$(PHASE_)LZMA) += lzma/ diff --git a/lib/aes/aes-decrypt.c b/lib/aes/aes-decrypt.c index 741102a4723..6432320e79b 100644 --- a/lib/aes/aes-decrypt.c +++ b/lib/aes/aes-decrypt.c @@ -4,37 +4,111 @@ */ #ifndef USE_HOSTCC +#include <dm.h> #include <malloc.h> #endif #include <image.h> #include <uboot_aes.h> +#ifndef USE_HOSTCC +static int image_aes_validate_lengths(struct image_cipher_info *info, + size_t cipher_len) +{ + if (!cipher_len || cipher_len % AES_BLOCK_LENGTH || + info->size_unciphered > cipher_len) + return -EINVAL; + + return 0; +} +#endif + +int image_aes_decrypt_to(struct image_cipher_info *info, + const void *cipher, size_t cipher_len, + void *data, size_t *size) +{ +#ifdef USE_HOSTCC + return -ENOSYS; +#else + unsigned int aes_blocks, key_len = info->cipher->key_len; + int ret; + + ret = image_aes_validate_lengths(info, cipher_len); + if (ret) + return ret; + + *size = info->size_unciphered; + + if (CONFIG_IS_ENABLED(DM_AES)) { + struct udevice *dev; + + ret = uclass_first_device_err(UCLASS_AES, &dev); + if (!ret) { + u8 slot; + + ret = dm_aes_get_software_key_slot(dev); + if (ret < 0) + return ret; + if (ret > 0xff) + return -ERANGE; + slot = ret; + + ret = dm_aes_set_key_for_key_slot(dev, key_len * 8, + (u8 *)info->key, + slot); + if (ret) + return ret; + + ret = dm_aes_select_key_slot(dev, key_len * 8, slot); + if (ret) + return ret; + + aes_blocks = cipher_len / AES_BLOCK_LENGTH; + + return dm_aes_cbc_decrypt(dev, (u8 *)info->iv, + (u8 *)cipher, data, + aes_blocks); + } + } + + if (!IS_ENABLED(CONFIG_XPL_BUILD)) { + unsigned char key_exp[AES256_EXPAND_KEY_LENGTH]; + + /* First we expand the key. */ + aes_expand_key((u8 *)info->key, key_len, key_exp); + + /* Calculate the number of AES blocks to decrypt. */ + aes_blocks = cipher_len / AES_BLOCK_LENGTH; + + aes_cbc_decrypt_blocks(key_len, key_exp, (u8 *)info->iv, + (u8 *)cipher, data, aes_blocks); + return 0; + } + + return -ENOSYS; +#endif +} + int image_aes_decrypt(struct image_cipher_info *info, const void *cipher, size_t cipher_len, void **data, size_t *size) { -#ifndef USE_HOSTCC - unsigned char key_exp[AES256_EXPAND_KEY_LENGTH]; - unsigned int aes_blocks, key_len = info->cipher->key_len; +#ifdef USE_HOSTCC + return 0; +#else + int ret; *data = malloc(cipher_len); if (!*data) { printf("Can't allocate memory to decrypt\n"); return -ENOMEM; } - *size = info->size_unciphered; - - memcpy(&key_exp[0], info->key, key_len); - /* First we expand the key. */ - aes_expand_key((u8 *)info->key, key_len, key_exp); - - /* Calculate the number of AES blocks to encrypt. */ - aes_blocks = DIV_ROUND_UP(cipher_len, AES_BLOCK_LENGTH); + ret = image_aes_decrypt_to(info, cipher, cipher_len, *data, size); + if (ret) { + free(*data); + *data = NULL; + } - aes_cbc_decrypt_blocks(key_len, key_exp, (u8 *)info->iv, - (u8 *)cipher, *data, aes_blocks); + return ret; #endif - - return 0; } -- 2.53.0

