From 1d9f9b05c640354007e72d338ecca4fd7d5cb54e Mon Sep 17 00:00:00 2001 From: Harshit Malpani Date: Thu, 10 Feb 2022 18:07:26 +0530 Subject: [PATCH] Add esp_encrypted_img to component manager --- .github/workflows/upload_component.yml | 2 +- esp_encrypted_img/CMakeLists.txt | 3 + esp_encrypted_img/LICENSE | 202 ++++++++ esp_encrypted_img/README.md | 40 ++ esp_encrypted_img/idf_component.yml | 6 + esp_encrypted_img/image_format.png | Bin 0 -> 29043 bytes esp_encrypted_img/include/esp_encrypted_img.h | 105 ++++ esp_encrypted_img/project_include.cmake | 18 + esp_encrypted_img/src/esp_encrypted_img.c | 448 ++++++++++++++++++ esp_encrypted_img/test/CMakeLists.txt | 6 + .../test/certs/test_rsa_private_key.pem | 39 ++ esp_encrypted_img/test/image.bin | Bin 0 -> 12192 bytes esp_encrypted_img/test/test.c | 306 ++++++++++++ esp_encrypted_img/tools/esp_enc_img_gen.py | 124 +++++ test_app/CMakeLists.txt | 9 +- 15 files changed, 1305 insertions(+), 3 deletions(-) create mode 100644 esp_encrypted_img/CMakeLists.txt create mode 100644 esp_encrypted_img/LICENSE create mode 100644 esp_encrypted_img/README.md create mode 100644 esp_encrypted_img/idf_component.yml create mode 100644 esp_encrypted_img/image_format.png create mode 100644 esp_encrypted_img/include/esp_encrypted_img.h create mode 100644 esp_encrypted_img/project_include.cmake create mode 100644 esp_encrypted_img/src/esp_encrypted_img.c create mode 100644 esp_encrypted_img/test/CMakeLists.txt create mode 100644 esp_encrypted_img/test/certs/test_rsa_private_key.pem create mode 100644 esp_encrypted_img/test/image.bin create mode 100644 esp_encrypted_img/test/test.c create mode 100644 esp_encrypted_img/tools/esp_enc_img_gen.py 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 0000000000000000000000000000000000000000..5882211ae9cdcbb64cfc5106799c4d0c0d0a6461 GIT binary patch literal 29043 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 bb5ZhyK&@nu{sZ_jMK*KO!zND-oQ?bsRXXgP literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6d4bb7cd3d77c842f1a6103a6a4977b06cd80bcb GIT binary patch literal 12192 zcmcJU1CA&P0tCmlZNIT?+qP}nwr$(CZQHgz`!BI|g-$A+)Ld&gI45&sm+b_Zqxp0U zF_qbFQsr4Z|1^Ga%F4w!YaxvaYe{q%nfJtgL6Y}ZY`RvmLi!5q_Ap=J^MefqT-w&Y zt;K89KX|##LYUV%0$lb{Wg2XMPLdogjDM5{z=#aLR{e(xkkCs zj${kkt6_gb{%XHN;-uU(fXxB2PiOB>5F`G_v8L&m{WGB`C8h8nm#sx5oe#%nVM8Qb z4_r6+HP4VNENo?bkbjG8SBt^zQ)Nv3O$q&;vEzSQ^%tCO0Je=p;zS@-mx+@aKtc zrl*wW1L4cL_R9gr39t+wP!{X~oj|o2{%AbMf6NY*8HsFDg&|7CqrRs+@`IW5tFwgY=y2GAJRKa;%gi2(jjxbU1L z4r06PlU)H#So1b)+#zDi=azX#_rEjHw8;(2ZY}QUUxti_b zT97{*JkqEYd*r)F1AS*at4agp-%&N+b|L3f*ar8=BDI?;$#JfAFI)x8YR!+};s$l4 z^)&9upn(V_Pu3aKNUl}#811x9;FODD5@ZzNI-8!$!&75LulS~)Nkvks0Ai-!B zTepKEd8FqfTK^HP{+{%>aCGV_-~2uL{dk?BO;c)r3y-9Y^X{th^fac@c-s^8Jja)} zOw%%|`PFQ5QZ_{uk*t}q;x+i%szkevBS%Pdbr(@3TftK>bn_n!a$263iBt}(iO4t|WFkBYSYF@DJqu1L(ng%eR zkvCE6Jg-^dzAM@_68c@lyk486vjiu#+@@;?FRT-P)40dC=pJO{7?{j-B=MK3U)fuhiFVKnMW{+&nhq0bQDsN5W-%t4f! zk#i4y=CYlPS2X(~U$9s$mld2N2DzZjKkIoztdRerd!HkIepT$aVxT?z)2=z$X8y@n@lbq_OpOs+^@}50e4>4m zGA5SQEzE;Ds458=uQr9Cd#hPT)sOW;_4jtE*xXA&|H+)RrhgVhsOYw^}L2sykAB0 zzxp<81E?q%4AK_1@G8h0FPW`MPUyt4p3fLPpK6@_my_%QCP?X4!#M@)WJqK9D6ocE zsSoNwTJkw zQrOB|e&6Wy#hP&R%H^^;^)1i;A9ZdVOG09!E`B5P=i5k^obOy>qu3H{=SYQv^90tOS>Zd7Js8W2v#M3&AY_vud5Yp4c$gCLY0E zd_Ag-F$IE`UJJwWn2Wg#+0cO!uvOmL;JZ|EfDsx-6AQU7mFYG!h2QMryKP#2KL7^G z+Tqf5JRIz@y1&j0o{n=vP0dx+C0{7Vsw6_Az(0K*`}6vZ4rxHB194(X3{1vAGh|(V^DxRgP*=k1p#cmiUa5wwDPx*` zBqsSvHpBIZd|WSjhUO@?Wi#GRdxX>YlvL$HSiYW1kl}Bx(`qUCc>fNJwXD)tH8O32l7kzk**rjz za=Nt4d$H6>l&=#O^=R+BkaRoyBP}sr6s(Po(ZIbXXg#Z+n*AuGM-BJaJk4|Dx-hV>F@pXp-N)3`cd*bg=!`Yht`ANRN=cM{pFmi zMPo1tkg`%hE9^0-x#g7Ob%$}r$~4uMEnF$@o>+>Vc5$3XbjURMGI)s6QUOM~%4aWm z^2hm<7x(2gOKMqq{ObY!8f_DZSefHKv|D}mZctkew13?#pa4iK`m;HvH6*Cz*JV`# zstiM$w0-YXv{~X%Mpc9|F>q;k3~-F#)6Se6#KH*+92RI-*4i<3H_H?#s|`d!(EE@= zMbEA`8e{rMQhNqh=%mO_Ln*5zT{#QpgB;gB|=3c;;y@0XXggkSWEFvaZ3i!>3_*D zD^>$6eqhTxUO&}DL&&AnWvtSsA8^IFg(5#3KM;)ng4Lgl^;v*I?=3k0(2#hStPk`8 zxHQ`&E>T2+;aYQ^s0gtH_#XE)?87J_qdxu+_PnxP*Qs!lo93O&$db7WeYwdgW}ftK zCBQNPiPD2aLHZ-?xC7+1z^5OTMC&yyBrCgpS78R5pWest^eO}2*sh*feIGFcq6;p5x*cqn6+~j_ zj9wvPrH#;0cHzG(*a4_`r0m?2aXq&DXf!S4qX|P58+QGqC8N$|ot$v-hT1B@6Y{My zIEX`q1={Z!R&>qeW}`)ysM5G?xLc4b%pneJ%G5RC2X<{G^HF)&D$~wut5a}GX^?t9%0s5n+Aa`&?BF`dm8;`GAQ@_jiW+NV z+-Mg4hL8c$5BO`t%uixxgidngh2GGvB_070cbtozGYq>nc%aExA{X3St*h)QL4B<*3^TLn0@C4Xpz zx*sBh;D#jzeNodd-3~})cAMAi-GmB;;n|qD`y`!m8B0vl!aV)qlntHd?g``}f%uMA z6z;l8Eago7E*)cykZ+QP@EY9at6!v7W@$PfZp_sz=xuZ>YhUSvp-K6@#X-zyKUiZ7w`om`k1pLL}T4@UdcP~iDG98^^>%IXGPYPV>- z?{t^RVpU2_eU=-zz}wpHb$rkMWVK>q* zFGx_C)qsMqQIQ7eq0=j}m@jnnqhFC98G|Dx&^x`{bVZij(L1P}9DAp;pSR|L>}}T7 z68dBXABQ@hS7HWA;qODdjMzB|cfnCp;{pX_M=B8bb6qd`=_8X6_m#)L&QxO9`lvk( zM=_uUBRnCXR0#$?FmNv-i4#pd8!H>&o18S_P(`%*z-JQ_H*S?@6w7(RG`H;LJX4&$ zdIarxVObfI%1@yRG(XHp-Lz1r-#K$w^975-TM|F-49M$vaGcBobTJgTh}DanMcU%c z5+Y)V{$1zl6(Eg3D&;Z>s~NPMjVFq`@1A=ZR=vbDqE|-@#7<2EcR?%l+|Wa8Wcmlb zrkJzMi>zduv-%UI-9CiY)$~bh6~`nf$3?VQ6S&-~#C>-Q5hfv@(~FVeGRylAI}X=~ zn(B)`J*TZY>RliV-S^=lu`LTP?1jjcv<3J~kFg6NRGPilsx(&mI##rmb9SZ8w>;YT zTw}Yv2>1LK6>jitGI?EsjRAIouE0@}Sg7JK+f&#sshCKPzGV{QkWYp}r5U-<_-pGV zRU1ktZI?VVK4M1N7eunAgq(xc>I747UNXY?GaV(-`bLE80w0RPV0NKR!62U=f?j7v zNORi&v%YWkLxT2_YdR%I4LSbevS43Sq24qWoDYx88THJvVEdmFxmE1fWmPbQujr@wgc? zHcHckPoE;NQt;Va2lwL;utES0;rJNFy(FUHv=v6}$`z0R4oEgJUlic$Y$S zSl?IounM;!;p6nI)KZ|;&Hfd#u4(_P3yN5*EOA?CG*~!S4swKbJ7a%UN{^adiCYuv zG!afz3U%+~5c1y$-~_mM5^83dkNTb}4BK3kun%$#e52*eH=@?0hBlNix7IisY(ZaO9d@r&wI`S*1(-7 zl@Sjc+-pTn^~nDr0ue?s+LMLU^f18_W{4CiInp zm$Mgkk)zC5Swm4)8yk2A^wimXQ%4b?boxD5ccOJ!)gkD(URT`Knf4~9 z3L2ujT?l+)@+Mkm>e8b`=h1!^eWaCzdLjM&*8Jy;p|Jf8XCg3XG1N*GZJ*~hJWx>Wv$Z7j1l(2YlrQ_Vg!UyMR^kBX zLvu)(ie>|V+kKJxD;D2L5xN<{uK|L;&dVG z2|{P;9v_T(so;jiq&!vzM{Mc;3RhKCIk#@B!^u`o4gbRDz5@8Xx5L$_FUG4Mf3ys$ zew3V_gXfOo$AQuX;(&yRG)rc4Bax&1i))UErbXHR5vOq6E3>=%0E;zHm-);KJ+jA< z?FF)xhSkHYRHgkMtRG=OR5a~`|70&Y99%LO%%MCGXUgXnEs;?=`|G4k>yWo_rc2YG zbBiUJ+)c@D4gw2cxmam%^dDML53M}1tQ?wTg;{+{X5h`Da4U^)Rlcl^EDX%S@@Y(@ zv#bc~YZy&FLwLW7#F{kTYWXz*Ifcp>cCmwGzc#H^$lX$Cs?8WK)dngI>yzG4atG3G zv8)R}j@lGuo$xLADh2#~^}D=iP?hw|8cDl>C&|65AswsS?qZlaDUG4}kh4TT3KH|i z$Xm&Iy5{c`St0g_MX1-AFS&D*yv^}38t7^!+v zBd%)0$F;+f(_BuGE0gBDLLFiJ7A_#!lUleHGGa8DUu@3D!Ea5uJd=hS*u1(dQE7&7 z$1xt^v+yz2{j?5714CWAL)*xSJ#Yrx%z=weZj3Rdj#Lp;4Dt|yU)Q=p4eLYAj*g2+ z|9geU&`en2eu^VK;3zI82Rnc6Sp7UB#^tNfyLon2H8+3@EDxGWwwrNi{n6Iq+vpA5 zn`L+7SQY5H(q`{$?w{TOxJ1nF!H?X+qwyW1^cVWZE!2xYa_ZAe4Fi~ZbQrJYp8~Hf zM?G0smX!ko>DdMwRXkqO-dreja}lFoyzC_2F6hO) zcxxGteNSTvt)?3nTDEouQsw2Zu5B3FF`X+#%8u<9qbnr%5K>Sc`@jTbqVHMX&+}pPp6HlOd3Wl z|9H}s&yZdc_l@X($|659gMAFB=ktGlTcxN8kT zB#!rp&@h_W=G`||I18jqSkR#5z|$>CW`=HACowF>+yyXN5Sd!O-PuXil_LY{HNGre zmWz8vTIW#hoZR`;D(O=MxC_xE7vj?_y?%Yh&}X-F{J!`X@7Ro@DZbo9WX#+;Ql-^u z$%~LZ4W{dpv$%MR zEy%225$E1M0sF8!eqp4pAgt}m$*fn5fsY+{bO=*Hos+5CX6G4QlmPGqx`p)7J6Gf9 zWFHN8>8>KpPQQ*ZF1@`PH@MZaTx$*bU3%{hNv~KS6?Yn5{RN0IxpZMuQ+5Afv~vq? z8>gCb2(CVrOG(7tA^?=P!TIA>AbL)ISi-5!-R$2j{c%}QF88=0(uEY=)6bp^K$>-?ed<4bPaYLP?$|&t$?(a;m z+Bxk_0^3ielh(=?fCtH-_`bL_H5$X*E0B_HLnEzZTTpZ(K`fsVk`j8Xv-eCwZ1GEc z@3R6;slB@LEEXt{XawGdB;SQdP34&UxnaXLB4@cI<-Lz{kHboHm@QD~>+8r+p(;6p z;G=c+X4*yGSy*vPXPXss@^7nhXQ}QDSCO=53WOkF+T9JcrespQq|mr2yvHH26~Dt$ z;gAu6laDrtPb!>6YGDz}Qbpw)u5sVs&pcam3L?Wjhu+Z;GQDQNxLz1BaQ zFpieiYr!4Ija6pxn2@v8nRJtNGB{I%XiH3K(mzHq@81#^OBzq~FF%%)k4X8(ubJHW(XZNOpn?SDjv-IWRz1(=R?eDzwGxg0a2jT zfCTg2%Zf)$QcW@#OQTq@pivTro}ij_|At}peY3Xy*GrNple4kkIXa)Tmb%n@vy3Qo zIbg6rB?zj(Hw7`%O+9NRMDlhyy{+pSn0DNeZX{HtX2s=ty1dnWM+0`CuHTP!sOdyV z03Q^}KhG>_;nl#b{Y^uI)#_v*MU-zmlf}Bp8QS`k}q@bV%p84%xmd@Mz zqv8aG@e|AoWtjtvN3`ZO>hcSsV>slE+gOu(8{!lSB=#yNUB0_)+)^kCmavm85TKun z{@jBC-C+?WU!Byg>F_h9!V+Xv=;`S`{l3P0SFp=gl$7aKgFKD5$=Jlv4WM z>htc#+D=8&r4h&QhIzJ^rIF~i*%1>8#71nQgLQY9t|#ImK*-;5;0BnT7mmS9jHr9_ ze-y1fm}F1D=q@L7v(LT{Ehi_JDCsv$P zI_Kg5Sw>&GlW*6U|hro3GbN{A~h7kO_suc`LAv_uASvF+!!z! z1{W%QY3jyjS$N^UlXCUnriS*9P?G`evmqXypAu`TTX7h7RI-ak_RdsS#Io+0GN=Hh zKJ(eF_+hOX9wj_oc%L-)*wfjxuy?J&EMw52x}K`58l1YwK)bUDj=Iayk@{WU)l&Zp(MQF1!9AZZF1%t0e=vq((L6(W^2iUU{MhHi zG8gzSk=X0oigyCCPy;Wi7td(YO5-|Z(NbXH@62n9tHJ6G1i`KJxTKAy!5ZJ$DhA4w%Uh>=1!3?5F}Gq=UZzS4$`xS5D)EW^DBuWdgBRa-c5E{dH3m6Jo$ z8RW(68sSr*xgR8qPe)^r7Va9wMY6LLd1?M>*vYx59Q+~d$u^L_18Vuzgx!XZAi-R=U@w8 zQSrgDA}~CzLmogxdj1CJuRiFp!X{&0LQ6AlSgrm?!#l^)KwHFoJT$m*BpE2n2oI0E z!-Ub?`?jBC8z>Y$R|vuHZ;eX^hO(GfoK-?UmyHhA{ti1lU=>ObPp_hdK^nt z(#)5lsbx(o>sbF$-HH#|_Ar2UFloz5V{_X2Gh<+v}F!6cNjb!RDs=WRomNEu3SDxSOHs=a}^Fc zB>}3E)m16*r}kv9jg72cGJv#jAy=X5!!Ua~ec(oKk<|Agfo)6zb54qc`Ni}cGU75* z!n;|}6MTWUr>`SEeRmzG-;ZGdMM8r!*H#djw>aG-pL6AnL`LICD`}pxh(c`VSi?ayChW^wf%;KhyrFIb1(q}Mjd`SiM1Ysa-J#}_J z|E~Qi-4RD^ObN8gKuOjCxba`H$1lrVo!XxbIXk8`XENQ0C4O#g3Z!X&ZS|s|*6sV9 z66g?spKr!v9Z}875(z+9kl1i_YktNN)>I*71-ilkOH9*aN*Stm`QIrlJ=y_VelFPw zB^VSoWq^-O1CSn@ZU0p8EwVBg~iEgqM&b)5IZ27SxY`u+nDhn#xMaLwPG~ebV z6+XWPjOR0Ur{jPb64T3*;QlfIBUhj|#f{XCLjosfMC7Gum|HiZ4Yhl&u!cDxKE}Mq zGdA<#JEcUO^V!uEM)vV);}dv^R=YLefA4}{3&|U395W68Y$Z@gk+>tqmi6TDY6?9; z&{|JmbV@t$S_j^7uq;SGA5Kz-uwku;*V*kWYUMY3ub1<@dT>xw;y&>QvE=^)6*>vT zX#9MRg^FX<<9!MroK#oZ1*eG@7w!^3gds98{MCnX`pRF9vW1 zPsgcPq3?od?atN|LA-d_LmMzgI802P4w)#vKi855w1RO#-fJR&)SfYY>==C`q_@L3 zh27=4+}vVpc}|><6V%lN&V5dCR10sQ=(-s}_c(A1&0=!^^8M%N8K_tBR199(KmYP# zN_V@N_3J^KsnG#onO}`GD1JJl)=%-e!7o~+6wuU9mz)ZFszzq&y9@I^#;@a7F4<8T ztE)~w^+{l$Cm&LgJeq;MBn6@Uoc|%KJ=Mab-$#+`vsW>N)x)TU+z;GrDt@VtTxWWo zr^?1UnSl4PNs1wxEv3#u;w!XlXmn>1c=je|(0*|PXyT>U_dL8pf<vTkvLJJt6MM znu5hfnulZ7de>hXRhm}352WQ^szTne`MBp9pq#ecQjkfgZ<6}U zanitki9ydne=Q4|@-rjWmJJGgo3U`{rZ63C6laD(Z38BiEk9>upGC61uL~gR7p6_& zaO-|UUj+-4?tiV+8l`mU3RVOqO|ihB9)aEv#e$1kTNyr^@|lrN7Nw)J_WS&X#_WMbRSTMQ}}rBu_%dk*W5rf(&Cie%n9Os+TlWee;}<>V3=_Nr#>slO*m~=P}FCL zU%QE9wv4K=fuw+H`*~8t% zZ!JTKB#}0{$$|*JR8>2PeaL5h=GKrL+y9{ef~v3w)KCf~X;GcQhGI63@Zn}bP_rte z4LK4Ii*29=gj-VcX<9-z3+w_;aXhI@Y1Md@MG(3E2C9}YFU?cxFps&9?!(n`_t;j%t1rQ$9W67>y5YGZ-qYZYhDnJ&MYQq*#QJ}_- zeKyU89Dd?nmFQ_fL2gsoVx0FRI$gq%=3V>y2uk=rF{cY>z^8{Ef%)@kKDfZPrOXm; zitz*=Fh%@Ed+xd;w8{nyKyy!XvyDH*;i68m8&f{d6JZ5f zmBtoIlWR{r>}|I&^$LjpmeotpCw=b}^f2~y(vDNul|Kp14P6COw)tRH0_=o5C*p_} z0P_3#(0;Kcv0|}$mVIE77xNUTUPfu`bn7q z99xM+KN?$tCRT)(y*M=m%>iK}nGSF>a)|J>cdtK;vd}c3POO45wO ztR$9XgL$GswA{kJbPkFPBg01y!Y_qY;4_x^M7N=xQGoR zbFi5)qF9H;nla!j9VdJ!tk(H*{>txzRg33<=Xs+f3^b8?5Daz)fUV$j*o&FEPOXb{ z+ZdTdIbwd-4|hHR6PVaP?nobW_NR)ec(jbcRaLM0K`GHxm9_;TsN#6;ilujY0luZK z3`+(AznJ%9qsz*t+0T6C>EBrwfbOhjBi~1@kk?~qyHZ4=p4IuD| zM0-=%h+FlyXX)ryBt^$^n%&VDyywd*2t`qi`F+p{<(_dxf&mtpeu;@TFAGhkPZ+tA zLE7WO!(m_3pUs2cx{((ErC$B&G524OfzB$Jw2oe=QyKv+%f60JG8zS)RYr}waF7Fz z9u%YLnHgZto!1ZU+>=~i2vsg*!|JS?OcaHZr4mv0P#umd0Y9hWp|f)QnM)p~JWwW#$OqcP#Y>I;N@ z=i8{+)X5Jz_ozy!y`Z>?+T03&h&U!o5se7xkJ*7Qrs2Z5Z_d63J6x zK1lIbz7<=vDZ;9e#~<@0zIny9&7 z!}N+a;JwirXmFoQf#@{5OjXQm>>ZHYol_=4e(`14xJgJ^t@_J4E3Kde0pjt-LXZ-| z$+2O^cjG4|ao->z{qQ0&+ORE~&F-R~c^tB~MK%vdKyE~MZjWo3oOYHYiZm1A3fDUb z8S09~B5L5&ni$v{RPj9v$E(5C#2iswnu>eBL2C3y^`tgxf#}EmXq=K2DY4yI!uOph zKz9@X0|llJ%K@%D|A}GGYdtKGT6jSlGYJYZBwj65_61b?!v8C3dQ$k@EfpxN{V|z$ z)#L5+Wn$SQXws-sCy%VMt*dZ7YPcC!xyl}{2?&8AVz5w=Caz31+v(zJBVwyn-Vuj$ zW7q9UP!B*)?|_!gh)E5^5ZLb~>H%mM$6Ul&SaSg5%H{mbY%fP{VEEI0NcsVEdeeBX z?Tin||DK6!SyW~`MO;T)#i{^k+13KfCQg4qIu=&u07RXyrK%AC*%*EP%prrRxd{0V zp7I;o?vvJ;$BvA=1}&_lZcwzro@ogtfX&=@q#o*zVu{|j5}b0Nrw;r*Up%=I|LZUB zVU@jsW~VjU2>gtjGRd4G8eA{+RgF=y3UTdBCT8)phbFpi;)QAMq_Y?kfeiP+ctQLv zu)M0T5BWMgOjA#Mb6LJmv=ebw!Iye+wI#7R{|yZnO^Xve3=y|8g?VDvny+7w1w6yN zB{Hs#(92uw0kBwqiS+4ur6S%o49Rhv%zA}mdQf2r{Aqerfym=2Imu>N zQ8*0Lt~36a6pSb$})))h#Huk|=iQP%)qt z0D9CznQIdzMn1&wXP97epeS1iy%2foho?)3r=+wwXu_^k#3rkBrp?-}+W4D^z#LMi zK91NUDW}X9^$tUk?+R{PEiT#DkO_1;|Vd9}cX zIUAAJ^=~d)<(B2qou->Y6$~f+&Rn`>pNku9wHO6Lv?7!fqMWj=&`>_{IAe51KTf_S zZpS(r+g0e>On%nEpm5`JbXfr|x0qyLf4)RLzFSus$Xjj5w+cqx_KJZkk{i7g82_@t jTJ-PSdZBO{Bux4ZS?&|GlrKO~1ki;1wl6x7YUu5MQ<1Hi literal 0 HcmV?d00001 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)