diff --git a/.build-test-rules.yml b/.build-test-rules.yml index 6f34753740..687318b033 100644 --- a/.build-test-rules.yml +++ b/.build-test-rules.yml @@ -47,3 +47,7 @@ esp_lcd_qemu_rgb/examples/lcd_qemu_rgb_panel: enable: - if: ((IDF_VERSION_MAJOR == 5 and IDF_VERSION_MINOR >= 3) or (IDF_VERSION_MAJOR > 5)) and (IDF_TARGET in ["esp32", "esp32c3"]) reason: Example is meant to be run under QEMU, which currently only supports ESP32 and ESP32-C3 +esp_encrypted_img/examples/pre_encrypted_ota: + enable: + - if: IDF_VERSION_MAJOR > 4 and INCLUDE_DEFAULT == 1 + reason: Example uses esp_encrypted_img component which was introduced in IDF v5.0 diff --git a/.github/workflows/build_and_run_examples.yml b/.github/workflows/build_and_run_examples.yml index 9cd7ea1bcb..ec22d6dfb6 100644 --- a/.github/workflows/build_and_run_examples.yml +++ b/.github/workflows/build_and_run_examples.yml @@ -61,7 +61,7 @@ jobs: - name: Install Python packages env: PIP_EXTRA_INDEX_URL: "https://dl.espressif.com/pypi/" - run: pip install --only-binary cryptography pytest-embedded pytest-embedded-serial-esp pytest-embedded-idf + run: pip install --only-binary cryptography pytest-embedded pytest-embedded-serial-esp pytest-embedded-idf rangehttpserver - name: Run examples run: pytest --target=${{ matrix.idf_target }} -m generic --build-dir=build_${{ matrix.idf_target }} --ignore=usb --ignore=test_app @@ -90,6 +90,6 @@ jobs: - name: Install Python packages env: PIP_EXTRA_INDEX_URL: "https://dl.espressif.com/pypi/" - run: pip install --only-binary cryptography pytest-embedded pytest-embedded-serial-esp pytest-embedded-idf + run: pip install --only-binary cryptography pytest-embedded pytest-embedded-serial-esp pytest-embedded-idf rangehttpserver - name: Run examples run: pytest --target=${{ matrix.idf_target }} -m ethernet --build-dir=build_${{ matrix.idf_target }} --ignore=usb --ignore=test_app diff --git a/esp_encrypted_img/examples/pre_encrypted_ota/CMakeLists.txt b/esp_encrypted_img/examples/pre_encrypted_ota/CMakeLists.txt new file mode 100644 index 0000000000..df49470c0d --- /dev/null +++ b/esp_encrypted_img/examples/pre_encrypted_ota/CMakeLists.txt @@ -0,0 +1,8 @@ +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(pre_encrypted_ota) diff --git a/esp_encrypted_img/examples/pre_encrypted_ota/README.md b/esp_encrypted_img/examples/pre_encrypted_ota/README.md new file mode 100644 index 0000000000..948fe059d9 --- /dev/null +++ b/esp_encrypted_img/examples/pre_encrypted_ota/README.md @@ -0,0 +1,49 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | + +# Encrypted Binary OTA + +This example demonstrates OTA updates with pre-encrypted binary using `esp_encrypted_img` component's APIs and tool. + +Pre-encrypted firmware binary must be hosted on OTA update server. +This firmware will be fetched and then decrypted on device before being flashed. +This allows firmware to remain `confidential` on the OTA update channel irrespective of underlying transport (e.g., non-TLS). + +## ESP Encrypted Image Abstraction Layer + +This example uses `esp_encrypted_img` component hosted at [idf-extra-components/esp_encrypted_img](https://github.com/espressif/idf-extra-components/blob/master/esp_encrypted_img) and available though the [IDF component manager](https://components.espressif.com/component/espressif/esp_encrypted_img). + +Please refer to its documentation [here](https://github.com/espressif/idf-extra-components/blob/master/esp_encrypted_img/README.md) for more details. + + +## How to use the example + +To create self-signed certificate and key, refer to README.md in upper level 'examples' directory. This certificate should be flashed with binary as it will be used for connection with server. + +### Creating RSA key for encryption + +You can generate a public and private RSA key pair using following commands: + +`openssl genrsa -out rsa_key/private.pem 3072` + +This generates a 3072-bit RSA key pair, and writes them to a file. + +Private key is required for decryption process and is used as input to the `esp_encrypted_img` component. Private key can either be embedded into the firmware or stored in NVS. + +Encrypted image generation tool will derive public key (from private key) and use it for encryption purpose. + +* **NOTE:** We highly recommend the use of flash encryption or NVS encryption to protect the RSA Private Key on the device. +* **NOTE:** RSA key provided in the example is for demonstration purpose only. We recommend to create a new key for production applications. + +## Build and Flash example + +``` +idf.py build flash +``` + +* An encrypted image is automatically generated by build system. Upload the generated encrypted image (`build/pre_encrypted_ota_secure.bin`) to a server for performing OTA update. + + +## Configuration + +Refer the README.md in the parent directory for the setup details. diff --git a/esp_encrypted_img/examples/pre_encrypted_ota/main/CMakeLists.txt b/esp_encrypted_img/examples/pre_encrypted_ota/main/CMakeLists.txt new file mode 100644 index 0000000000..600f0c5b1a --- /dev/null +++ b/esp_encrypted_img/examples/pre_encrypted_ota/main/CMakeLists.txt @@ -0,0 +1,8 @@ +idf_build_get_property(project_dir PROJECT_DIR) +idf_component_register(SRCS "pre_encrypted_ota.c" + INCLUDE_DIRS "." + EMBED_TXTFILES ${project_dir}/server_certs/ca_cert.pem + ${project_dir}/rsa_key/private.pem) + +create_esp_enc_img(${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.bin + ${project_dir}/rsa_key/private.pem ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}_secure.bin app) diff --git a/esp_encrypted_img/examples/pre_encrypted_ota/main/Kconfig.projbuild b/esp_encrypted_img/examples/pre_encrypted_ota/main/Kconfig.projbuild new file mode 100644 index 0000000000..18a21ffbb3 --- /dev/null +++ b/esp_encrypted_img/examples/pre_encrypted_ota/main/Kconfig.projbuild @@ -0,0 +1,45 @@ +menu "Example Configuration" + + config EXAMPLE_FIRMWARE_UPGRADE_URL + string "firmware upgrade url endpoint" + default "https://192.168.0.3:8070/hello_world.bin" + help + URL of server which hosts the encrypted firmware image. + + config EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN + bool + default y if EXAMPLE_FIRMWARE_UPGRADE_URL = "FROM_STDIN" + + config EXAMPLE_SKIP_COMMON_NAME_CHECK + bool "Skip server certificate CN fieldcheck" + default n + help + This allows you to skip the validation of OTA server certificate CN field. + + config EXAMPLE_SKIP_VERSION_CHECK + bool "Skip firmware version check" + default n + help + This allows you to skip the firmware version check. + + config EXAMPLE_OTA_RECV_TIMEOUT + int "OTA Receive Timeout" + default 5000 + help + Maximum time for reception + + config EXAMPLE_ENABLE_PARTIAL_HTTP_DOWNLOAD + bool "Enable partial HTTP download" + default n + help + This enables use of Range header in esp_https_ota component. + Firmware image will be downloaded over multiple HTTP requests. + + config EXAMPLE_HTTP_REQUEST_SIZE + int "HTTP request size" + default MBEDTLS_SSL_IN_CONTENT_LEN + depends on EXAMPLE_ENABLE_PARTIAL_HTTP_DOWNLOAD + help + This options specifies HTTP request size. Number of bytes specified + in this option will be downloaded in single HTTP request. +endmenu diff --git a/esp_encrypted_img/examples/pre_encrypted_ota/main/idf_component.yml b/esp_encrypted_img/examples/pre_encrypted_ota/main/idf_component.yml new file mode 100644 index 0000000000..6d595e22a7 --- /dev/null +++ b/esp_encrypted_img/examples/pre_encrypted_ota/main/idf_component.yml @@ -0,0 +1,9 @@ +## IDF Component Manager Manifest File +version: "1.0.0" +description: Pre Encrypted OTA Example +dependencies: + espressif/esp_encrypted_img: + version: "^2.0.1" + override_path: '../../../' + protocol_examples_common: + path: ${IDF_PATH}/examples/common_components/protocol_examples_common diff --git a/esp_encrypted_img/examples/pre_encrypted_ota/main/pre_encrypted_ota.c b/esp_encrypted_img/examples/pre_encrypted_ota/main/pre_encrypted_ota.c new file mode 100644 index 0000000000..599838a2f1 --- /dev/null +++ b/esp_encrypted_img/examples/pre_encrypted_ota/main/pre_encrypted_ota.c @@ -0,0 +1,227 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* Pre Encrypted HTTPS OTA example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_system.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_ota_ops.h" +#include "esp_http_client.h" +#include "esp_https_ota.h" +#include "nvs.h" +#include "nvs_flash.h" +#include "protocol_examples_common.h" +#include "esp_encrypted_img.h" + +#if CONFIG_EXAMPLE_CONNECT_WIFI +#include "esp_wifi.h" +#endif + +static const char *TAG = "pre_encrypted_ota_example"; +extern const char server_cert_pem_start[] asm("_binary_ca_cert_pem_start"); +extern const char server_cert_pem_end[] asm("_binary_ca_cert_pem_end"); + +extern const char rsa_private_pem_start[] asm("_binary_private_pem_start"); +extern const char rsa_private_pem_end[] asm("_binary_private_pem_end"); + +#define OTA_URL_SIZE 256 + +static esp_err_t validate_image_header(esp_app_desc_t *new_app_info) +{ + if (new_app_info == NULL) { + return ESP_ERR_INVALID_ARG; + } + + const esp_partition_t *running = esp_ota_get_running_partition(); + esp_app_desc_t running_app_info; + if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) { + ESP_LOGI(TAG, "Running firmware version: %s", running_app_info.version); + } + +#ifndef CONFIG_EXAMPLE_SKIP_VERSION_CHECK + if (memcmp(new_app_info->version, running_app_info.version, sizeof(new_app_info->version)) == 0) { + ESP_LOGW(TAG, "Current running version is the same as a new. We will not continue the update."); + return ESP_FAIL; + } +#endif + return ESP_OK; +} + +static esp_err_t _decrypt_cb(decrypt_cb_arg_t *args, void *user_ctx) +{ + if (args == NULL || user_ctx == NULL) { + ESP_LOGE(TAG, "_decrypt_cb: Invalid argument"); + return ESP_ERR_INVALID_ARG; + } + esp_err_t err; + pre_enc_decrypt_arg_t pargs = {}; + pargs.data_in = args->data_in; + pargs.data_in_len = args->data_in_len; + err = esp_encrypted_img_decrypt_data((esp_decrypt_handle_t *)user_ctx, &pargs); + if (err != ESP_OK && err != ESP_ERR_NOT_FINISHED) { + return err; + } + + static bool is_image_verified = false; + if (pargs.data_out_len > 0) { + args->data_out = pargs.data_out; + args->data_out_len = pargs.data_out_len; + if (!is_image_verified) { + is_image_verified = true; + const int app_desc_offset = sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t); + // It is unlikely to not have App Descriptor available in first iteration of decrypt callback. + assert(args->data_out_len >= app_desc_offset + sizeof(esp_app_desc_t)); + esp_app_desc_t *app_info = (esp_app_desc_t *) &args->data_out[app_desc_offset]; + err = validate_image_header(app_info); + if (err != ESP_OK) { + free(pargs.data_out); + } + return err; + } + } else { + args->data_out_len = 0; + } + + return ESP_OK; +} + +void pre_encrypted_ota_task(void *pvParameter) +{ + ESP_LOGI(TAG, "Starting Pre Encrypted OTA example"); + + esp_err_t ota_finish_err = ESP_OK; + esp_http_client_config_t config = { + .url = CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL, + .cert_pem = server_cert_pem_start, + .timeout_ms = CONFIG_EXAMPLE_OTA_RECV_TIMEOUT, + .keep_alive_enable = true, + }; + esp_decrypt_cfg_t cfg = {}; + cfg.rsa_priv_key = rsa_private_pem_start; + cfg.rsa_priv_key_len = rsa_private_pem_end - rsa_private_pem_start; + esp_decrypt_handle_t decrypt_handle = esp_encrypted_img_decrypt_start(&cfg); + if (!decrypt_handle) { + ESP_LOGE(TAG, "OTA upgrade failed"); + vTaskDelete(NULL); + } + +#ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN + char url_buf[OTA_URL_SIZE]; + if (strcmp(config.url, "FROM_STDIN") == 0) { + example_configure_stdin_stdout(); + fgets(url_buf, OTA_URL_SIZE, stdin); + int len = strlen(url_buf); + url_buf[len - 1] = '\0'; + config.url = url_buf; + } else { + ESP_LOGE(TAG, "Configuration mismatch: wrong firmware upgrade image url"); + abort(); + } +#endif + +#ifdef CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK + config.skip_cert_common_name_check = true; +#endif + + esp_https_ota_config_t ota_config = { + .http_config = &config, +#ifdef CONFIG_EXAMPLE_ENABLE_PARTIAL_HTTP_DOWNLOAD + .partial_http_download = true, + .max_http_request_size = CONFIG_EXAMPLE_HTTP_REQUEST_SIZE, +#endif + .decrypt_cb = _decrypt_cb, + .decrypt_user_ctx = (void *)decrypt_handle, + .enc_img_header_size = esp_encrypted_img_get_header_size(), + }; + + esp_https_ota_handle_t https_ota_handle = NULL; + esp_err_t err = esp_https_ota_begin(&ota_config, &https_ota_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ESP HTTPS OTA Begin failed"); + vTaskDelete(NULL); + } + + while (1) { + err = esp_https_ota_perform(https_ota_handle); + if (err != ESP_ERR_HTTPS_OTA_IN_PROGRESS) { + break; + } + // esp_https_ota_perform returns after every read operation which gives user the ability to + // monitor the status of OTA upgrade by calling esp_https_ota_get_image_len_read, which gives length of image + // data read so far. + ESP_LOGD(TAG, "Image bytes read: %d", esp_https_ota_get_image_len_read(https_ota_handle)); + } + + if (!esp_https_ota_is_complete_data_received(https_ota_handle)) { + // the OTA image was not completely received and user can customise the response to this situation. + ESP_LOGE(TAG, "Complete data was not received."); + } else { + err = esp_encrypted_img_decrypt_end(decrypt_handle); + if (err != ESP_OK) { + goto ota_end; + } + ota_finish_err = esp_https_ota_finish(https_ota_handle); + if ((err == ESP_OK) && (ota_finish_err == ESP_OK)) { + ESP_LOGI(TAG, "ESP_HTTPS_OTA upgrade successful. Rebooting ..."); + vTaskDelay(1000 / portTICK_PERIOD_MS); + esp_restart(); + } else { + if (ota_finish_err == ESP_ERR_OTA_VALIDATE_FAILED) { + ESP_LOGE(TAG, "Image validation failed, image is corrupted"); + } + ESP_LOGE(TAG, "ESP_HTTPS_OTA upgrade failed 0x%x", ota_finish_err); + vTaskDelete(NULL); + } + } + +ota_end: + esp_https_ota_abort(https_ota_handle); + ESP_LOGE(TAG, "ESP_HTTPS_OTA upgrade failed"); + vTaskDelete(NULL); +} + +void app_main(void) +{ + // Initialize NVS. + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + // 1.OTA app partition table has a smaller NVS partition size than the non-OTA + // partition table. This size mismatch may cause NVS initialization to fail. + // 2.NVS partition contains data in new format and cannot be recognized by this version of code. + // If this happens, we erase NVS partition and initialize NVS again. + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + ESP_ERROR_CHECK( err ); + + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. + * Read "Establishing Wi-Fi or Ethernet Connection" section in + * examples/protocols/README.md for more information about this function. + */ + ESP_ERROR_CHECK(example_connect()); + +#if CONFIG_EXAMPLE_CONNECT_WIFI + /* Ensure to disable any WiFi power save mode, this allows best throughput + * and hence timings for overall OTA operation. + */ + esp_wifi_set_ps(WIFI_PS_NONE); +#endif // CONFIG_EXAMPLE_CONNECT_WIFI + + xTaskCreate(&pre_encrypted_ota_task, "pre_encrypted_ota_task", 1024 * 8, NULL, 5, NULL); +} diff --git a/esp_encrypted_img/examples/pre_encrypted_ota/pytest_pre_encrypted_ota.py b/esp_encrypted_img/examples/pre_encrypted_ota/pytest_pre_encrypted_ota.py new file mode 100644 index 0000000000..76bb4a1a99 --- /dev/null +++ b/esp_encrypted_img/examples/pre_encrypted_ota/pytest_pre_encrypted_ota.py @@ -0,0 +1,137 @@ +# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +import http.server +import multiprocessing +import os +import socket +import ssl +from typing import Callable + +import pexpect +import pytest +from pytest_embedded import Dut +from RangeHTTPServer import RangeRequestHandler + +server_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'server_certs/ca_cert.pem') +key_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'server_certs/server_key.pem') +enc_bin_name = 'pre_encrypted_ota_secure.bin' + + +def get_my_ip() -> str: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + # doesn't even have to be reachable + s.connect(('8.8.8.8', 1)) + IP = s.getsockname()[0] + except Exception: + IP = '127.0.0.1' + finally: + s.close() + return IP + + +def https_request_handler() -> Callable[...,http.server.BaseHTTPRequestHandler]: + """ + Returns a request handler class that handles broken pipe exception + """ + class RequestHandler(RangeRequestHandler): + def finish(self) -> None: + try: + if not self.wfile.closed: + self.wfile.flush() + self.wfile.close() + except socket.error: + pass + self.rfile.close() + + def handle(self) -> None: + try: + RangeRequestHandler.handle(self) + except socket.error: + pass + + return RequestHandler + + +def start_https_server(ota_image_dir: str, server_ip: str, server_port: int) -> None: + os.chdir(ota_image_dir) + requestHandler = https_request_handler() + httpd = http.server.HTTPServer((server_ip, server_port), requestHandler) + + httpd.socket = ssl.wrap_socket(httpd.socket, + keyfile=key_file, + certfile=server_file, server_side=True) + httpd.serve_forever() + + +@pytest.mark.esp32 +@pytest.mark.ethernet +def test_examples_protocol_pre_encrypted_ota_example(dut: Dut) -> None: + server_port = 8001 + host_ip = get_my_ip() + # Start server + thread1 = multiprocessing.Process(target=start_https_server, args=(dut.app.binary_path, host_ip, server_port)) + thread1.daemon = True + thread1.start() + try: + dut.expect('Loaded app from partition at offset', timeout=30) + try: + ip_address = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=30)[1].decode() + print('Connected to AP/Ethernet with IP: {}'.format(ip_address)) + except pexpect.exceptions.TIMEOUT: + raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP/Ethernet') + + dut.expect('Starting Pre Encrypted OTA example', timeout=30) + print('writing to device: {}'.format('https://' + host_ip + ':' + str(server_port) + '/' + enc_bin_name)) + dut.write('https://' + host_ip + ':' + str(server_port) + '/' + enc_bin_name) + dut.expect('Magic Verified', timeout=30) + dut.expect('Reading RSA private key', timeout=30) + dut.expect('upgrade successful. Rebooting', timeout=60) + # after reboot + dut.expect('Loaded app from partition at offset', timeout=30) + finally: + thread1.terminate() + + +@pytest.mark.esp32 +@pytest.mark.ethernet +@pytest.mark.parametrize('config', ['partial_download',], indirect=True) +def test_examples_protocol_pre_encrypted_ota_example_partial_request(dut: Dut) -> None: + server_port = 8001 + host_ip = get_my_ip() + # Size of partial HTTP request + request_size = int(dut.app.sdkconfig.get('EXAMPLE_HTTP_REQUEST_SIZE')) + # File to be downloaded. This file is generated after compilation + binary_file = os.path.join(dut.app.binary_path, enc_bin_name) + bin_size = os.path.getsize(binary_file) + http_requests = int((bin_size / request_size) - 1) + assert http_requests > 1 + + # Start server + thread1 = multiprocessing.Process(target=start_https_server, args=(dut.app.binary_path, host_ip, server_port)) + thread1.daemon = True + thread1.start() + + try: + dut.expect('Loaded app from partition at offset', timeout=30) + try: + ip_address = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=30)[1].decode() + print('Connected to AP/Ethernet with IP: {}'.format(ip_address)) + except pexpect.exceptions.TIMEOUT: + raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP/Ethernet') + + dut.expect('Starting Pre Encrypted OTA example', timeout=30) + print('writing to device: {}'.format('https://' + host_ip + ':' + str(server_port) + '/' + enc_bin_name)) + dut.write('https://' + host_ip + ':' + str(server_port) + '/' + enc_bin_name) + dut.expect('Magic Verified', timeout=30) + dut.expect('Reading RSA private key', timeout=30) + + for _ in range(http_requests): + dut.expect('Connection closed', timeout=60) + + dut.expect('upgrade successful. Rebooting', timeout=60) + # after reboot + dut.expect('Loaded app from partition at offset', timeout=30) + + finally: + thread1.terminate() diff --git a/esp_encrypted_img/examples/pre_encrypted_ota/rsa_key/private.pem b/esp_encrypted_img/examples/pre_encrypted_ota/rsa_key/private.pem new file mode 100644 index 0000000000..cb0a8b7aae --- /dev/null +++ b/esp_encrypted_img/examples/pre_encrypted_ota/rsa_key/private.pem @@ -0,0 +1,39 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/esp_encrypted_img/examples/pre_encrypted_ota/sdkconfig.ci b/esp_encrypted_img/examples/pre_encrypted_ota/sdkconfig.ci new file mode 100644 index 0000000000..162324ed34 --- /dev/null +++ b/esp_encrypted_img/examples/pre_encrypted_ota/sdkconfig.ci @@ -0,0 +1,17 @@ +CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL="FROM_STDIN" +CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y +CONFIG_EXAMPLE_SKIP_VERSION_CHECK=y +CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=3000 + +CONFIG_EXAMPLE_CONNECT_ETHERNET=y +CONFIG_EXAMPLE_CONNECT_WIFI=n +CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y +CONFIG_EXAMPLE_ETH_PHY_IP101=y +CONFIG_EXAMPLE_ETH_MDC_GPIO=23 +CONFIG_EXAMPLE_ETH_MDIO_GPIO=18 +CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5 +CONFIG_EXAMPLE_ETH_PHY_ADDR=1 + +CONFIG_MBEDTLS_TLS_CLIENT_ONLY=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_EXAMPLE_CONNECT_IPV6=n diff --git a/esp_encrypted_img/examples/pre_encrypted_ota/sdkconfig.ci.partial_download b/esp_encrypted_img/examples/pre_encrypted_ota/sdkconfig.ci.partial_download new file mode 100644 index 0000000000..6699158c38 --- /dev/null +++ b/esp_encrypted_img/examples/pre_encrypted_ota/sdkconfig.ci.partial_download @@ -0,0 +1,19 @@ +CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL="FROM_STDIN" +CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y +CONFIG_EXAMPLE_SKIP_VERSION_CHECK=y +CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=3000 +CONFIG_EXAMPLE_ENABLE_PARTIAL_HTTP_DOWNLOAD=y +CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y + +CONFIG_EXAMPLE_CONNECT_ETHERNET=y +CONFIG_EXAMPLE_CONNECT_WIFI=n +CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y +CONFIG_EXAMPLE_ETH_PHY_IP101=y +CONFIG_EXAMPLE_ETH_MDC_GPIO=23 +CONFIG_EXAMPLE_ETH_MDIO_GPIO=18 +CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5 +CONFIG_EXAMPLE_ETH_PHY_ADDR=1 + +CONFIG_MBEDTLS_TLS_CLIENT_ONLY=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_EXAMPLE_CONNECT_IPV6=n diff --git a/esp_encrypted_img/examples/pre_encrypted_ota/sdkconfig.defaults b/esp_encrypted_img/examples/pre_encrypted_ota/sdkconfig.defaults new file mode 100644 index 0000000000..298a6b5aa0 --- /dev/null +++ b/esp_encrypted_img/examples/pre_encrypted_ota/sdkconfig.defaults @@ -0,0 +1,6 @@ +# Default sdkconfig parameters to use the OTA +# partition table layout, with a 4MB flash size +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_PARTITION_TABLE_TWO_OTA=y +# Enable ESP HTTPS OTA decryption callback +CONFIG_ESP_HTTPS_OTA_DECRYPT_CB=y diff --git a/esp_encrypted_img/examples/pre_encrypted_ota/server_certs/ca_cert.pem b/esp_encrypted_img/examples/pre_encrypted_ota/server_certs/ca_cert.pem new file mode 100644 index 0000000000..b29ba7ab1f --- /dev/null +++ b/esp_encrypted_img/examples/pre_encrypted_ota/server_certs/ca_cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDWDCCAkACCQCbF4+gVh/MLjANBgkqhkiG9w0BAQsFADBuMQswCQYDVQQGEwJJ +TjELMAkGA1UECAwCTUgxDDAKBgNVBAcMA1BVTjEMMAoGA1UECgwDRVNQMQwwCgYD +VQQLDANFU1AxDDAKBgNVBAMMA0VTUDEaMBgGCSqGSIb3DQEJARYLZXNwQGVzcC5j +b20wHhcNMjEwNzEyMTIzNjI3WhcNNDEwNzA3MTIzNjI3WjBuMQswCQYDVQQGEwJJ +TjELMAkGA1UECAwCTUgxDDAKBgNVBAcMA1BVTjEMMAoGA1UECgwDRVNQMQwwCgYD +VQQLDANFU1AxDDAKBgNVBAMMA0VTUDEaMBgGCSqGSIb3DQEJARYLZXNwQGVzcC5j +b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDhxF/y7bygndxPwiWL +SwS9LY3uBMaJgup0ufNKVhx+FhGQOu44SghuJAaH3KkPUnt6SOM8jC97/yQuc32W +ukI7eBZoA12kargSnzdv5m5rZZpd+NznSSpoDArOAONKVlzr25A1+aZbix2mKRbQ +S5w9o1N2BriQuSzd8gL0Y0zEk3VkOWXEL+0yFUT144HnErnD+xnJtHe11yPO2fEz +YaGiilh0ddL26PXTugXMZN/8fRVHP50P2OG0SvFpC7vghlLp4VFM1/r3UJnvL6Oz +3ALc6dhxZEKQucqlpj8l1UegszQToopemtIj0qXTHw2+uUnkUyWIPjPC+wdOAoap +rFTRAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAItw24y565k3C/zENZlxyzto44ud +IYPQXN8Fa2pBlLe1zlSIyuaA/rWQ+i1daS8nPotkCbWZyf5N8DYaTE4B0OfvoUPk +B5uGDmbuk6akvlB5BGiYLfQjWHRsK9/4xjtIqN1H58yf3QNROuKsPAeywWS3Fn32 +3//OpbWaClQePx6udRYMqAitKR+QxL7/BKZQsX+UyShuq8hjphvXvk0BW8ONzuw9 +RcoORxM0FzySYjeQvm4LhzC/P3ZBhEq0xs55aL2a76SJhq5hJy7T/Xz6NFByvlrN +lFJJey33KFrAf5vnV9qcyWFIo7PYy2VsaaEjFeefr7q3sTFSMlJeadexW2Y= +-----END CERTIFICATE----- diff --git a/esp_encrypted_img/examples/pre_encrypted_ota/server_certs/server_key.pem b/esp_encrypted_img/examples/pre_encrypted_ota/server_certs/server_key.pem new file mode 100644 index 0000000000..20a4bdb624 --- /dev/null +++ b/esp_encrypted_img/examples/pre_encrypted_ota/server_certs/server_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDhxF/y7bygndxP +wiWLSwS9LY3uBMaJgup0ufNKVhx+FhGQOu44SghuJAaH3KkPUnt6SOM8jC97/yQu +c32WukI7eBZoA12kargSnzdv5m5rZZpd+NznSSpoDArOAONKVlzr25A1+aZbix2m +KRbQS5w9o1N2BriQuSzd8gL0Y0zEk3VkOWXEL+0yFUT144HnErnD+xnJtHe11yPO +2fEzYaGiilh0ddL26PXTugXMZN/8fRVHP50P2OG0SvFpC7vghlLp4VFM1/r3UJnv +L6Oz3ALc6dhxZEKQucqlpj8l1UegszQToopemtIj0qXTHw2+uUnkUyWIPjPC+wdO +AoaprFTRAgMBAAECggEAE0HCxV/N1Q1h+1OeDDGL5+74yjKSFKyb/vTVcaPCrmaH +fPvp0ddOvMZJ4FDMAsiQS6/n4gQ7EKKEnYmwTqj4eUYW8yxGUn3f0YbPHbZT+Mkj +z5woi3nMKi/MxCGDQZX4Ow3xUQlITUqibsfWcFHis8c4mTqdh4qj7xJzehD2PVYF +gNHZsvVj6MltjBDAVwV1IlGoHjuElm6vuzkfX7phxcA1B4ZqdYY17yCXUnvui46z +Xn2kUTOOUCEgfgvGa9E+l4OtdXi5IxjaSraU+dlg2KsE4TpCuN2MEVkeR5Ms3Y7Q +jgJl8vlNFJDQpbFukLcYwG7rO5N5dQ6WWfVia/5XgQKBgQD74at/bXAPrh9NxPmz +i1oqCHMDoM9sz8xIMZLF9YVu3Jf8ux4xVpRSnNy5RU1gl7ZXbpdgeIQ4v04zy5aw +8T4tu9K3XnR3UXOy25AK0q+cnnxZg3kFQm+PhtOCKEFjPHrgo2MUfnj+EDddod7N +JQr9q5rEFbqHupFPpWlqCa3QmQKBgQDldWUGokNaEpmgHDMnHxiibXV5LQhzf8Rq +gJIQXb7R9EsTSXEvsDyqTBb7PHp2Ko7rZ5YQfyf8OogGGjGElnPoU/a+Jij1gVFv +kZ064uXAAISBkwHdcuobqc5EbG3ceyH46F+FBFhqM8KcbxJxx08objmh58+83InN +P9Qr25Xw+QKBgEGXMHuMWgQbSZeM1aFFhoMvlBO7yogBTKb4Ecpu9wI5e3Kan3Al +pZYltuyf+VhP6XG3IMBEYdoNJyYhu+nzyEdMg8CwXg+8LC7FMis/Ve+o7aS5scgG +1to/N9DK/swCsdTRdzmc/ZDbVC+TuVsebFBGYZTyO5KgqLpezqaIQrTxAoGALFCU +10glO9MVyl9H3clap5v+MQ3qcOv/EhaMnw6L2N6WVT481tnxjW4ujgzrFcE4YuxZ +hgwYu9TOCmeqopGwBvGYWLbj+C4mfSahOAs0FfXDoYazuIIGBpuv03UhbpB1Si4O +rJDfRnuCnVWyOTkl54gKJ2OusinhjztBjcrV1XkCgYEA3qNi4uBsPdyz9BZGb/3G +rOMSw0CaT4pEMTLZqURmDP/0hxvTk1polP7O/FYwxVuJnBb6mzDa0xpLFPTpIAnJ +YXB8xpXU69QVh+EBbemdJWOd+zp5UCfXvb2shAeG3Tn/Dz4cBBMEUutbzP+or0nG +vSXnRLaxQhooWm+IuX9SuBQ= +-----END PRIVATE KEY-----