From 7c3832ff2aba04079e5c7c4031e7647be517bb4d Mon Sep 17 00:00:00 2001 From: "harshal.patil" Date: Fri, 6 Dec 2024 09:07:54 +0530 Subject: [PATCH] feat(nvs_flash): Add a minimal XTS-AES layer for the bootloader --- components/nvs_flash/CMakeLists.txt | 9 +- .../private_include/nvs_bootloader_aes.h | 23 ++ .../private_include/nvs_bootloader_xts_aes.h | 94 ++++++++ components/nvs_flash/src/nvs_bootloader_aes.c | 43 ++++ .../nvs_flash/src/nvs_bootloader_xts_aes.c | 200 ++++++++++++++++++ 5 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 components/nvs_flash/private_include/nvs_bootloader_aes.h create mode 100644 components/nvs_flash/private_include/nvs_bootloader_xts_aes.h create mode 100644 components/nvs_flash/src/nvs_bootloader_aes.c create mode 100644 components/nvs_flash/src/nvs_bootloader_xts_aes.c diff --git a/components/nvs_flash/CMakeLists.txt b/components/nvs_flash/CMakeLists.txt index 27f66cc9be..6f43b478bb 100644 --- a/components/nvs_flash/CMakeLists.txt +++ b/components/nvs_flash/CMakeLists.txt @@ -1,6 +1,11 @@ if(BOOTLOADER_BUILD) # bootloader build simplified version set(srcs "src/nvs_bootloader.c") + + if(CONFIG_NVS_ENCRYPTION) + list(APPEND srcs "src/nvs_bootloader_aes.c" + "src/nvs_bootloader_xts_aes.c") + endif() set(requires "esp_partition") idf_component_register(SRCS "${srcs}" @@ -52,7 +57,9 @@ else() target_compile_options(${COMPONENT_LIB} PUBLIC --coverage) target_link_libraries(${COMPONENT_LIB} PUBLIC --coverage) else() - target_sources(${COMPONENT_LIB} PRIVATE "src/nvs_encrypted_partition.cpp") + target_sources(${COMPONENT_LIB} PRIVATE "src/nvs_encrypted_partition.cpp" + "src/nvs_bootloader_aes.c" + "src/nvs_bootloader_xts_aes.c") target_link_libraries(${COMPONENT_LIB} PRIVATE idf::mbedtls) endif() diff --git a/components/nvs_flash/private_include/nvs_bootloader_aes.h b/components/nvs_flash/private_include/nvs_bootloader_aes.h new file mode 100644 index 0000000000..be05e9d4eb --- /dev/null +++ b/components/nvs_flash/private_include/nvs_bootloader_aes.h @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "sdkconfig.h" +#include "rom/aes.h" + +#if CONFIG_IDF_TARGET_ESP32 +enum AES_TYPE { + AES_ENC, + AES_DEC, +}; +#endif /* CONFIG_IDF_TARGET_ESP32 */ + +int nvs_bootloader_aes_crypt_ecb(enum AES_TYPE mode, + const unsigned char *key, + enum AES_BITS key_bits, + const unsigned char input[16], + unsigned char output[16]); diff --git a/components/nvs_flash/private_include/nvs_bootloader_xts_aes.h b/components/nvs_flash/private_include/nvs_bootloader_xts_aes.h new file mode 100644 index 0000000000..d1862c370b --- /dev/null +++ b/components/nvs_flash/private_include/nvs_bootloader_xts_aes.h @@ -0,0 +1,94 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "esp_err.h" +#include "nvs_bootloader_aes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \brief The XTS-AES context-type definition. + */ +typedef struct +{ + uint8_t crypt_key[32]; /*!< The AES context to use for AES block + encryption or decryption. */ + uint8_t tweak_key[32]; /*!< The AES context used for tweak + computation. */ +} nvs_bootloader_xts_aes_context; + +/** Invalid key length. */ +#define NVS_BOOTLOADER_ERR_AES_INVALID_KEY_LENGTH -0x0020 +/** Invalid data input length. */ +#define NVS_BOOTLOADER_ERR_AES_INVALID_INPUT_LENGTH -0x0022 + +/** + * @brief Initializes the specified XTS-AES context. + * + * @param ctx The XTS-AES context to be initialized. This must not be \c NULL. + */ +void nvs_bootloader_xts_aes_init(nvs_bootloader_xts_aes_context *ctx); + +/** + * @brief Clears the specified XTS-AES context. + * + * @param ctx The XTS-AES context to clear. + * If this is \c NULL, this function does nothing. + * Otherwise, the context must have been at least initialized. + */ +void nvs_bootloader_xts_aes_free(nvs_bootloader_xts_aes_context *ctx); + +/** + * @brief Sets the XTS-AES encryption-decryption key + * + * @param ctx The XTS-AES context to which the key should be bound. + * It must be initialized. + * @param key The encryption key. + * This must be a readable buffer of size \p key_bytes bytes. + * @param key_bytes The size of data passed in bits. Valid options are: + *
  • 128 bits
  • + *
  • 192 bits
  • + *
  • 256 bits
