From 04b2fdfc6f7259116a50170b0d38dab9507e20d3 Mon Sep 17 00:00:00 2001 From: Shubham Patil Date: Wed, 7 Jun 2023 10:45:07 +0530 Subject: [PATCH] [ESP32] Support for using encrypted app binary for OTA upgrades (#26978) (#27115) * [ESP32] Link wpa_supplicant library to libchip * [ESP32] Support for using encrypted app binary for OTA upgrades * Changes in application code * Guide for how to use encrypted ota * remove ota configs from sdkconfig.defaults * Added few words to wordlist * changed keypair to "key pair" --- .github/.wordlist.txt | 3 + config/esp32/components/chip/CMakeLists.txt | 8 ++ config/esp32/components/chip/Kconfig | 7 + .../esp32/components/chip/idf_component.yml | 5 + docs/guides/esp32/ota.md | 54 ++++++++ examples/lighting-app/esp32/CMakeLists.txt | 6 + examples/platform/esp32/ota/OTAHelper.cpp | 13 ++ src/platform/ESP32/OTAImageProcessorImpl.cpp | 123 +++++++++++++++++- src/platform/ESP32/OTAImageProcessorImpl.h | 19 +++ 9 files changed, 236 insertions(+), 2 deletions(-) diff --git a/.github/.wordlist.txt b/.github/.wordlist.txt index 4b91d6cb46ff81..97778a6b01f282 100644 --- a/.github/.wordlist.txt +++ b/.github/.wordlist.txt @@ -355,6 +355,8 @@ DCONF DCONFIG debianutils debugText +decrypt +decrypted DEDEDEDE deepnote DefaultOTAProviders @@ -1146,6 +1148,7 @@ RPC RPCs RPi RPis +RSA rsn RSSI RST diff --git a/config/esp32/components/chip/CMakeLists.txt b/config/esp32/components/chip/CMakeLists.txt index 3d1cf673d0cf2e..97c4df56041493 100644 --- a/config/esp32/components/chip/CMakeLists.txt +++ b/config/esp32/components/chip/CMakeLists.txt @@ -380,6 +380,11 @@ if((NOT CONFIG_USE_MINIMAL_MDNS) AND (CONFIG_ENABLE_WIFI_STATION OR CONFIG_ENABL list(APPEND chip_libraries $) endif() +if (CONFIG_ENABLE_ENCRYPTED_OTA) + idf_component_get_property(esp_encrypted_img_lib espressif__esp_encrypted_img COMPONENT_LIB) + list(APPEND chip_libraries $) +endif() + idf_component_get_property(main_lib main COMPONENT_LIB) list(APPEND chip_libraries $) @@ -405,6 +410,9 @@ if (CONFIG_ESP32_WIFI_ENABLED) foreach(blob ${blobs}) list(APPEND chip_libraries "${esp_wifi_dir}/lib/${target_name}/lib${blob}.a") endforeach() + + idf_component_get_property(wpa_supplicant_lib wpa_supplicant COMPONENT_LIB) + list(APPEND chip_libraries $) endif() idf_component_get_property(esp_netif_lib esp_netif COMPONENT_LIB) diff --git a/config/esp32/components/chip/Kconfig b/config/esp32/components/chip/Kconfig index 28161878aa0125..2fd6bd770ef849 100644 --- a/config/esp32/components/chip/Kconfig +++ b/config/esp32/components/chip/Kconfig @@ -183,6 +183,13 @@ menu "CHIP Core" help Enable this option to enable OTA Requestor for example + config ENABLE_ENCRYPTED_OTA + bool "Enable pre encrypted OTA" + depends on ENABLE_OTA_REQUESTOR + default n + help + Enable this option to use the pre encrypted OTA image + config OTA_AUTO_REBOOT_ON_APPLY bool "Reboot the device after applying the OTA image" depends on ENABLE_OTA_REQUESTOR diff --git a/config/esp32/components/chip/idf_component.yml b/config/esp32/components/chip/idf_component.yml index b9ce7591b88dd6..9f0198e920706b 100644 --- a/config/esp32/components/chip/idf_component.yml +++ b/config/esp32/components/chip/idf_component.yml @@ -10,3 +10,8 @@ dependencies: version: "^2.3.0" rules: - if: "idf_version >=4.3" + + espressif/esp_encrypted_img: + version: "2.0.3" + rules: + - if: "idf_version >=4.4" diff --git a/docs/guides/esp32/ota.md b/docs/guides/esp32/ota.md index 0217514523efd0..080649240ba594 100644 --- a/docs/guides/esp32/ota.md +++ b/docs/guides/esp32/ota.md @@ -54,3 +54,57 @@ chip-tool. On receiving this command OTA requestor will query for OTA image. ``` ./out/debug/chip-tool otasoftwareupdaterequestor announce-ota-provider 0 0 0 0 ``` + +## Encrypted OTA + +ESP32 supports transferring encrypted OTA images. Currently, an application +image can be encrypted/decrypted using an RSA-3072 key pair. + +### Firmware Changes + +- Enable configuration options for OTA requestor and Encrypted OTA: + + ``` + CONFIG_ENABLE_OTA_REQUESTOR=y + CONFIG_ENABLE_ENCRYPTED_OTA=y + ``` + +- Applications need to provide the key pair to the OTA image processor using + the `InitEncryptedOTA()` API to decrypt the received OTA image. + +- For testing purposes, in `examples/lighting-app/esp32`, there is a logic of + embedding the private key in the firmware. To quickly test, please generate + the key pair and rename it as `esp_image_encryption_public_key.pem` and copy + it to directory `examples/lighting-app/esp32`. + +Please follow the steps below to generate an application image for OTA upgrades: + +1. Generate a new RSA-3072 key pair or use an existing one. + + - To generate a key pair, use the following command: + + ``` + openssl genrsa -out esp_image_encryption_key.pem 3072 + ``` + + - Extract the public key from the key pair: + ``` + openssl rsa -in esp_image_encryption_key.pem -pubout -out esp_image_encryption_public_key.pem + ``` + +2. Encrypt the application binary using the + [esp_enc_img_gen.py](https://github.com/espressif/idf-extra-components/blob/master/esp_encrypted_img/tools/esp_enc_img_gen.py) + script. + + - Use the following command to encrypt the OTA image with the public key: + + ``` + python3 esp_enc_img_gen.py encrypt lighting-app.bin esp_image_encryption_public_key.pem lighting-app-encrypted.bin + ``` + + - Append the Matter OTA header: + ``` + src/app/ota_image_tool.py create --vendor-id 0xFFF1 --product-id 0x8000 --version 2 --version-str "v2.0" -da sha256 lighting-app-encrypted.bin lighting-app-encrypted-ota.bin + ``` + +3. Use the `lighting-app-encrypted-ota.bin` file with the OTA Provider app. diff --git a/examples/lighting-app/esp32/CMakeLists.txt b/examples/lighting-app/esp32/CMakeLists.txt index e9a5418e8dda46..b37c8154a45208 100644 --- a/examples/lighting-app/esp32/CMakeLists.txt +++ b/examples/lighting-app/esp32/CMakeLists.txt @@ -37,6 +37,12 @@ endif() project(chip-lighting-app) +# WARNING: This is just an example for using key for decrypting the encrypted OTA image +# Please do not use it as is. +if(CONFIG_ENABLE_ENCRYPTED_OTA) + target_add_binary_data(chip-lighting-app.elf "esp_image_encryption_key.pem" TEXT) +endif() + # C++17 is required for RPC build. idf_build_set_property(CXX_COMPILE_OPTIONS "-std=gnu++17;-Os;-DCHIP_HAVE_CONFIG_H" APPEND) idf_build_set_property(C_COMPILE_OPTIONS "-Os" APPEND) diff --git a/examples/platform/esp32/ota/OTAHelper.cpp b/examples/platform/esp32/ota/OTAHelper.cpp index cd4cfe90dea17d..0b254f5d13e86e 100644 --- a/examples/platform/esp32/ota/OTAHelper.cpp +++ b/examples/platform/esp32/ota/OTAHelper.cpp @@ -49,6 +49,15 @@ chip::Optional gRequestorCanConsent; static chip::ota::UserConsentState gUserConsentState = chip::ota::UserConsentState::kUnknown; chip::ota::DefaultOTARequestorUserConsent gUserConsentProvider; +// WARNING: This is just an example for using key for decrypting the encrypted OTA image +// Please do not use it as is for production use cases +#if CONFIG_ENABLE_ENCRYPTED_OTA +extern const char sOTADecryptionKeyStart[] asm("_binary_esp_image_encryption_key_pem_start"); +extern const char sOTADecryptionKeyEnd[] asm("_binary_esp_image_encryption_key_pem_end"); + +CharSpan sOTADecryptionKey(sOTADecryptionKeyStart, sOTADecryptionKeyEnd - sOTADecryptionKeyStart); +#endif // CONFIG_ENABLE_ENCRYPTED_OTA + } // namespace bool CustomOTARequestorDriver::CanConsent() @@ -67,6 +76,10 @@ void OTAHelpers::InitOTARequestor() gDownloader.SetImageProcessorDelegate(&gImageProcessor); gRequestorUser.Init(&gRequestorCore, &gImageProcessor); +#if CONFIG_ENABLE_ENCRYPTED_OTA + gImageProcessor.InitEncryptedOTA(sOTADecryptionKey); +#endif // CONFIG_ENABLE_ENCRYPTED_OTA + if (gUserConsentState != chip::ota::UserConsentState::kUnknown) { gUserConsentProvider.SetUserConsentState(gUserConsentState); diff --git a/src/platform/ESP32/OTAImageProcessorImpl.cpp b/src/platform/ESP32/OTAImageProcessorImpl.cpp index a1bd313e75f378..0cff98147aa7a6 100644 --- a/src/platform/ESP32/OTAImageProcessorImpl.cpp +++ b/src/platform/ESP32/OTAImageProcessorImpl.cpp @@ -27,6 +27,10 @@ #include "esp_system.h" #include "lib/core/CHIPError.h" +#if CONFIG_ENABLE_ENCRYPTED_OTA +#include +#endif // CONFIG_ENABLE_ENCRYPTED_OTA + #define TAG "OTAImageProcessor" using namespace chip::System; using namespace ::chip::DeviceLayer::Internal; @@ -54,6 +58,20 @@ void PostOTAStateChangeEvent(DeviceLayer::OtaState newState) } // namespace +#if CONFIG_ENABLE_ENCRYPTED_OTA +void OTAImageProcessorImpl::EndDecryption() +{ + VerifyOrReturn(mEncryptedOTAEnabled); + + esp_err_t err = esp_encrypted_img_decrypt_end(mOTADecryptionHandle); + if (err != ESP_OK) + { + ChipLogError(SoftwareUpdate, "Failed to end pre encrypted OTA esp_err:%d", err); + } + mOTADecryptionHandle = nullptr; +} +#endif // CONFIG_ENABLE_ENCRYPTED_OTA + bool OTAImageProcessorImpl::IsFirstImageRun() { OTARequestorInterface * requestor = GetRequestorInstance(); @@ -145,6 +163,32 @@ void OTAImageProcessorImpl::HandlePrepareDownload(intptr_t context) imageProcessor->mDownloader->OnPreparedForDownload(ESP32Utils::MapError(err)); return; } + +#if CONFIG_ENABLE_ENCRYPTED_OTA + if (imageProcessor->mEncryptedOTAEnabled == false) + { + ChipLogError(SoftwareUpdate, "Encrypted OTA is not initialized"); + imageProcessor->mDownloader->OnPreparedForDownload(ESP32Utils::MapError(err)); + return; + } + + // This struct takes in private key but arguments are named as pub_key + // This is the issue in the esp_encrypted_img component + // https://github.com/espressif/idf-extra-components/blob/791d506/esp_encrypted_img/include/esp_encrypted_img.h#L47 + const esp_decrypt_cfg_t decryptionConfig = { + .rsa_pub_key = imageProcessor->mKey.data(), + .rsa_pub_key_len = imageProcessor->mKey.size(), + }; + + imageProcessor->mOTADecryptionHandle = esp_encrypted_img_decrypt_start(&decryptionConfig); + if (imageProcessor->mOTADecryptionHandle == nullptr) + { + ChipLogError(SoftwareUpdate, "Failed to initialize encrypted OTA"); + imageProcessor->mDownloader->OnPreparedForDownload(ESP32Utils::MapError(ESP_FAIL)); + return; + } +#endif // CONFIG_ENABLE_ENCRYPTED_OTA + imageProcessor->mHeaderParser.Init(); imageProcessor->mDownloader->OnPreparedForDownload(CHIP_NO_ERROR); PostOTAStateChangeEvent(DeviceLayer::kOtaDownloadInProgress); @@ -158,6 +202,11 @@ void OTAImageProcessorImpl::HandleFinalize(intptr_t context) ChipLogError(SoftwareUpdate, "ImageProcessor context is null"); return; } + +#if CONFIG_ENABLE_ENCRYPTED_OTA + imageProcessor->EndDecryption(); +#endif // CONFIG_ENABLE_ENCRYPTED_OTA + esp_err_t err = esp_ota_end(imageProcessor->mOTAUpdateHandle); if (err != ESP_OK) { @@ -185,6 +234,11 @@ void OTAImageProcessorImpl::HandleAbort(intptr_t context) ChipLogError(SoftwareUpdate, "ImageProcessor context is null"); return; } + +#if CONFIG_ENABLE_ENCRYPTED_OTA + imageProcessor->EndDecryption(); +#endif // CONFIG_ENABLE_ENCRYPTED_OTA + if (esp_ota_abort(imageProcessor->mOTAUpdateHandle) != ESP_OK) { ESP_LOGE(TAG, "ESP OTA abort failed"); @@ -218,7 +272,59 @@ void OTAImageProcessorImpl::HandleProcessBlock(intptr_t context) return; } - esp_err_t err = esp_ota_write(imageProcessor->mOTAUpdateHandle, block.data(), block.size()); + esp_err_t err; + ByteSpan blockToWrite = block; + +#if CONFIG_ENABLE_ENCRYPTED_OTA + if (imageProcessor->mEncryptedOTAEnabled == false) + { + ChipLogError(SoftwareUpdate, "Encrypted OTA is not initialized"); + imageProcessor->mDownloader->EndDownload(CHIP_ERROR_INCORRECT_STATE); + PostOTAStateChangeEvent(DeviceLayer::kOtaDownloadFailed); + return; + } + + if (imageProcessor->mOTADecryptionHandle == nullptr) + { + ChipLogError(SoftwareUpdate, "OTA decryption handle is nullptr"); + imageProcessor->mDownloader->EndDownload(CHIP_ERROR_INCORRECT_STATE); + PostOTAStateChangeEvent(DeviceLayer::kOtaDownloadFailed); + return; + } + + pre_enc_decrypt_arg_t preEncOtaDecryptArgs = { + .data_in = reinterpret_cast(block.data()), + .data_in_len = block.size(), + .data_out = nullptr, + .data_out_len = 0, + }; + + err = esp_encrypted_img_decrypt_data(imageProcessor->mOTADecryptionHandle, &preEncOtaDecryptArgs); + if (err != ESP_OK && err != ESP_ERR_NOT_FINISHED) + { + ChipLogError(SoftwareUpdate, "esp_encrypted_img_decrypt_data failed err:%d", err); + imageProcessor->mDownloader->EndDownload(CHIP_ERROR_WRITE_FAILED); + PostOTAStateChangeEvent(DeviceLayer::kOtaDownloadFailed); + return; + } + + ChipLogDetail(SoftwareUpdate, "data_in_len:%u, data_out_len:%u", preEncOtaDecryptArgs.data_in_len, + preEncOtaDecryptArgs.data_out_len); + + if (preEncOtaDecryptArgs.data_out == nullptr || preEncOtaDecryptArgs.data_out_len <= 0) + { + ChipLogProgress(SoftwareUpdate, "Decrypted data is null or out len is zero"); + } + + blockToWrite = ByteSpan(reinterpret_cast(preEncOtaDecryptArgs.data_out), preEncOtaDecryptArgs.data_out_len); +#endif // CONFIG_ENABLE_ENCRYPTED_OTA + + err = esp_ota_write(imageProcessor->mOTAUpdateHandle, blockToWrite.data(), blockToWrite.size()); + +#if CONFIG_ENABLE_ENCRYPTED_OTA + free(preEncOtaDecryptArgs.data_out); +#endif // CONFIG_ENABLE_ENCRYPTED_OTA + if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ota_write failed (%s)", esp_err_to_name(err)); @@ -226,7 +332,8 @@ void OTAImageProcessorImpl::HandleProcessBlock(intptr_t context) PostOTAStateChangeEvent(DeviceLayer::kOtaDownloadFailed); return; } - imageProcessor->mParams.downloadedBytes += block.size(); + + imageProcessor->mParams.downloadedBytes += blockToWrite.size(); imageProcessor->mDownloader->FetchNextData(); } @@ -310,4 +417,16 @@ CHIP_ERROR OTAImageProcessorImpl::ProcessHeader(ByteSpan & block) return CHIP_NO_ERROR; } +#if CONFIG_ENABLE_ENCRYPTED_OTA +CHIP_ERROR OTAImageProcessorImpl::InitEncryptedOTA(const CharSpan & key) +{ + VerifyOrReturnError(mEncryptedOTAEnabled == false, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(IsSpanUsable(key), CHIP_ERROR_INVALID_ARGUMENT); + + mKey = key; + mEncryptedOTAEnabled = true; + return CHIP_NO_ERROR; +} +#endif // CONFIG_ENABLE_ENCRYPTED_OTA + } // namespace chip diff --git a/src/platform/ESP32/OTAImageProcessorImpl.h b/src/platform/ESP32/OTAImageProcessorImpl.h index 5d0775c4392f13..03e1367767d97a 100644 --- a/src/platform/ESP32/OTAImageProcessorImpl.h +++ b/src/platform/ESP32/OTAImageProcessorImpl.h @@ -23,6 +23,10 @@ #include #include +#if CONFIG_ENABLE_ENCRYPTED_OTA +#include +#endif // CONFIG_ENABLE_ENCRYPTED_OTA + namespace chip { class OTAImageProcessorImpl : public OTAImageProcessorInterface @@ -38,6 +42,13 @@ class OTAImageProcessorImpl : public OTAImageProcessorInterface bool IsFirstImageRun() override; CHIP_ERROR ConfirmCurrentImage() override; +#if CONFIG_ENABLE_ENCRYPTED_OTA + // @brief This API initializes the handling of encrypted OTA image + // @param key null terminated RSA-3072 key in PEM format + // @return CHIP_NO_ERROR on success, appropriate error code otherwise + CHIP_ERROR InitEncryptedOTA(const CharSpan & key); +#endif // CONFIG_ENABLE_ENCRYPTED_OTA + private: static void HandlePrepareDownload(intptr_t context); static void HandleFinalize(intptr_t context); @@ -54,6 +65,14 @@ class OTAImageProcessorImpl : public OTAImageProcessorInterface const esp_partition_t * mOTAUpdatePartition = nullptr; esp_ota_handle_t mOTAUpdateHandle; OTAImageHeaderParser mHeaderParser; + +#if CONFIG_ENABLE_ENCRYPTED_OTA + void EndDecryption(); + + CharSpan mKey; + bool mEncryptedOTAEnabled = false; + esp_decrypt_handle_t mOTADecryptionHandle = nullptr; +#endif // CONFIG_ENABLE_ENCRYPTED_OTA }; } // namespace chip