Security Pada ESP32 – Secure OTA Update

Salah satu security best practice adalah mekanisme update firmware dilapangan. Jika kita menemukan vulnerability, mengambil kembali semua devices akan membutuhkan biaya, belum lagi terjadi service downtime.

Devices harus dapat memeriksa ke server apakah terdapat update firmware dan melakukan download dan aktifasi update.

Sebelum memulai project ini, berikut beberapa point penting:

  • Menyiapkan Secure web server, yntuk menyimpan firmware binaries. kita gunakan openssl untuk generating private/public key pair dan menjalankan secure web server.
  • Menyiapkan dua OTA partitions untuk active firmware dan ruang untuk firmware baru yang didownload.dari web server.
  • Pada OTA update logic, kita perlu bandingkan versidari active firmware dan firmware yang ada di server. JIka versi sama, berarti tidak ada firmware baru, dan tidak dilakukan update. Jika versi berbeda, kita download firmware baru dan reboot ESP32.

Persiapan Project

Buat project baru, lalu edit file platformio.ini,

[env:az-delivery-devkit-v4]
platform = espressif32
board = az-delivery-devkit-v4
framework = espidf
monitor_speed = 115200
board_build.embed_txtfiles =server/ca_cert.pem
build_flags =
-DWIFI_SSID=${sysenv.WIFI_SSID}
-DWIFI_PASS=${sysenv.WIFI_PASS}
-DAPP_OTA_URL=${sysenv.APP_OTA_URL}
board_build.partitions = with_ota_parts.csv

Buka src/CmakeList.txt, tambahkan informasi certificate path.

FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/*.*)
idf_component_register(SRCS ${app_sources})
target_add_binary_data(${COMPONENT_TARGET} "../server/ca_cert.pem" TEXT)

Buka platformIO CLI, definisikan environment variable seperti berikut (jika Anda menggunakan windows, ganti export dengan set.

(penv)$ export WIFI_SSID='\"<your_wifi_ssid>\"'
(penv)$ export WIFI_PASS='\"<your_wifi_pass>\"'
(penv)$ export APP_OTA_URL='\"https://<server_ip>:1111/
firmware.bin\"'

Buat file partition dengan nama with_ota_parts.csv,

# Name, Type, Subtyp, Ofs, Size, Flags
nvs, data, nvs, , 0x4000,
otadata, data, ota, , 0x2000,
phy_init, data, phy, , 0x1000,
factory, app, factory,, 1M,
ota_0, app, ota_0, , 1M,
ota_1, app, ota_1, , 1M,

Generate public-private key pair untuk HTTPS server. Firmware akan menggunakan public key untuk secure connection.

(penv)$ mkdir server
(penv)$ cd server
(penv)$ openssl req -x509 -newkey rsa:2048 -keyout ca_
key.pem -out ca_cert.pem -days 365 -nodes

Jawab pertanyaan untuk membuat certificate, (hal yang penting adalah bagian Common Name, isi dengan local IP address) pada tutorial digunakan jawaban seperti berikut:

Country Name (2 letter code) [AU]:ID
State or Province Name (full name) [Some-State]:JawaBarat
Locality Name (eg, city) []:Bandung
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Skillplus
Organizational Unit Name (eg, section) []:Tutorial   
Common Name (e.g. server FQDN or YOUR name) []:192.168.0.113
Email Address []:test@test.com

Pada root project, buat file version.txt, isi dengan versi yang Anda inginkan, pada tutorial digunakan 0.0.1

0.0.1

Copy library wifi_connect yang telah digunakan pada modul sebelumnya ke folder lib.

Code

Sekarang kita sudah bisa mulai coding, buka file src/main.c, tambahkan code berikut:

//import lib dan init var
#include "freertos/FreeRTOS.h"
#include "freertos/task.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 "string.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "esp_wifi.h"
#include "wifi_connect.h"

static const char *TAG = "ota_test";
extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end");

//fungsi exit_ota
void exit_ota(const char *mess)
{
  printf("> exiting: %s\n", mess);
  vTaskDelay(1000 / portTICK_PERIOD_MS);
  vTaskDelete(NULL);
}


//fungsi start ota
void start_ota(void *arg)
{
  esp_http_client_config_t config = {
    .url = APP_OTA_URL,
    .cert_pem = (char *)server_cert_pem_start,
  };
  esp_https_ota_config_t ota_config = {
    .http_config = &config,
  };
  esp_https_ota_handle_t https_ota_handle = NULL;
  if (esp_https_ota_begin(&ota_config, &https_ota_handle) != ESP_OK)
  {
    exit_ota("esp_https_ota_begin failed");
    return;
  }
  esp_app_desc_t new_app;
  if (esp_https_ota_get_img_desc(https_ota_handle, &new_app) != ESP_OK)
  {
    exit_ota("esp_https_ota_get_img_desc failed");
    return;
  }
  const esp_partition_t *current_partition = esp_ota_get_running_partition();
  esp_app_desc_t existing_app;
  esp_ota_get_partition_description(current_partition, &existing_app);
  ESP_LOGI(TAG, "existing version: '%s'", existing_app.version);
  ESP_LOGI(TAG, "target version: '%s'", new_app.version);
  if (memcmp(new_app.version, existing_app.version, sizeof(new_app.version)) == 0)
  {
    exit_ota("no update");
    return;
  }
  ESP_LOGI(TAG, "updating...");
  while (esp_https_ota_perform(https_ota_handle) == ESP_ERR_HTTPS_OTA_IN_PROGRESS);
  if (esp_https_ota_is_complete_data_received(https_ota_handle) != true)
  {
    exit_ota("download failed");
    return;
  }
  if (esp_https_ota_finish(https_ota_handle) == ESP_OK)
  {
    ESP_LOGI(TAG, "rebooting..");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    esp_restart();
  }
  exit_ota("ota failed");
}

//fungsi wifi conn
void wifi_conn_cb(void)
{
  xTaskCreate(&start_ota, "ota", 8192, NULL, 5, NULL);
}

//fungsi wifi fail
void wifi_failed_cb(void)
{
  ESP_LOGE(TAG, "wifi failed");
}

//fungsi main
void app_main(void)
{
  ESP_LOGI(TAG, "this is 0.0.1");
  connect_wifi_params_t p = {
    .on_connected = wifi_conn_cb,
    .on_failed = wifi_failed_cb};
  connect_wifi(p);
  esp_wifi_set_ps(WIFI_PS_NONE);
}

Penjelasan Code

Bagian Import Library

Include file header yang digunakan dan definisikan dua global variables, untuk start dan end addresses dari server certificate yang di embedd dalam firmware binary.

Certificate digunakan untuk secure connection ke HTTPS server.

Bagian Exit OTA

exit_ota adalah helper function digunakan sebelum keluar dari OTA process.

Bagian Start OTA

Pertama kita definisikan HTTP client configuration untuk connect ke secure server. Digunakan firmware URL = APP_OTA_URL dan address dari embedded certificate.

Kemudian kita mulai process dengan memanggil esp_https_ota_begin. Berikut kita periksa apakah ada new firmware dengen membandingkan version dari running firmware dan version image pada server.

Fungsi esp_https_ota_get_img_desc untuk retrieve application description dari OTA server. Application description berisi informasi version. KIta bandingkan dengan existing image version. Jika sama, exit dari process.

Fungsi esp_https_ota_perform, akan mendonwload firmware dari server dalam chunks. Jika download sukses, kita panggil esp_https_ota_finish untuk menyelesaikan process dan reboot ESP32 agar bootloader load firmware baru.

Jika fail pada point tertentu, kita exit process disertai error message.

Bagian wifi callback function

wifi_conn_cb adalah callback function, dijalankan ketika ESP32 terhubung ke local Wi-Fi network. Akan menjalankan FreeRTOS task dengan start_ota function yang kita implementasikan sebelumnya.

Bagian Main Function

Pada app_main function, akan dipanggil connect_wifi. Fungsi esp_wifi_set_ps digunakan untuk disable power-save options of Wi-Fi agar OTA firmware update efisien.

Compile dan Upload

Sampai disini kita sudah selesai membuat code. Langkah berikutnya flash firmware. Dengan firmware awal, kita sudah siap melakukan OTA update.

penv)$ pio run -t clean
(penv)$ pio run
(penv)$ pio run -t erase
(penv)$ pio run -t upload

OpenSSL Server

Buka command prompt baru, jalankan server.

$ cd server
$ openssl s_server -WWW -key ca_key.pem -cert ca_cert.pem
 -port 1111

Server sudah berjalan, kita lakukan testing dengan perintah dibawah. Pastikan juga firewall tidak block port yang digunakan.

$ openssl s_client -connect localhost:1111
CONNECTED(00000003)

Testing OTA

Berikutnya kita testing OTA, compile versi baru dari firmware. Kemudian ubah versi pada file version.txt. Copy firmware baru ke folder server.

penv)$ pio run -t clean
(penv)$ echo "0.0.2" > version.txt
(penv)$ pio run
(penv)$ cp ./.pio/build/az-delivery-devkit-v4/firmware.
bin server/

Untuk melihat apakah devkit melakukan download firmware baru, tekan reset button dan lihat message pada serial monitor.

Sampai disini kita sudah selesai memimplementasikan OTA. Pada modul berikutnya kita akan mempelajar TLS untuk secure network communication.

Sharing is caring:

Leave a Comment