diff --git a/.github/workflows/upload_component.yml b/.github/workflows/upload_component.yml index e42127e..9f30c18 100644 --- a/.github/workflows/upload_component.yml +++ b/.github/workflows/upload_component.yml @@ -15,6 +15,6 @@ jobs: - name: Upload components to component service uses: espressif/github-actions/upload_components@master with: - directories: "cbor;jsmn;libsodium;pid_ctrl;qrcode;nghttp;sh2lib;expat" + directories: "cbor;jsmn;libsodium;pid_ctrl;qrcode;nghttp;sh2lib;expat;esp_encrypted_img" namespace: "espressif" api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }} diff --git a/esp_encrypted_img/CMakeLists.txt b/esp_encrypted_img/CMakeLists.txt new file mode 100644 index 0000000..9804891 --- /dev/null +++ b/esp_encrypted_img/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "src/esp_encrypted_img.c" + INCLUDE_DIRS "include" + PRIV_REQUIRES mbedtls) diff --git a/esp_encrypted_img/LICENSE b/esp_encrypted_img/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/esp_encrypted_img/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/esp_encrypted_img/README.md b/esp_encrypted_img/README.md new file mode 100644 index 0000000..feb409a --- /dev/null +++ b/esp_encrypted_img/README.md @@ -0,0 +1,40 @@ +# ESP Encrypted Image Abstraction Layer + +This component provides an API interface to decrypt data defined in "ESP Encrypted Image" format. This format is as specified at [Image Format](#image-format) + +This component can help in integrating pre encrypted firmware in over-the-air updates. Additionally, this component can also be used for other use-cases which requires addition of encryption layer for custom data. + + +## Image Format + +![Image Format](image_format.png) + + typedef struct { + char magic[4]; + char enc_gcm[384]; + char iv[16]; + char bin_size[4]; + char auth[16]; + char extra_header[88]; + } pre_enc_bin_header; + +The above struct represents encrypted image header. + +Note: +* RSA-3072 key is provided to the tool externally. You can generate RSA key pair using following command: + + `openssl genrsa -out rsa_key/private.pem 3072` + +* AES-GCM key and IV are generated by the tool itself. + +## Tool Info + +This component also contains tool ([esp_enc_img_gen.py](tools/esp_enc_img_gen.py)) to generate encrypted images using RSA3072 public key. + +To know more about the tool, use command: +`python esp_enc_img-gen.py --help` + + +## API Reference + +To learn more about how to use this component, please check API Documentation from header file [esp_encrypted_img.h](include/esp_encrypted_img.h) \ No newline at end of file diff --git a/esp_encrypted_img/idf_component.yml b/esp_encrypted_img/idf_component.yml new file mode 100644 index 0000000..f0a625e --- /dev/null +++ b/esp_encrypted_img/idf_component.yml @@ -0,0 +1,6 @@ +version: "1.0.0" +description: ESP Encrypted Image Abstraction Layer +url: https://github.com/espressif/idf-extra-components/tree/master/esp_encrypted_img +dependencies: + idf: + version: ">=4.4" diff --git a/esp_encrypted_img/image_format.png b/esp_encrypted_img/image_format.png new file mode 100644 index 0000000..5882211 Binary files /dev/null and b/esp_encrypted_img/image_format.png differ diff --git a/esp_encrypted_img/include/esp_encrypted_img.h b/esp_encrypted_img/include/esp_encrypted_img.h new file mode 100644 index 0000000..6f43327 --- /dev/null +++ b/esp_encrypted_img/include/esp_encrypted_img.h @@ -0,0 +1,105 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include + +#if 0 //High level layout for state machine + +@startuml +[*] --> READ_MAGIC +READ_MAGIC --> READ_MAGIC : READ LEN < 4 +READ_MAGIC --> DECODE_MAGIC : READ LEN = 4 + +DECODE_MAGIC --> READ_GCM : MAGIC VERIFIED +DECODE_MAGIC --> ESP_FAIL : MAGIC VERIFICATION FAILED +PROCESS_BINARY --> ESP_FAIL : DECRYPTION FAILED + +READ_GCM --> READ_GCM : READ_LEN < 384 +READ_GCM --> DECRYPT_GCM : READ_LEN = 384 +DECRYPT_GCM --> ESP_FAIL : DECRYPTION FAILED +DECRYPT_GCM --> READ_IV : DECRYPTION SUCCESSFUL +READ_IV --> READ_IV : READ LEN < 16 +READ_IV --> READ_BIN_SIZE +READ_BIN_SIZE --> READ_BIN_SIZE : READ LEN < 5 +READ_BIN_SIZE --> READ_AUTH +READ_AUTH --> READ_AUTH : READ LEN < 16 +READ_AUTH --> PROCESS_BINARY +PROCESS_BINARY --> PROCESS_BINARY : READ LEN < BIN_SIZE + +PROCESS_BINARY --> ESP_OK : READ LEN = BIN_SIZE +ESP_OK --> [*] +ESP_FAIL --> [*] +@enduml + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void *esp_decrypt_handle_t; + +typedef struct { + const char *rsa_pub_key; /*!< 3072 bit RSA key in PEM format */ + size_t rsa_pub_key_len; /*!< Length of the buffer pointed to by rsa_pub_key*/ +} esp_decrypt_cfg_t; + +typedef struct { + char *data_in; /*!< Pointer to data to be decrypted */ + size_t data_in_len; /*!< Input data length */ + char *data_out; /*!< Pointer to decrypted data */ + size_t data_out_len; /*!< Output data length */ +} pre_enc_decrypt_arg_t; + + +/** +* @brief This function returns esp_decrypt_handle_t handle. +* +* @param[in] cfg pointer to esp_decrypt_cfg_t structure +* +* @return +* - NULL On failure +* - esp_decrypt_handle_t handle +*/ +esp_decrypt_handle_t esp_encrypted_img_decrypt_start(const esp_decrypt_cfg_t *cfg); + + +/** +* @brief This function performs decryption on input data. +* +* This function must be called only if esp_encrypted_img_decrypt_start() returns successfully. +* This function must be called in a loop since since input data might not contain whole binary at once. +* This function must be called till it return ESP_OK. +* +* @note args->data_out must be freed after use provided args->data_out_len is greater than 0 +* +* @param[in] ctx pointer to esp_decrypt_handle_t +* @param[in/out] args pointer to pre_enc_decrypt_arg_t +* +* @return +* - ESP_FAIL On failure +* - ESP_ERR_DECRYPT_IN_PROGRESS Decryption is in process +* - ESP_OK Success +*/ +esp_err_t esp_encrypted_img_decrypt_data(esp_decrypt_handle_t *ctx, pre_enc_decrypt_arg_t *args); + + +/** +* @brief Clean-up decryption process. +* +* @param[in] ctx pointer to esp_decrypt_handle_t structure +* +* @return +* - ESP_FAIL On failure +* - ESP_OK +*/ +esp_err_t esp_encrypted_img_decrypt_end(esp_decrypt_handle_t *ctx); + + +#ifdef __cplusplus +} +#endif diff --git a/esp_encrypted_img/project_include.cmake b/esp_encrypted_img/project_include.cmake new file mode 100644 index 0000000..1002cbe --- /dev/null +++ b/esp_encrypted_img/project_include.cmake @@ -0,0 +1,18 @@ +set(ESP_IMG_GEN_TOOL_PATH ${CMAKE_CURRENT_LIST_DIR}/tools/esp_enc_img_gen.py) + +function(create_esp_enc_img input_file rsa_key_file output_file app) + cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}") + idf_build_get_property(python PYTHON) + + add_custom_command(OUTPUT ${output_file} + POST_BUILD + COMMAND ${python} ${ESP_IMG_GEN_TOOL_PATH} encrypt + ${input_file} + ${rsa_key_file} ${output_file} + DEPENDS gen_project_binary + COMMENT "Generating pre-encrypted binary" + VERBATIM + ) + add_custom_target(encrypt_bin_target DEPENDS ${output_file}) + add_dependencies(${app} encrypt_bin_target) +endfunction() diff --git a/esp_encrypted_img/src/esp_encrypted_img.c b/esp_encrypted_img/src/esp_encrypted_img.c new file mode 100644 index 0000000..956dbbb --- /dev/null +++ b/esp_encrypted_img/src/esp_encrypted_img.c @@ -0,0 +1,448 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_encrypted_img.h" +#include +#include +#include + +#include "mbedtls/pk.h" +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/gcm.h" +#include "sys/param.h" + +static const char *TAG = "esp_encrypted_img"; + +typedef enum { + ESP_PRE_ENC_IMG_READ_MAGIC, + ESP_PRE_ENC_IMG_READ_GCM, + ESP_PRE_ENC_IMG_READ_IV, + ESP_PRE_ENC_IMG_READ_BINSIZE, + ESP_PRE_ENC_IMG_READ_AUTH, + ESP_PRE_ENC_IMG_READ_EXTRA_HEADER, + ESP_PRE_ENC_DATA_DECODE_STATE, +} esp_encrypted_img_state; + +struct esp_encrypted_img_handle { + const char *rsa_pem; + size_t rsa_len; + uint32_t binary_file_len; + uint32_t binary_file_read; + char *gcm_key; + char *iv; + char auth_tag[16]; + esp_encrypted_img_state state; + mbedtls_gcm_context gcm_ctx; + size_t cache_buf_len; + char *cache_buf; +}; + +#define GCM_KEY_SIZE 32 +#define MAGIC_SIZE 4 +#define ENC_GCM_KEY_SIZE 384 +#define IV_SIZE 16 +#define BIN_SIZE_DATA 4 +#define AUTH_SIZE 16 +#define RESERVED_HEADER 88 + +typedef struct { + char magic[MAGIC_SIZE]; + char enc_gcm[ENC_GCM_KEY_SIZE]; + char iv[IV_SIZE]; + char bin_size[BIN_SIZE_DATA]; + char auth[AUTH_SIZE]; + char extra_header[RESERVED_HEADER]; +} pre_enc_bin_header; +#define HEADER_DATA_SIZE sizeof(pre_enc_bin_header) + +// Magic Byte is created using command: echo -n "esp_encrypted_img" | sha256sum +static uint32_t esp_enc_img_magic = 0x0788b6cf; + +typedef struct esp_encrypted_img_handle esp_encrypted_img_t; + +static int decipher_gcm_key(char *enc_gcm, esp_encrypted_img_t *handle) +{ + int ret = 1; + handle->gcm_key = calloc(1, GCM_KEY_SIZE); + if (!handle->gcm_key) { + return ESP_ERR_NO_MEM; + } + size_t olen = 0; + mbedtls_pk_context pk; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + const char *pers = "mbedtls_pk_encrypt"; + + mbedtls_ctr_drbg_init( &ctr_drbg ); + mbedtls_entropy_init( &entropy ); + mbedtls_pk_init( &pk ); + + if ((ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, + &entropy, (const unsigned char *) pers, + strlen(pers))) != 0) { + ESP_LOGE(TAG, "failed\n ! mbedtls_ctr_drbg_seed returned -0x%04x\n", (unsigned int) - ret); + free(handle->gcm_key); + goto exit; + } + + ESP_LOGI(TAG, "Reading RSA private key"); + + if ( (ret = mbedtls_pk_parse_key(&pk, (const unsigned char *) handle->rsa_pem, handle->rsa_len, NULL, 0)) != 0) { + ESP_LOGE(TAG, "failed\n ! mbedtls_pk_parse_keyfile returned -0x%04x\n", (unsigned int) - ret ); + free(handle->gcm_key); + goto exit; + } + + if (( ret = mbedtls_pk_decrypt( &pk, (const unsigned char *)enc_gcm, ENC_GCM_KEY_SIZE, (unsigned char *)handle->gcm_key, &olen, GCM_KEY_SIZE, + mbedtls_ctr_drbg_random, &ctr_drbg ) ) != 0 ) { + ESP_LOGE(TAG, "failed\n ! mbedtls_pk_decrypt returned -0x%04x\n", (unsigned int) - ret ); + free(handle->gcm_key); + goto exit; + } + handle->cache_buf = realloc(handle->cache_buf, 16); + if (!handle->cache_buf) { + return ESP_ERR_NO_MEM; + } + handle->state = ESP_PRE_ENC_IMG_READ_IV; + handle->binary_file_read = 0; + handle->cache_buf_len = 0; + handle->iv = calloc(1, IV_SIZE); + if (!handle->iv) { + return ESP_ERR_NO_MEM; + } + +exit: + mbedtls_pk_free( &pk ); + mbedtls_entropy_free( &entropy ); + mbedtls_ctr_drbg_free( &ctr_drbg ); + free((void *)handle->rsa_pem); + + return (ret); +} + +esp_decrypt_handle_t esp_encrypted_img_decrypt_start(const esp_decrypt_cfg_t *cfg) +{ + if (cfg == NULL || cfg->rsa_pub_key == NULL) { + ESP_LOGE(TAG, "esp_encrypted_img_decrypt_start : Invalid argument"); + return NULL; + } + ESP_LOGI(TAG, "Starting Decryption Process"); + + esp_encrypted_img_t *handle = calloc(1, sizeof(esp_encrypted_img_t)); + if (!handle) { + ESP_LOGE(TAG, "Couldn't allocate memory to handle"); + goto failure; + } + + handle->rsa_pem = calloc(1, cfg->rsa_pub_key_len); + if (!handle->rsa_pem) { + ESP_LOGE(TAG, "Couldn't allocate memory to handle->rsa_pem"); + goto failure; + } + + handle->cache_buf = calloc(1, ENC_GCM_KEY_SIZE); + if (!handle->cache_buf) { + ESP_LOGE(TAG, "Couldn't allocate memory to handle->cache_buf"); + goto failure; + } + + memcpy((void *)handle->rsa_pem, cfg->rsa_pub_key, cfg->rsa_pub_key_len); + handle->rsa_len = cfg->rsa_pub_key_len; + handle->state = ESP_PRE_ENC_IMG_READ_MAGIC; + + esp_decrypt_handle_t *ctx = (esp_decrypt_handle_t *)handle; + return ctx; + +failure: + if (!handle) { + return NULL; + } + if (handle->rsa_pem) { + free((void *)handle->rsa_pem); + } + if (handle) { + free(handle); + } + return NULL; +} + +static esp_err_t process_bin(esp_encrypted_img_t *handle, pre_enc_decrypt_arg_t *args, int curr_index) +{ + size_t data_len = args->data_in_len; + + handle->binary_file_read += data_len - curr_index; + int dec_len = 0; + if (handle->binary_file_read != handle->binary_file_len) { + size_t copy_len = 0; + + if ((handle->cache_buf_len + (data_len - curr_index)) - (handle->cache_buf_len + (data_len - curr_index)) % 16 > 0) { + args->data_out = realloc(args->data_out, (handle->cache_buf_len + (data_len - curr_index)) - (handle->cache_buf_len + (data_len - curr_index)) % 16); + if (!args->data_out) { + return ESP_ERR_NO_MEM; + } + } + if (handle->cache_buf_len != 0) { + copy_len = MIN(16 - handle->cache_buf_len, data_len - curr_index); + memcpy(handle->cache_buf + handle->cache_buf_len, args->data_in + curr_index, copy_len); + handle->cache_buf_len += copy_len; + if (handle->cache_buf_len != 16) { + args->data_out_len = 0; + return ESP_ERR_NOT_FINISHED; + } + if (mbedtls_gcm_update(&handle->gcm_ctx, 16, (const unsigned char *)handle->cache_buf, (unsigned char *) args->data_out) != 0) { + return ESP_FAIL; + } + dec_len = 16; + } + handle->cache_buf_len = (data_len - curr_index - copy_len) % 16; + if (handle->cache_buf_len != 0) { + data_len -= handle->cache_buf_len; + memcpy(handle->cache_buf, args->data_in + (data_len), handle->cache_buf_len); + } + + if (data_len - copy_len - curr_index > 0) { + if (mbedtls_gcm_update(&handle->gcm_ctx, data_len - copy_len - curr_index, (const unsigned char *)args->data_in + curr_index + copy_len, (unsigned char *)args->data_out + dec_len) != 0) { + return ESP_FAIL; + } + } + args->data_out_len = dec_len + data_len - curr_index - copy_len; + return ESP_ERR_NOT_FINISHED; + } + + args->data_out = realloc(args->data_out, handle->cache_buf_len + data_len - curr_index); + if (!args->data_out) { + return ESP_ERR_NO_MEM; + } + size_t copy_len = 0; + + copy_len = MIN(16 - handle->cache_buf_len, data_len - curr_index); + memcpy(handle->cache_buf + handle->cache_buf_len, args->data_in + curr_index, copy_len); + handle->cache_buf_len += copy_len; + if (mbedtls_gcm_update(&handle->gcm_ctx, handle->cache_buf_len, (const unsigned char *)handle->cache_buf, (unsigned char *)args->data_out) != 0) { + return ESP_FAIL; + } + if (data_len - curr_index - copy_len > 0) { + if (mbedtls_gcm_update(&handle->gcm_ctx, data_len - curr_index - copy_len, (const unsigned char *)(args->data_in + curr_index + copy_len), (unsigned char *)(args->data_out + 16)) != 0) { + return ESP_FAIL; + } + } + + args->data_out_len = handle->cache_buf_len + data_len - copy_len - curr_index; + handle->cache_buf_len = 0; + + return ESP_OK; +} + +static void read_and_cache_data(esp_encrypted_img_t *handle, pre_enc_decrypt_arg_t *args, int *curr_index, int data_size) +{ + const int data_left = data_size - handle->binary_file_read; + const int data_recv = args->data_in_len - *curr_index; + if (handle->state == ESP_PRE_ENC_IMG_READ_IV && handle->iv) { + memcpy(handle->iv + handle->cache_buf_len, args->data_in + *curr_index, MIN(data_recv, data_left)); + } else if (handle->state == ESP_PRE_ENC_IMG_READ_AUTH) { + memcpy(handle->auth_tag + handle->cache_buf_len, args->data_in + *curr_index, MIN(data_recv, data_left)); + } else { + memcpy(handle->cache_buf + handle->cache_buf_len, args->data_in + *curr_index, MIN(data_recv, data_left)); + } + handle->cache_buf_len += MIN(data_recv, data_left); + int temp = *curr_index; + *curr_index += MIN(data_recv, data_left); + handle->binary_file_read += MIN(args->data_in_len - temp, data_left); +} + +esp_err_t esp_encrypted_img_decrypt_data(esp_decrypt_handle_t *ctx, pre_enc_decrypt_arg_t *args) +{ + if (ctx == NULL || args == NULL || args->data_in == NULL) { + return ESP_ERR_INVALID_ARG; + } + esp_encrypted_img_t *handle = (esp_encrypted_img_t *)ctx; + if (handle == NULL) { + ESP_LOGE(TAG, "esp_encrypted_img_decrypt_data: Invalid argument"); + return ESP_ERR_INVALID_ARG; + } + + esp_err_t err; + int curr_index = 0; + + switch (handle->state) { + case ESP_PRE_ENC_IMG_READ_MAGIC: + if (handle->cache_buf_len == 0 && (args->data_in_len - curr_index) >= MAGIC_SIZE) { + uint32_t recv_magic = *(uint32_t *)args->data_in; + + if (recv_magic != esp_enc_img_magic) { + ESP_LOGE(TAG, "Magic Verification failed"); + free((void *)handle->rsa_pem); + return ESP_FAIL; + } + curr_index += MAGIC_SIZE; + } else { + read_and_cache_data(handle, args, &curr_index, MAGIC_SIZE); + if (handle->binary_file_read == MAGIC_SIZE) { + uint32_t recv_magic = *(uint32_t *)handle->cache_buf; + + if (recv_magic != esp_enc_img_magic) { + ESP_LOGE(TAG, "Magic Verification failed"); + free((void *)handle->rsa_pem); + return ESP_FAIL; + } + handle->binary_file_read = 0; + handle->cache_buf_len = 0; + } else { + return ESP_ERR_NOT_FINISHED; + } + } + ESP_LOGI(TAG, "Magic Verified"); + handle->state = ESP_PRE_ENC_IMG_READ_GCM; + /* falls through */ + case ESP_PRE_ENC_IMG_READ_GCM: + if (handle->cache_buf_len == 0 && args->data_in_len - curr_index >= ENC_GCM_KEY_SIZE) { + if (decipher_gcm_key(args->data_in + curr_index, handle) != 0) { + ESP_LOGE(TAG, "Unable to decipher GCM key"); + return ESP_FAIL; + } + curr_index += ENC_GCM_KEY_SIZE; + } else { + read_and_cache_data(handle, args, &curr_index, ENC_GCM_KEY_SIZE); + if (handle->cache_buf_len == ENC_GCM_KEY_SIZE) { + if (decipher_gcm_key(handle->cache_buf, handle) != 0) { + ESP_LOGE(TAG, "Unable to decipher GCM key"); + return ESP_FAIL; + } + } else { + return ESP_ERR_NOT_FINISHED; + } + } + /* falls through */ + case ESP_PRE_ENC_IMG_READ_IV: + if (handle->cache_buf_len == 0 && args->data_in_len - curr_index >= IV_SIZE) { + memcpy(handle->iv, args->data_in + curr_index, IV_SIZE); + handle->binary_file_read = IV_SIZE; + curr_index += IV_SIZE; + } else { + read_and_cache_data(handle, args, &curr_index, IV_SIZE); + } + if (handle->binary_file_read == IV_SIZE) { + handle->state = ESP_PRE_ENC_IMG_READ_BINSIZE; + handle->binary_file_read = 0; + handle->cache_buf_len = 0; + mbedtls_gcm_init(&handle->gcm_ctx); + if ((err = mbedtls_gcm_setkey(&handle->gcm_ctx, MBEDTLS_CIPHER_ID_AES, (const unsigned char *)handle->gcm_key, GCM_KEY_SIZE * 8)) != 0) { + ESP_LOGE(TAG, "Error: mbedtls_gcm_set_key: -0x%04x\n", (unsigned int) - err); + return ESP_FAIL; + } + free(handle->gcm_key); + if (mbedtls_gcm_starts(&handle->gcm_ctx, MBEDTLS_GCM_DECRYPT, (const unsigned char *)handle->iv, IV_SIZE, NULL, 0) != 0) { + ESP_LOGE(TAG, "Error: mbedtls_gcm_starts: -0x%04x\n", (unsigned int) - err); + return ESP_FAIL; + } + free(handle->iv); + handle->iv = NULL; + } else { + return ESP_ERR_NOT_FINISHED; + } + /* falls through */ + case ESP_PRE_ENC_IMG_READ_BINSIZE: + if (handle->cache_buf_len == 0 && (args->data_in_len - curr_index) >= BIN_SIZE_DATA) { + handle->binary_file_len = *(uint32_t *)(args->data_in + curr_index); + curr_index += BIN_SIZE_DATA; + } else { + read_and_cache_data(handle, args, &curr_index, BIN_SIZE_DATA); + if (handle->binary_file_read == BIN_SIZE_DATA) { + handle->binary_file_len = *(uint32_t *)handle->cache_buf; + } else { + return ESP_ERR_NOT_FINISHED; + } + } + handle->state = ESP_PRE_ENC_IMG_READ_AUTH; + handle->binary_file_read = 0; + handle->cache_buf_len = 0; + /* falls through */ + case ESP_PRE_ENC_IMG_READ_AUTH: + if (handle->cache_buf_len == 0 && args->data_in_len - curr_index >= AUTH_SIZE) { + memcpy(handle->auth_tag, args->data_in + curr_index, AUTH_SIZE); + handle->binary_file_read = AUTH_SIZE; + curr_index += AUTH_SIZE; + } else { + read_and_cache_data(handle, args, &curr_index, AUTH_SIZE); + } + if (handle->binary_file_read == AUTH_SIZE) { + handle->state = ESP_PRE_ENC_IMG_READ_EXTRA_HEADER; + handle->binary_file_read = 0; + handle->cache_buf_len = 0; + } else { + return ESP_ERR_NOT_FINISHED; + } + /* falls through */ + case ESP_PRE_ENC_IMG_READ_EXTRA_HEADER: + { + int temp = curr_index; + curr_index += MIN(args->data_in_len - curr_index, RESERVED_HEADER - handle->binary_file_read); + handle->binary_file_read += MIN(args->data_in_len - temp, RESERVED_HEADER - handle->binary_file_read); + if (handle->binary_file_read == RESERVED_HEADER) { + handle->state = ESP_PRE_ENC_DATA_DECODE_STATE; + handle->binary_file_read = 0; + handle->cache_buf_len = 0; + } else { + return ESP_ERR_NOT_FINISHED; + } + } + /* falls through */ + case ESP_PRE_ENC_DATA_DECODE_STATE: + err = process_bin(handle, args, curr_index); + return err; + } + return ESP_OK; +} + +esp_err_t esp_encrypted_img_decrypt_end(esp_decrypt_handle_t *ctx) +{ + if (ctx == NULL) { + return ESP_ERR_INVALID_ARG; + } + esp_encrypted_img_t *handle = (esp_encrypted_img_t *)ctx; + esp_err_t err = ESP_OK; + if (handle == NULL) { + ESP_LOGE(TAG, "esp_encrypted_img_decrypt_data: Invalid argument"); + return ESP_ERR_INVALID_ARG; + } + if (handle->state == ESP_PRE_ENC_DATA_DECODE_STATE) { + if (handle->cache_buf_len != 0 || handle->binary_file_read != handle->binary_file_len) { + ESP_LOGE(TAG, "Invalid operation"); + err = ESP_FAIL; + goto exit; + } + + char *got_auth = calloc(1, AUTH_SIZE); + if (!got_auth) { + ESP_LOGE(TAG, "Unable to allocate memory"); + err = ESP_FAIL; + goto exit; + } + err = mbedtls_gcm_finish(&handle->gcm_ctx, (unsigned char *)got_auth, AUTH_SIZE); + if (err != 0) { + ESP_LOGE(TAG, "Error: %d", err); + free(got_auth); + err = ESP_FAIL; + goto exit; + } + if (memcmp(got_auth, handle->auth_tag, AUTH_SIZE) != 0) { + ESP_LOGE(TAG, "Invalid Auth"); + free(got_auth); + err = ESP_FAIL; + goto exit; + } + + free(got_auth); + } + err = ESP_OK; +exit: + mbedtls_gcm_free(&handle->gcm_ctx); + free(handle->cache_buf); + free(handle); + return err; +} diff --git a/esp_encrypted_img/test/CMakeLists.txt b/esp_encrypted_img/test/CMakeLists.txt new file mode 100644 index 0000000..58df9d6 --- /dev/null +++ b/esp_encrypted_img/test/CMakeLists.txt @@ -0,0 +1,6 @@ +idf_component_register(SRC_DIRS "." + PRIV_INCLUDE_DIRS "." + REQUIRES unity + PRIV_REQUIRES cmock esp_encrypted_img + EMBED_TXTFILES certs/test_rsa_private_key.pem + EMBED_FILES image.bin) diff --git a/esp_encrypted_img/test/certs/test_rsa_private_key.pem b/esp_encrypted_img/test/certs/test_rsa_private_key.pem new file mode 100644 index 0000000..44d0970 --- /dev/null +++ b/esp_encrypted_img/test/certs/test_rsa_private_key.pem @@ -0,0 +1,39 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/esp_encrypted_img/test/image.bin b/esp_encrypted_img/test/image.bin new file mode 100644 index 0000000..6d4bb7c Binary files /dev/null and b/esp_encrypted_img/test/image.bin differ diff --git a/esp_encrypted_img/test/test.c b/esp_encrypted_img/test/test.c new file mode 100644 index 0000000..fa8f6c5 --- /dev/null +++ b/esp_encrypted_img/test/test.c @@ -0,0 +1,306 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include "unity.h" +#include "esp_system.h" +#include "esp_encrypted_img.h" + +extern const uint8_t rsa_private_pem_start[] asm("_binary_test_rsa_private_key_pem_start"); +extern const uint8_t rsa_private_pem_end[] asm("_binary_test_rsa_private_key_pem_end"); + +extern const uint8_t bin_start[] asm("_binary_image_bin_start"); +extern const uint8_t bin_end[] asm("_binary_image_bin_end"); + +TEST_CASE("Sending all data at once", "[encrypted_img]") +{ + esp_decrypt_cfg_t cfg = { + .rsa_pub_key = (char *)rsa_private_pem_start, + .rsa_pub_key_len = rsa_private_pem_end - rsa_private_pem_start, + }; + esp_decrypt_handle_t *ctx = esp_encrypted_img_decrypt_start(&cfg); + TEST_ASSERT_NOT_NULL(ctx); + + pre_enc_decrypt_arg_t *args = calloc(1, sizeof(pre_enc_decrypt_arg_t)); + TEST_ASSERT_NOT_NULL(args); + + args->data_in = (char *)bin_start; + args->data_in_len = bin_end - bin_start; + + esp_err_t err; + err = esp_encrypted_img_decrypt_data(ctx, args); + + TEST_ESP_OK(err); + printf("Successful\n"); + printf("\n"); + + err = esp_encrypted_img_decrypt_end(ctx); + TEST_ESP_OK(err); + if (args->data_out) { + free(args->data_out); + } + free(args); +} + +TEST_CASE("Sending 1 byte data at once", "[encrypted_img]") +{ + esp_decrypt_cfg_t cfg = { + .rsa_pub_key = (char *)rsa_private_pem_start, + .rsa_pub_key_len = rsa_private_pem_end - rsa_private_pem_start, + }; + esp_decrypt_handle_t *ctx = esp_encrypted_img_decrypt_start(&cfg); + TEST_ASSERT_NOT_NULL(ctx); + + pre_enc_decrypt_arg_t *args = calloc(1, sizeof(pre_enc_decrypt_arg_t)); + TEST_ASSERT_NOT_NULL(args); + + esp_err_t err; + int i = 0; + do { + args->data_in = (char *)(bin_start + i); + i++; + args->data_in_len = 1; + err = esp_encrypted_img_decrypt_data(ctx, args); + if (err == ESP_FAIL) { + printf("ESP_FAIL ERROR\n"); + break; + } + } while (err != ESP_OK); + TEST_ESP_OK(err); + + err = esp_encrypted_img_decrypt_end(ctx); + TEST_ESP_OK(err); + if (args->data_out) { + free(args->data_out); + } + free(args); +} + +TEST_CASE("Invalid Magic", "[encrypted_img]") +{ + uint8_t cipher[] = { + 0x34, 0x12, 0xbc, 0xec, + 0xf5, 0x3a, 0x72, 0x55, 0x36, 0x0f, 0x4a, 0x92, + 0x5f, 0x15, 0xef, 0xbb, 0xe8, 0x9c, 0x7e, 0x43, + 0x7c, 0x87, 0x3d, 0x00, 0xb4, 0x22, 0xcc, 0x35, + 0x77, 0x92, 0x6a, 0x44, 0x80, 0x83, 0x9d, 0x0d, + 0x4c, 0x28, 0x5b, 0x7d, 0xbd, 0xef, 0x59, 0x56, + 0xba, 0x0f, 0x9d, 0xab, 0x3b, 0x09, 0x26, 0x77, + 0x82, 0x28, 0x24, 0x6e, 0xb8, 0x35, 0x96, 0x87, + 0xd6, 0x35, 0x03, 0x9b, 0x4e, 0x60, 0xaf, 0x11, + 0x14, 0x74, 0x13, 0x75, 0xb1, 0xf6, 0x5b, 0xe5, + 0xb9, 0xf0, 0x2b, 0x37, 0x02, 0xaf, 0x76, 0x8f, + 0xe1, 0x08, 0xfa, 0x46, 0xa1, 0xb0, 0x8d, 0x04, + 0xb6, 0x09, 0x8b, 0x0f, 0x8c, 0x54, 0xab, 0x9d, + 0xe9, 0x32, 0xb7, 0xae, 0xc5, 0x6d, 0x5d, 0x85, + 0x54, 0x2b, 0x1e, 0x8c, 0xbe, 0x4c, 0xee, 0x89, + 0xc4, 0x16, 0x97, 0x6c, 0x48, 0xaf, 0x3c, 0xef, + 0x90, 0x01, 0x12, 0xd6, 0x72, 0xf4, 0xca, 0xc5, + 0xa1, 0xec, 0x8d, 0xe0, 0x4b, 0xc8, 0xb2, 0xb4, + 0x66, 0x92, 0xd8, 0x0f, 0x65, 0x4d, 0x16, 0x30, + 0x6d, 0x2e, 0x3b, 0x64, 0x22, 0x36, 0x02, 0x43, + 0x10, 0xa8, 0x77, 0xb1, 0xf2, 0x8c, 0x29, 0x8d, + 0x23, 0xf2, 0xc6, 0xd4, 0x5b, 0x2e, 0x78, 0x15, + 0x8b, 0x5e, 0x0a, 0x2b, 0xd8, 0x76, 0x83, 0xe6, + 0xf9, 0x2a, 0xad, 0xf7, 0xf1, 0x06, 0x18, 0xbf, + 0xe1, 0x50, 0x89, 0xbf, 0x92, 0xfd, 0xe8, 0xcf, + 0x06, 0x5b, 0xf3, 0xa0, 0x3b, 0xa2, 0x60, 0x0e, + 0x04, 0xc8, 0x80, 0xb7, 0x97, 0xce, 0xdc, 0xc6, + 0x7c, 0xdb, 0x82, 0x77, 0xa7, 0x84, 0xf3, 0xf3, + 0x4a, 0xfa, 0xce, 0x9a, 0x1e, 0x4b, 0x81, 0x45, + 0xed, 0xfc, 0xd7, 0xe3, 0x4d, 0x40, 0x03, 0x8f, + 0x17, 0x30, 0x8f, 0x35, 0x62, 0xc3, 0x9e, 0x1d, + 0x11, 0xee, 0x3c, 0x5a, 0xbd, 0x3b, 0x32, 0x6c, + 0x16, 0xef, 0xab, 0x67, 0xe7, 0x57, 0x89, 0x61, + 0xbd, 0x1c, 0xb0, 0x28, 0x58, 0x3b, 0x70, 0xd7, + 0x4a, 0x8e, 0x08, 0x0b, 0x3b, 0x14, 0x15, 0x7c, + 0x0e, 0x97, 0xcc, 0xa6, 0x00, 0xf7, 0x0c, 0xa0, + 0x3a, 0xd0, 0xc7, 0x97, 0xbe, 0xf6, 0xc6, 0xe7, + 0xc4, 0xc4, 0x90, 0x3d, 0x94, 0xf8, 0xd3, 0x71, + 0x85, 0x6c, 0x40, 0x97, 0xae, 0x52, 0x0e, 0xe0, + 0x70, 0x15, 0x13, 0xe4, 0xcd, 0xbb, 0xf0, 0x80, + 0x11, 0x56, 0x08, 0xff, 0x71, 0xa3, 0xca, 0x00, + 0x43, 0x0d, 0xde, 0xa1, 0x81, 0xaa, 0x52, 0x9a, + 0xfd, 0x64, 0x58, 0x6c, 0x99, 0x37, 0x9b, 0x04, + 0x46, 0x33, 0x22, 0x88, 0x53, 0x1a, 0x99, 0x54, + 0xf2, 0x77, 0x00, 0x23, 0xf0, 0xf3, 0x24, 0x02, + 0x7d, 0x2a, 0x93, 0x98, 0x25, 0x5f, 0x55, 0xaf, + 0x99, 0xf6, 0x69, 0x4e, 0x3a, 0xdf, 0x8f, 0xed, + 0x59, 0x91, 0xaa, 0xff, 0x0b, 0xaa, 0x70, 0x8e, + 0x08, 0xfe, 0xa1, 0x32, 0xa9, 0xa7, 0xc6, 0xf9, + 0x1d, 0xfa, 0x52, 0x32, 0xc5, 0x5b, 0x64, 0x1e, + 0x75, 0x67, 0xe0, 0xac, 0xe0, 0x35, 0x9d, 0x4a, + 0x09, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x9a, 0xf1, + 0xe2, 0x4a, 0xe8, 0xea, 0xed, 0x68, 0x15, 0xf4, + 0x04, 0x36, 0x96, 0x60, 0x48, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x5a, 0x2f, 0x2a, 0x16, + 0x6c, 0x8b, 0x34, 0x4e, 0xc0 + }; + + esp_decrypt_cfg_t cfg = { + .rsa_pub_key = (char *)rsa_private_pem_start, + .rsa_pub_key_len = rsa_private_pem_end - rsa_private_pem_start, + }; + esp_decrypt_handle_t *ctx = esp_encrypted_img_decrypt_start(&cfg); + TEST_ASSERT_NOT_NULL(ctx); + + pre_enc_decrypt_arg_t *args = calloc(1, sizeof(pre_enc_decrypt_arg_t)); + TEST_ASSERT_NOT_NULL(args); + + args->data_in = (char *)cipher; + args->data_in_len = 521; + + esp_err_t err; + + err = esp_encrypted_img_decrypt_data(ctx, args); + + TEST_ESP_ERR(ESP_FAIL, err); + + err = esp_encrypted_img_decrypt_end(ctx); + free(args); +} + +TEST_CASE("Invalid Image", "[encrypted_img]") +{ + //"Espressif" is encoded using GCM key. After successful decoding, "Espressif" will be printed. + uint8_t cipher[] = { + 0xcf, 0xb6, 0x88, 0x07, + 0xf5, 0x3a, 0x72, 0x55, 0x36, 0x0f, 0x4a, 0x92, + 0x5f, 0x15, 0xef, 0xbb, 0xe8, 0x9c, 0x7e, 0x43, + 0x7c, 0x87, 0x3d, 0x00, 0xb4, 0x22, 0xcc, 0x35, + 0x77, 0x92, 0x6a, 0x44, 0x80, 0x83, 0x9d, 0x0d, + 0x4c, 0x28, 0x5b, 0x7d, 0xbd, 0xef, 0x59, 0x56, + 0xba, 0x0f, 0x9d, 0xab, 0x3b, 0x09, 0x26, 0x77, + 0x82, 0x28, 0x24, 0x6e, 0xb8, 0x35, 0x96, 0x87, + 0xd6, 0x35, 0x03, 0x9b, 0x4e, 0x60, 0xaf, 0x11, + 0x14, 0x74, 0x13, 0x75, 0xb1, 0xf6, 0x5b, 0xe5, + 0xb9, 0xf0, 0x2b, 0x37, 0x02, 0xaf, 0x76, 0x8f, + 0xe1, 0x08, 0xfa, 0x46, 0xa1, 0xb0, 0x8d, 0x04, + 0xb6, 0x09, 0x8b, 0x0f, 0x8c, 0x54, 0xab, 0x9d, + 0xe9, 0x32, 0xb7, 0xae, 0xc5, 0x6d, 0x5d, 0x85, + 0x54, 0x2b, 0x1e, 0x8c, 0xbe, 0x4c, 0xee, 0x89, + 0xc4, 0x16, 0x97, 0x6c, 0x48, 0xaf, 0x3c, 0xef, + 0x90, 0x01, 0x12, 0xd6, 0x72, 0xf4, 0xca, 0xc5, + 0xa1, 0xec, 0x8d, 0xe0, 0x4b, 0xc8, 0xb2, 0xb4, + 0x66, 0x92, 0xd8, 0x0f, 0x65, 0x4d, 0x16, 0x30, + 0x6d, 0x2e, 0x3b, 0x64, 0x22, 0x36, 0x02, 0x43, + 0x10, 0xa8, 0x77, 0xb1, 0xf2, 0x8c, 0x29, 0x8d, + 0x23, 0xf2, 0xc6, 0xd4, 0x5b, 0x2e, 0x78, 0x15, + 0x8b, 0x5e, 0x0a, 0x2b, 0xd8, 0x76, 0x83, 0xe6, + 0xf9, 0x2a, 0xad, 0xf7, 0xf1, 0x06, 0x18, 0xbf, + 0xe1, 0x50, 0x89, 0xbf, 0x92, 0xfd, 0xe8, 0xcf, + 0x06, 0x5b, 0xf3, 0xa0, 0x3b, 0xa2, 0x60, 0x0e, + 0x04, 0xc8, 0x80, 0xb7, 0x97, 0xce, 0xdc, 0xc6, + 0x7c, 0xdb, 0x82, 0x77, 0xa7, 0x84, 0xf3, 0xf3, + 0x4a, 0xfa, 0xce, 0x9a, 0x1e, 0x4b, 0x81, 0x45, + 0xed, 0xfc, 0xd7, 0xe3, 0x4d, 0x40, 0x03, 0x8f, + 0x17, 0x30, 0x8f, 0x35, 0x62, 0xc3, 0x9e, 0x1d, + 0x11, 0xee, 0x3c, 0x5a, 0xbd, 0x3b, 0x32, 0x6c, + 0x16, 0xef, 0xab, 0x67, 0xe7, 0x57, 0x89, 0x61, + 0xbd, 0x1c, 0xb0, 0x28, 0x58, 0x3b, 0x70, 0xd7, + 0x4a, 0x8e, 0x08, 0x0b, 0x3b, 0x14, 0x15, 0x7c, + 0x0e, 0x97, 0xcc, 0xa6, 0x00, 0xf7, 0x0c, 0xa0, + 0x3a, 0xd0, 0xc7, 0x97, 0xbe, 0xf6, 0xc6, 0xe7, + 0xc4, 0xc4, 0x90, 0x3d, 0x94, 0xf8, 0xd3, 0x71, + 0x85, 0x6c, 0x40, 0x97, 0xae, 0x52, 0x0e, 0xe0, + 0x70, 0x15, 0x13, 0xe4, 0xcd, 0xbb, 0xf0, 0x80, + 0x11, 0x56, 0x08, 0xff, 0x71, 0xa3, 0xca, 0x00, + 0x43, 0x0d, 0xde, 0xa1, 0x81, 0xaa, 0x52, 0x9a, + 0xfd, 0x64, 0x58, 0x6c, 0x99, 0x37, 0x9b, 0x04, + 0x46, 0x33, 0x22, 0x88, 0x53, 0x1a, 0x99, 0x54, + 0xf2, 0x77, 0x00, 0x23, 0xf0, 0xf3, 0x24, 0x02, + 0x7d, 0x2a, 0x93, 0x98, 0x25, 0x5f, 0x55, 0xaf, + 0x99, 0xf6, 0x69, 0x4e, 0x3a, 0xdf, 0x8f, 0xed, + 0x59, 0x91, 0xaa, 0xff, 0x0b, 0xaa, 0x70, 0x8e, + 0x08, 0xfe, 0xa1, 0x32, 0xa9, 0xa7, 0xc6, 0xf9, + 0x1d, 0xfa, 0x52, 0x32, 0xc5, 0x5b, 0x64, 0x1e, + 0x75, 0x67, 0xe0, 0xac, 0xe0, 0x35, 0x9d, 0x4a, + 0x09, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x9a, 0xf1, + 0xe2, 0x4a, 0xe8, 0xea, 0xed, 0x68, 0x15, 0xf4, + 0x04, 0x36, 0x96, 0x60, 0x48, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x5a, 0x2f, 0x2a, 0x16, + 0x6c, 0x8b, 0x34, 0x4e, 0xd0 + }; + + esp_decrypt_cfg_t cfg = { + .rsa_pub_key = (char *)rsa_private_pem_start, + .rsa_pub_key_len = rsa_private_pem_end - rsa_private_pem_start, + }; + esp_decrypt_handle_t *ctx = esp_encrypted_img_decrypt_start(&cfg); + TEST_ASSERT_NOT_NULL(ctx); + + pre_enc_decrypt_arg_t *args = calloc(1, sizeof(pre_enc_decrypt_arg_t)); + TEST_ASSERT_NOT_NULL(args); + + args->data_in = (char *)cipher; + args->data_in_len = 521; + + esp_err_t err; + err = esp_encrypted_img_decrypt_data(ctx, args); + + TEST_ESP_OK(err); + + err = esp_encrypted_img_decrypt_end(ctx); + TEST_ESP_ERR(ESP_FAIL, err); + if (args->data_out) { + free(args->data_out); + } + free(args); +} + +TEST_CASE("Sending random size data at once", "[encrypted_img]") +{ + esp_decrypt_cfg_t cfg = { + .rsa_pub_key = (char *)rsa_private_pem_start, + .rsa_pub_key_len = rsa_private_pem_end - rsa_private_pem_start, + }; + esp_decrypt_handle_t *ctx = esp_encrypted_img_decrypt_start(&cfg); + TEST_ASSERT_NOT_NULL(ctx); + + pre_enc_decrypt_arg_t *args = calloc(1, sizeof(pre_enc_decrypt_arg_t)); + TEST_ASSERT_NOT_NULL(args); + + esp_err_t err; + + int i = 0; + do { + uint32_t y = esp_random(); + y = (y % 16) + 1; + uint32_t x = y < ((bin_end - bin_start) - i) ? y : ((bin_end - bin_start) - i); + args->data_in = (char *)(bin_start + i); + i += x; + args->data_in_len = x; + err = esp_encrypted_img_decrypt_data(ctx, args); + if (err == ESP_FAIL) { + printf("ESP_FAIL ERROR\n"); + break; + } + } while (err != ESP_OK); + + TEST_ESP_OK(err); + + err = esp_encrypted_img_decrypt_end(ctx); + TEST_ESP_OK(err); + if (args->data_out) { + free(args->data_out); + } + free(args); +} diff --git a/esp_encrypted_img/tools/esp_enc_img_gen.py b/esp_encrypted_img/tools/esp_enc_img_gen.py new file mode 100644 index 0000000..62c257f --- /dev/null +++ b/esp_encrypted_img/tools/esp_enc_img_gen.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python +# +# Encrypted image generation tool. This tool helps in generating encrypted binary image +# in pre-defined format with assistance of RSA-3072 bit key. +# +# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import argparse +import os +import sys + +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.ciphers.aead import AESGCM + +# Magic Byte is created using command: echo -n "esp_encrypted_img" | sha256sum +esp_enc_img_magic = 0x0788b6cf + +GCM_KEY_SIZE = 32 +MAGIC_SIZE = 4 +ENC_GCM_KEY_SIZE = 384 +IV_SIZE = 16 +BIN_SIZE_DATA = 4 +AUTH_SIZE = 16 +RESERVED_HEADER = (512 - (MAGIC_SIZE + ENC_GCM_KEY_SIZE + IV_SIZE + BIN_SIZE_DATA + AUTH_SIZE)) + + +def generate_key_GCM(size: int) -> bytes: + return os.urandom(int(size)) + + +def generate_IV_GCM() -> bytes: + return os.urandom(IV_SIZE) + + +def encrypt_binary(plaintext: bytes, key: bytes, IV: bytes) -> tuple: + encobj = AESGCM(key) + ct = encobj.encrypt(IV, plaintext, None) + return ct[:len(plaintext)], ct[len(plaintext):] + + +def encrypt(input_file: str, rsa_key_file_name: str, output_file: str) -> None: + print('Encrypting image ...') + with open(input_file, 'rb') as image: + data = image.read() + + with open(rsa_key_file_name, 'rb') as key_file: + private_key = serialization.load_pem_private_key(key_file.read(), password=None) + + public_key = private_key.public_key() + + gcm_key = generate_key_GCM(GCM_KEY_SIZE) + iv = generate_IV_GCM() + + encrypted_gcm_key = public_key.encrypt(gcm_key, padding.PKCS1v15()) + ciphertext, authtag = encrypt_binary(data, gcm_key, iv) + + with open(output_file, 'ab') as image: + image.write(esp_enc_img_magic.to_bytes(MAGIC_SIZE, 'little')) + image.write((encrypted_gcm_key)) + image.write((iv)) + image.write(len(ciphertext).to_bytes(BIN_SIZE_DATA, 'little')) + image.write(authtag) + image.write(bytearray(RESERVED_HEADER)) + image.write(ciphertext) + + print('Done') + + +def decrypt_binary(ciphertext: bytes, authTag: bytes, key: bytes, IV: bytes) -> bytes: + encobj = AESGCM(key) + plaintext = encobj.decrypt(IV, ciphertext + authTag, None) + return plaintext + + +def decrypt(input_file: str, rsa_key: str, output_file: str) -> None: + print('Decrypting image ...') + with open(rsa_key, 'rb') as key_file: + private_key = serialization.load_pem_private_key(key_file.read(), password=None) + + with open(input_file, 'rb') as file: + recv_magic = file.read(MAGIC_SIZE) + if(int.from_bytes(recv_magic, 'little') != esp_enc_img_magic): + print('Error: Magic Verification Failed', file=sys.stderr) + raise SystemExit(1) + print('Magic verified successfully') + + encrypted_gcm_key = file.read(ENC_GCM_KEY_SIZE) + gcm_key = private_key.decrypt(encrypted_gcm_key, padding.PKCS1v15()) + + iv = file.read(IV_SIZE) + bin_size = int.from_bytes(file.read(BIN_SIZE_DATA), 'little') + auth = file.read(AUTH_SIZE) + + file.read(RESERVED_HEADER) + enc_bin = file.read(bin_size) + + decrypted_binary = decrypt_binary(enc_bin, auth, gcm_key, iv) + + with open(output_file, 'ab') as file: + file.write(decrypted_binary) + print('Done') + + +def main() -> None: + parser = argparse.ArgumentParser('Encrypted Image Tool') + subparsers = parser.add_subparsers(dest='operation', help='run enc_image -h for additional help') + subparsers.add_parser('encrypt', help='Encrypt an binary') + subparsers.add_parser('decrypt', help='Decrypt an encrypted image') + parser.add_argument('input_file') + parser.add_argument('RSA_key') + parser.add_argument('output_file_name') + + args = parser.parse_args() + + if(args.operation == 'encrypt'): + encrypt(args.input_file, args.RSA_key, args.output_file_name) + if(args.operation == 'decrypt'): + decrypt(args.input_file, args.RSA_key, args.output_file_name) + + +if __name__ == '__main__': + main() diff --git a/test_app/CMakeLists.txt b/test_app/CMakeLists.txt index 8e9a679..41475c8 100644 --- a/test_app/CMakeLists.txt +++ b/test_app/CMakeLists.txt @@ -2,10 +2,15 @@ # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.5) -set(EXTRA_COMPONENT_DIRS ../libsodium ../expat) +set(EXTRA_COMPONENT_DIRS ../libsodium ../expat ../esp_encrypted_img) # Set the components to include the tests for. -set(TEST_COMPONENTS libsodium expat CACHE STRING "List of components to test") +set(TEST_COMPONENTS libsodium expat esp_encrypted_img CACHE STRING "List of components to test") + +include($ENV{IDF_PATH}/tools/cmake/version.cmake) # $ENV{IDF_VERSION} was added after v4.3... +if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_LESS "4.4") + set(EXCLUDE_COMPONENTS esp_encrypted_img) +endif() include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(idf_extra_test_app)