Skip to content

Commit

Permalink
[ESP32] Support for using encrypted app binary for OTA upgrades (#26978)
Browse files Browse the repository at this point in the history
* [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"
  • Loading branch information
shubhamdp authored and pull[bot] committed Feb 10, 2024
1 parent ffc535d commit 3712210
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .github/.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,8 @@ DCONF
DCONFIG
debianutils
debugText
decrypt
decrypted
DEDEDEDE
deepnote
DefaultOTAProviders
Expand Down Expand Up @@ -1151,6 +1153,7 @@ RPC
RPCs
RPi
RPis
RSA
rsn
RSSI
RST
Expand Down
8 changes: 8 additions & 0 deletions config/esp32/components/chip/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,11 @@ if((NOT CONFIG_USE_MINIMAL_MDNS) AND (CONFIG_ENABLE_WIFI_STATION OR CONFIG_ENABL
list(APPEND chip_libraries $<TARGET_FILE:${mdns_lib}>)
endif()

if (CONFIG_ENABLE_ENCRYPTED_OTA)
idf_component_get_property(esp_encrypted_img_lib espressif__esp_encrypted_img COMPONENT_LIB)
list(APPEND chip_libraries $<TARGET_FILE:${esp_encrypted_img_lib}>)
endif()

idf_component_get_property(main_lib main COMPONENT_LIB)
list(APPEND chip_libraries $<TARGET_FILE:${main_lib}>)

Expand All @@ -413,6 +418,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 $<TARGET_FILE:${wpa_supplicant_lib}>)
endif()

idf_component_get_property(esp_netif_lib esp_netif COMPONENT_LIB)
Expand Down
7 changes: 7 additions & 0 deletions config/esp32/components/chip/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,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
Expand Down
5 changes: 5 additions & 0 deletions config/esp32/components/chip/idf_component.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
54 changes: 54 additions & 0 deletions docs/guides/esp32/ota.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,57 @@ chip-tool. On receiving this command OTA requestor will query for OTA image.
```
./out/debug/chip-tool otasoftwareupdaterequestor announce-ota-provider <PROVIDER NODE ID> 0 0 0 <REQUESTOR NODE ID> 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.
6 changes: 6 additions & 0 deletions examples/lighting-app/esp32/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
13 changes: 13 additions & 0 deletions examples/platform/esp32/ota/OTAHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ chip::Optional<bool> 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()
Expand All @@ -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);
Expand Down
123 changes: 121 additions & 2 deletions src/platform/ESP32/OTAImageProcessorImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
#include "esp_system.h"
#include "lib/core/CHIPError.h"

#if CONFIG_ENABLE_ENCRYPTED_OTA
#include <esp_encrypted_img.h>
#endif // CONFIG_ENABLE_ENCRYPTED_OTA

#define TAG "OTAImageProcessor"
using namespace chip::System;
using namespace ::chip::DeviceLayer::Internal;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand All @@ -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)
{
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -218,15 +272,68 @@ 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<const char *>(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<const uint8_t *>(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));
imageProcessor->mDownloader->EndDownload(CHIP_ERROR_WRITE_FAILED);
PostOTAStateChangeEvent(DeviceLayer::kOtaDownloadFailed);
return;
}
imageProcessor->mParams.downloadedBytes += block.size();

imageProcessor->mParams.downloadedBytes += blockToWrite.size();
imageProcessor->mDownloader->FetchNextData();
}

Expand Down Expand Up @@ -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
19 changes: 19 additions & 0 deletions src/platform/ESP32/OTAImageProcessorImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
#include <platform/CHIPDeviceLayer.h>
#include <platform/OTAImageProcessor.h>

#if CONFIG_ENABLE_ENCRYPTED_OTA
#include <esp_encrypted_img.h>
#endif // CONFIG_ENABLE_ENCRYPTED_OTA

namespace chip {

class OTAImageProcessorImpl : public OTAImageProcessorInterface
Expand All @@ -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);
Expand All @@ -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

0 comments on commit 3712210

Please sign in to comment.