+ * @return \c 0 indicating success. + */ +int nvs_bootloader_xts_aes_setkey(nvs_bootloader_xts_aes_context *ctx, + const unsigned char *key, + unsigned int key_bytes); + +/** + * @brief Performs an XTS-AES encryption or decryption operation for an entire XTS data unit. + * + * @param ctx The XTS-AES context to use for XTS-AES operation + * It must be initialized and bound to a key. + * @param mode The AES operation: AES_ENC or AES_DEC. + * @param length The length of a data unit in bytes. + * @param data_unit The address of the data unit encoded as an array of 16 + * bytes in little-endian format. For disk encryption, this + * is typically the index of the block device sector that + * contains the data. + * @param input The buffer holding the input data (which is an entire + * data unit). This function reads \p length Bytes from \p + * input. + * @param output The buffer holding the output data (which is an entire + * data unit). This function writes \p length Bytes to \p + * output. + * @return \c 0 on success. + * 1 on failure. + */ +int nvs_bootloader_aes_crypt_xts(nvs_bootloader_xts_aes_context *ctx, + enum AES_TYPE mode, + size_t length, + const unsigned char data_unit[16], + const unsigned char *input, + unsigned char *output); + +#ifdef __cplusplus +} +#endif diff --git a/components/nvs_flash/src/nvs_bootloader_aes.c b/components/nvs_flash/src/nvs_bootloader_aes.c new file mode 100644 index 0000000000..c6a2e20cf0 --- /dev/null +++ b/components/nvs_flash/src/nvs_bootloader_aes.c @@ -0,0 +1,43 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "nvs_bootloader_aes.h" +#include "sdkconfig.h" + +int nvs_bootloader_aes_crypt_ecb(enum AES_TYPE mode, + const unsigned char *key, + enum AES_BITS key_bits, + const unsigned char input[16], + unsigned char output[16]) +{ + int ret = -1; + ets_aes_enable(); + +#if CONFIG_IDF_TARGET_ESP32 + if (mode == AES_ENC) { + ret = ets_aes_setkey_enc(key, key_bits); + } else { + ret = ets_aes_setkey_dec(key, key_bits); + } + + if (ret) { + ets_aes_crypt(input, output); + // In case of esp32, ets_aes_setkey_dec returns 1 on success, + // whereas for other targets ets_aes_setkey return 0 on success + ret = 0; + } +#else /* !CONFIG_IDF_TARGET_ESP32m*/ + ret = ets_aes_setkey(mode, key, key_bits); + + if (ret == 0) { + ets_aes_block(input, output); + } +#endif /* CONFIG_IDF_TARGET_ESP32 */ + + ets_aes_disable(); + return ret; +} diff --git a/components/nvs_flash/src/nvs_bootloader_xts_aes.c b/components/nvs_flash/src/nvs_bootloader_xts_aes.c new file mode 100644 index 0000000000..bd7500f537 --- /dev/null +++ b/components/nvs_flash/src/nvs_bootloader_xts_aes.c @@ -0,0 +1,200 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include "esp_err.h" + +#include "nvs_bootloader_aes.h" +#include "nvs_bootloader_xts_aes.h" + +/* + * NOTE: The implementation of the below APIs have been copied + * from the mbedtls (v3.6.2) implementation of the XTS-AES APIs. + */ +void nvs_bootloader_xts_aes_init(nvs_bootloader_xts_aes_context *ctx) +{ + bzero(&ctx->crypt_key, sizeof(ctx->crypt_key)); + bzero(&ctx->tweak_key, sizeof(ctx->tweak_key)); +} + +void nvs_bootloader_xts_aes_free(nvs_bootloader_xts_aes_context *ctx) +{ + if (ctx) { + bzero(&ctx->crypt_key, sizeof(ctx->crypt_key)); + bzero(&ctx->tweak_key, sizeof(ctx->tweak_key)); + } +} + +int nvs_bootloader_xts_aes_setkey(nvs_bootloader_xts_aes_context *ctx, + const unsigned char *key, + unsigned int key_bytes) +{ + size_t xts_key_bytes = key_bytes / 2; + + memcpy(&ctx->crypt_key, key, xts_key_bytes); + memcpy(&ctx->tweak_key, &key[xts_key_bytes], xts_key_bytes); + return 0; +} + +/* Endianness with 64 bits values */ +#ifndef GET_UINT64_LE +#define GET_UINT64_LE(n,b,i) \ +{ \ + (n) = ((uint64_t) (b)[(i) + 7] << 56) \ + | ((uint64_t) (b)[(i) + 6] << 48) \ + | ((uint64_t) (b)[(i) + 5] << 40) \ + | ((uint64_t) (b)[(i) + 4] << 32) \ + | ((uint64_t) (b)[(i) + 3] << 24) \ + | ((uint64_t) (b)[(i) + 2] << 16) \ + | ((uint64_t) (b)[(i) + 1] << 8) \ + | ((uint64_t) (b)[(i) ] ); \ +} +#endif + +#ifndef PUT_UINT64_LE +#define PUT_UINT64_LE(n,b,i) \ +{ \ + (b)[(i) + 7] = (unsigned char) ((n) >> 56); \ + (b)[(i) + 6] = (unsigned char) ((n) >> 48); \ + (b)[(i) + 5] = (unsigned char) ((n) >> 40); \ + (b)[(i) + 4] = (unsigned char) ((n) >> 32); \ + (b)[(i) + 3] = (unsigned char) ((n) >> 24); \ + (b)[(i) + 2] = (unsigned char) ((n) >> 16); \ + (b)[(i) + 1] = (unsigned char) ((n) >> 8); \ + (b)[(i) ] = (unsigned char) ((n) ); \ +} +#endif + +/* + * GF(2^128) multiplication function + * + * This function multiplies a field element by x in the polynomial field + * representation. It uses 64-bit word operations to gain speed but compensates + * for machine endianness and hence works correctly on both big and little + * endian machines. + */ +static void bootloader_gf128mul_x_ble(unsigned char r[16], + const unsigned char x[16]) +{ + uint64_t a, b, ra, rb; + + GET_UINT64_LE(a, x, 0); + GET_UINT64_LE(b, x, 8); + + ra = (a << 1) ^ 0x0087 >> (8 - ((b >> 63) << 3)); + rb = (a >> 63) | (b << 1); + + PUT_UINT64_LE(ra, r, 0); + PUT_UINT64_LE(rb, r, 8); +} + +/* + * XTS-AES buffer encryption/decryption + */ +int nvs_bootloader_aes_crypt_xts(nvs_bootloader_xts_aes_context *ctx, + enum AES_TYPE mode, + size_t length, + const unsigned char data_unit[16], + const unsigned char *input, + unsigned char *output) +{ + int ret; + size_t blocks = length / 16; + size_t leftover = length % 16; + unsigned char tweak[16] = {}; + unsigned char prev_tweak[16] = {}; + unsigned char tmp[16] = {}; + + /* Sectors must be at least 16 bytes. */ + if (length < 16) { + return NVS_BOOTLOADER_ERR_AES_INVALID_INPUT_LENGTH; + } + + /* NIST SP 80-38E disallows data units larger than 2**20 blocks. */ + if (length > ( 1 << 20 ) * 16) { + return NVS_BOOTLOADER_ERR_AES_INVALID_INPUT_LENGTH; + } + + /* Compute the tweak. */ + ret = nvs_bootloader_aes_crypt_ecb(AES_ENC, (const unsigned char *) &ctx->tweak_key, + AES256, data_unit, tweak); + if (ret != 0) { + return (ret); + } + + while (blocks--) { + size_t i; + + if (leftover && (mode == AES_DEC) && blocks == 0) { + /* We are on the last block in a decrypt operation that has + * leftover bytes, so we need to use the next tweak for this block, + * and this tweak for the lefover bytes. Save the current tweak for + * the leftovers and then update the current tweak for use on this, + * the last full block. */ + memcpy(prev_tweak, tweak, sizeof(tweak)); + bootloader_gf128mul_x_ble(tweak, tweak); + } + + for (i = 0; i < 16; i++) { + tmp[i] = input[i] ^ tweak[i]; + } + + ret = nvs_bootloader_aes_crypt_ecb(mode, (const unsigned char *) &ctx->crypt_key, AES256, tmp, tmp); + if (ret != 0) { + return (ret); + } + + for (i = 0; i < 16; i++) { + output[i] = tmp[i] ^ tweak[i]; + } + + /* Update the tweak for the next block. */ + bootloader_gf128mul_x_ble(tweak, tweak); + + output += 16; + input += 16; + } + + if (leftover) { + /* If we are on the leftover bytes in a decrypt operation, we need to + * use the previous tweak for these bytes (as saved in prev_tweak). */ + unsigned char *t = mode == AES_DEC ? prev_tweak : tweak; + + /* We are now on the final part of the data unit, which doesn't divide + * evenly by 16. It's time for ciphertext stealing. */ + size_t i; + unsigned char *prev_output = output - 16; + + /* Copy ciphertext bytes from the previous block to our output for each + * byte of ciphertext we won't steal. At the same time, copy the + * remainder of the input for this final round (since the loop bounds + * are the same). */ + for (i = 0; i < leftover; i++) { + output[i] = prev_output[i]; + tmp[i] = input[i] ^ t[i]; + } + + /* Copy ciphertext bytes from the previous block for input in this + * round. */ + for (; i < 16; i++) { + tmp[i] = prev_output[i] ^ t[i]; + } + + ret = nvs_bootloader_aes_crypt_ecb(mode, (const unsigned char *) &ctx->crypt_key, AES256, tmp, tmp); + if (ret != 0) { + return ret; + } + + /* Write the result back to the previous block, overriding the previous + * output we copied. */ + for (i = 0; i < 16; i++) { + prev_output[i] = tmp[i] ^ t[i]; + } + } + + return 0; +}