Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ESP32] Support for using encrypted app binary for OTA upgrades #26978

Merged
merged 7 commits into from
Jun 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -384,6 +384,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 @@ -409,6 +414,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 @@ -186,6 +186,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