Перейти к содержимому

HTTPS, SSL/TLS соединения на ESP32 и ESP-IDF

Добрый день, уважаемый читатель! Продолжаем тему защищенных соединений, но теперь уже применительно к ESP32 и Espressif IoT Development Framework (ESP-IDF).

В ESP-IDF за безопасные соединения “по умолчанию” отвечает открытая библиотека Mbed TLS — это библиотека C, которая реализует криптографические примитивы, манипулирование сертификатами X.509 и протоколы SSL/TLS и DTLS. Небольшой размер кода делает её подходящим вариантом для встраиваемых систем.

Но не спешите скачивать и изучать исходную версию с GitHub, в ESP-IDF уже имеется своя, портированная разработчиками версия, так что исходная реализация нам не понадобится.

Библиотека ESP-TLS и Mbed TLS для работы на ESP32 и ESP-IDF

Mbed TLS поддерживает версии от SSL 3.0 до TLS 1.3 и DTLS от 1.0 до 1.2. В разных версиях ESP-IDF могут быть разные версии Mbed TLS. В следующей таблице перечислены соответствия версии ESP-IDF и Mbed TLS:

Библиотека ESP-TLS и Mbed TLS для работы на ESP32 и ESP-IDF


Вводная часть

Если вы читаете эту статью, то я смею предположить, что вы уже знаете что такое SSL, TLS, чем отличаются HTTP и HTTPS, и как получить корневой сертификат для сайта. Если нет – прошу прочитать теоретический материал из предыдущей статьи: TLS (httpS) соединения на ESP8266

На этом будет считать теоретическую часть оконченной и перейдем сразу к практике!


Альтернативы

Стоит сразу упомянуть, что Mbed TLS не является единственно возможной библиотекой, которая поддерживается ESP-IDF. Компонент ESP-TLS действует как уровень абстракции над базовой библиотекой SSL/TLS и, таким образом, вы имеете возможность использовать либо Mbed TLS, либо wolfSSL в качестве базовой библиотеки SSL/TLS.

По умолчанию используются Mbed TLS, но при необходимости можно использовать другую библиотеку – wolfSSL, которая доступна по адресу https://github.com/espressif/esp-wolfssl в двоичном формате. Там же вы найдете также несколько примеров, полезных для понимания API. wolfSSL снижает расход оперативной памяти и стека, но вместо этого ведет к увеличению размера скомпилированного проекта (при прочих равных условиях).

Библиотека ESP-TLS и Mbed TLS для работы на ESP32 и ESP-IDF


Варианты проверки сертификата сервера

Как мы выяснили из предыдущей статьи, для установки защищенного соединения с сервером нам требуется проверить его сертификат, используя для этого корневой сертификат удостоверяющего центра (которому мы безрассудно доверяем). В программах для Arduino корневой сертификат обычно включается прямо в код программы как const char*.

ESP-TLS предоставляет для хранения корневых сертификатов гораздо больше возможностей:

  • cacert_buf и cacert_bytes : сертификат центра сертификации может быть предоставлен в обычном бинарном буфере cacert_buf размером cacert_bytes. В чем-то этот способ очень похож на способ, обычно применяемых в программах для Arduino. ESP-TLS будет явно использовать сертификат, присутствующий в буфере, для проверки сервера.
  • use_global_ca_store : вы можете создать глобальное хранилище сертификатов, заполнить его всеми необходимыми для работы сертификатами, а затем его можно будет использовать для проверки сервера для всех соединений ESP-TLS, которые на него ссылаются.
  • crt_bundle_attach : API ESP x509 Certificate Bundle предоставляет простой способ включить полный (или частичный) набор корневых сертификатов x509 для проверки сервера TLS. В этом случае вам не нужно искать и скачивать корневой сертификат для сервера, с которым вы собираетесь работать, ESP-IDF включить в вашу программу около 130 доверенных сертификатов из хранилища корневых сертификатов Mozilla NSS (около 200 килобайт). С одной стороны так, конечно же проще, с другой – есть подводные камни, о которых поговорим ниже.
  • psk_hint_key : чтобы использовать pre-shared ключи для проверки сервера. Для этого необходимо предварительно включить опцию CONFIG_ESP_TLS_PSK_VERIFICATION в конфигурации меню ESP-TLS. Затем в программу должен быть передан указатель на ключ и hint PSK через esp_tls_cfg_t. ESP-TLS будет использовать PSK для проверки сервера только в том случае, если не выбран другой вариант проверки сервера.
  • skip server verification : это небезопасная опция, предусмотренная в ESP-TLS для целей тестирования. Этот параметр можно установить, только включив опции CONFIG_ESP_TLS_INSECURE и CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY в меню конфигурации SdkConfig. Если этот параметр включен, ESP-TLS по умолчанию будет пропускать проверку сервера, если в настройках esp_tls_cfg_t не выбраны другие параметры проверки сервера. ПРЕДУПРЕЖДЕНИЕ. Включение этой опции сопряжено с потенциальным риском установления TLS-соединения с сервером, имеющим фальшивую идентификацию, при условии, что сертификат сервера не предоставляется ни через API, ни через другой механизм, такой как ca_store и т. д.

Ниже рассмотрим эти варианты (ну почти, кроме двух последних) и чуть-чуть сравним их влияние на размер свободной кучи и скомпилированного бинарника, при прочих равных условиях.

Напомню, что прежде чем устанавливать любое SSL / TLS соединение, совершенно необходимо, чтобы ESP32 был подключен к сети и в системе были установлены корректные дата и время. В этой статье предполагается, что вы уже подключили свою ESP32 к точке доступа и получили корректное время с SNTP, либо считали его из подключенных часов реального времени.

О том, как это сделать, я рассказывал ранее в других статьях:


Подключение корневых сертификатов к проекту

Итак, вы имеете файл с корневым сертификатом ЦС, который выдал сертификат серверу, к котором вы собираетесь подключаться (сам или через другие ЦС по цепочке – абсолютно не важно). Вот с таким содержимым:

Библиотека ESP-TLS и Mbed TLS для работы на ESP32 и ESP-IDF

Теперь его необходимо подключить к проекту.

Включить его в проект как const char* (по аналогии с arduino) у меня не вышло 🤷‍♂️. Да я и не встречал ни одного примера на ESP-IDF, где был бы применен такой способ. Что ж, будем считать, что то не реально.

Вместо этого ESP-IDF предлагает подключить файл сертификата просто как внешний двоичный файл. Прежде всего, смените его расширение на .pem, так как виндовая утилита, с помощью которой я сохранил файл, сохраняет его с неправильным расширением. Хотя… я не думаю, что расширение на что-то сильно повлияет.

Я предпочитаю хранить сертификаты в отдельной папке вместе с общими библиотеками – это позволяет одновременно использовать их сразу во всех проектах. Но в данном примере я скопировал их в папку /certs проекта.

Открываем папку /src проекта и находим в ней файл CMakeLists.txt, в который необходимо добавить такую строку:

target_add_binary_data(${COMPONENT_TARGET} ${CMAKE_SOURCE_DIR}/certs/isrg_root_x1.pem TEXT)

Где isrg_root_x1.pem – это имя файла подключаемого сертификата. Если вы желаете хранить файлы сертификатов в другом месте, скорректируйте путь. Повторите это для всех файлов, используемых в проекте, например так:

Библиотека ESP-TLS и Mbed TLS для работы на ESP32 и ESP-IDF

В принципе, если вы программируете на “чистом” ESP-IDF, то этого уже достаточно, больше ничего не требуется.

Однако, если вы используете PlatformIO, то этого будет не достаточно. Он не сможет начать сборку проекта, так как ничего “не знает” об этих файлах. Необходимо указать эти файлы и ему тоже, в файле platformio.ini, например так:

В примере подключены сразу три корневых сертификата

В примере подключены сразу три корневых сертификата

После этого эти файлы станут доступны в проекте PlatformIO и ESP-IDF.

Как достать содержимое подключенных файлов из кода программы

Подключенные к проекту файлы доступны из кода программы с ассемблерными идентификаторами:

  • _binary_%имя_вашего_файла_с_расширением%_start – указатель на начало содержимого файла (первый байт)
  • _binary_%имя_вашего_файла_с_расширением%_end – указатель на конец содержимого файла (последний байт)

То есть в нашем случае это будут:

  • _binary_isrg_root_x1_pem_start – начало сертификата
  • _binary_isrg_root_x1_pem_end – конец сертификата

Всё готово, можно настраивать безопасное подключение. Работать будем с тем же сервером, что и в предыдущих статьях, но по протоколу https и на esp32, то есть используем тот же самый URL:

httpS://open-monitoring.online/get?cid=2468&key=H9xOdR&p1=ЗНАЧ1&p2=ЗНАЧ2&p3=ЗНАЧ3

Использовать в примерах ниже я буду одно и тоже соединение, однажды инициализированное при запуске задачи. Что это такое и почему, я рассказывал в статье HTTP запросы на ESP8266 и ESP32 в разделе Повторное использование HTTP-соединений“.


Вариант 1: передаем сертификат через буфер

Самый простой вариант настройки TLS-соединения в ESP-IDF – передать указатель на сертификат напрямую. То есть практически точно так же, как в проектах для Arduino.

Шаг 1. подключаем файл сертификата к проекту, как было описано выше. То есть добавляем указания на файл сертификата в CMakeLists.txt и в platformio.ini.

Шаг 2. Указываем внешние константы-указатели на буфер в сертификатом:

Библиотека ESP-TLS и Mbed TLS для работы на ESP32 и ESP-IDF

Шаг 3. Настраиваем параметры соединения:

Библиотека ESP-TLS и Mbed TLS для работы на ESP32 и ESP-IDF

Шаг 4. Инициализируем HTTP-соединение как обычно и пользуемся. Как это делается, я рассказывал в предыдущей статье. Ну или подключаемся к MQTT-брокеру, к примеру.

Библиотека ESP-TLS и Mbed TLS для работы на ESP32 и ESP-IDF

Примечание: здесь я в качестве примера использую данные о свободной оперативной памяти в куче и применил динамическое формирование запроса в статическом буфере static char buffer[256]; по шаблону.

Достоинства: простота использования
Недостатки: необходимость прикреплять сертификат к программе и следить за сроками их действия; дешифрация сертификата производится при каждом обращении к сертификату.

Этот вариант отлично подходит для случаев, когда TLS-соединение используется относительно редко. Размер двоичного файла при этом варианте получается что-то около этого: Flash: 80.0% (used 838461 bytes from 1048576 bytes)

Ссылка на проект-пример: dzen/https_v1_buffer


Вариант 2: используем глобальное хранилище сертификатов

Если вы собираетесь использовать ваши TLS соединения (HTTPS, MQTT over TLS, SFTP и прочая-прочая-прочая) постоянно, то имеет смысл завести себе глобальное хранилище сертификатов. Тем более это имеет смысл, если один и тот же корневой сертификат будет использован сразу в нескольких соединениях. Один раз добавили сертификат в общий список (при этом он будет дешифрован, насколько я понимаю) – и всё, дальше только пользуемся готовеньким. Очень удобно.

Код в этом случае будет чуть более сложным, но не сильно.

Шаг 1. подключаем файл сертификата к проекту, как было описано выше. То есть добавляем указания на файл сертификата в CMakeLists.txt и в platformio.ini.

Шаг 2. Указываем внешние константы-указатели на буфер в сертификатом:

Библиотека ESP-TLS и Mbed TLS для работы на ESP32 и ESP-IDF

То есть пока всё как в предыдущем случае. А дальше начинаются изменения…

Шаг 3. Инициализируем хранилище и добавляем в него необходимые сертификаты. Лучше всего сделать это до запуска рабочих задач:

Библиотека ESP-TLS и Mbed TLS для работы на ESP32 и ESP-IDF

Шаг 4. Настраиваем соединение. Почти как в предыдущем случае, но вам уже не нужно указывать данные сертификата (их мы указали ранее, при добавлении в хранилище), просто укажите что хотите использовать глобальное хранилище:

Библиотека ESP-TLS и Mbed TLS для работы на ESP32 и ESP-IDF

Достоинства: скорость работы и простое использование КС для нескольких соединений одновременно
Недостатки: необходимость прикреплять сертификат к программе и следить за сроками их действия.

Этот вариант отлично подходит для случаев, когда TLS-соединение используется часто или (и) в нескольких разных соединениях.

Ссылка на проект-пример: dzen/https_v2_global_store


Вариант 3: используем пакет сертификатов от Mozilla

Н-да, скажете вы:
– А нафига вот это фсё, корневые сертификаты какие-то, искать их, скачивать, прикреплять к программе… Нежели нельзя как на взрослых контуперах – вообще не задумываться о корневых сертификатах,

Ответ: можно! ESP-IDF с некоторых пор (вроде бы с версии 4.3 или 4.4, но могу ошибаться) позволяет подключить к вашему проекту вообще весь список корневых сертификатов из хранилища корневых сертификатов Mozilla NSS, однако следует учитывать что они сразу же займут на вашей flash-памяти около 64 килобайт.

В этом случае шаги 1 и 2 и предыдущих примеров нам уже не понадобятся, но нужно будет включить эту опцию в sdkconfig.

Шаг 1. Активируем опцию crt_bundle_attach в sdkconfig:

Для этого необходимо в первую очередь активировать крыжик в меню (Top) → Component config → mbedTLS → Certificate Bundle:

Библиотека ESP-TLS и Mbed TLS для работы на ESP32 и ESP-IDF

Затем вы должны определить, какие сертификаты включать в ваш проект:

  • весь список пакетов сертификатов по умолчанию (около 200 килобайт в несжатом виде)
  • использовать только самые распространенные сертификаты из пакетов по умолчанию, уменьшив размер занимаемой памяти примерно на 50%, но сохраняя при этом покрытие около 99%
  • не использовать готовые пакеты, вместо этого вы сможете добавить в пустой bundle свои сертификаты из отдельно указанной папки – по сути это получается почти тоже самое что и global_storage, но сертификаты добавляются в список еще на этапе компиляции программы.

Библиотека ESP-TLS и Mbed TLS для работы на ESP32 и ESP-IDF

Не забудьте сохранить ваши настройки!

Шаг 2. Подключите заголовочный файл #include “esp_crt_bundle.h” к вашему проекту.

Шаг 3. Используйте этот механизм через callback-функцию esp_crt_bundle_attach, которую собственно и предоставляет нам esp_crt_bundle.h:

Библиотека ESP-TLS и Mbed TLS для работы на ESP32 и ESP-IDF

В данном случае нам нет необходимости задумываться о том, как написать callback-функцию, она уже написана разработчиками и предоставляется в готовом виде. Сделано это видимо для того, чтобы иметь возможность подключить “левый” пакет сертификатов, отличный от системного.

Как видите – всё достаточно просто. Теперь вам не придется следить за корневыми сертификатами и сроками их действия. Они обновятся автоматически при следующем обновлении ESP-IDF, либо это можно сделать вручную.

Взамен придется платить несколько большим размером двоичного файла – у меня размер скомпилированного файла оказался flash: 86.2% (used 903977 bytes from 1048576 bytes), то есть это всего ~64 лишних килобайт в сжатом варианте (вместо ожидаемых 200 в несжатом виде). Но это в случае использования полного списка сертификатов.

Но в этом режиме проверки сертификата есть некоторые проблемы с сертификатами, выданными популярным бесплатным центром Let’s Encrypt. Браузер показывает, что сертификат этому промежуточному центру сертификации был выдан корневым ЦС ISRG Root X1, поэтому в предыдущих случаях я просто указывал сертификат ISRG Root X1 и не парился. И всё прекрасно работало. С bundle “по умолчанию” верификация не проходит:

Библиотека ESP-TLS и Mbed TLS для работы на ESP32 и ESP-IDF

Bundle не работает? Оказалось, это не совсем так. Дело в том, что сертификат для Let’s Encrypt был выдан одновременно двумя центрами сертификации: ISRG Root X1 и DST Root CA X3, это так называемая перекрестная подпись:

Библиотека ESP-TLS и Mbed TLS для работы на ESP32 и ESP-IDF

Поэтому, когда ведется поиск по названию корневого сертификата в bundle, то фактически ищется DST Root CA X3, но его на момент написания этой статьи в списке сертификатов bundle от Mozilla нет. Решение очевидно: добавить в список “вручную”, я разместил дополнительный сертификат в папке bundle проекта (в настойках указываем именно папку, а не конкретный файл; и именно в папке проекта, а не “на стороне”):

Библиотека ESP-TLS и Mbed TLS для работы на ESP32 и ESP-IDF

После этого проверка выполняется нормально:

Библиотека ESP-TLS и Mbed TLS для работы на ESP32 и ESP-IDF

Достоинства: очень простое использование; нет необходимости самому обновлять корневые сертификаты
Недостатки: больший размер двоичного файла, небольшие проблемы с Let’s Encrypt

Ссылка на проект-пример: dzen/https_v3_bundle_attach

Какой вариант предпочтете вы – решать только вам


Параметры библиотеки Mbed TLS

Библиотека имеет Mbed TLS достаточно много параметров, которые можно найти в sdkconfig по адресу (Top) → Component config → mbedTLS. Честно говоря, я не знаю назначения всех опций, но некоторые из них немного изучил, давайте про них и расскажу.

Часть из них мы уже рассмотрели (что качается Certificate Bundle). Но библиотека имеет ещё несколько любопытных опций, которые напрямую влияют на размер приложения и размер занимаемой оперативной памяти (кучи) при работе с защищенными соединениями.

Предупреждение! Всё нижеописанное справедливо для framework-espidf @ 3.50000.0 (5.0.0), для других версий структура меню может отличаться!

Библиотека ESP-TLS и Mbed TLS для работы на ESP32 и ESP-IDF

Меня интересуют в первую очередь эти опции, так как они влияют на размер используемой оперативной памяти (кучи) для установки TLS-соединений (для краткости я опустил (Top) → Component config → mbedTLS для каждой из них):

  • mbedTLS v3.x related → Variable SSL buffer lengthиспользовать SSL буферы переменной длины. Видимо это какой-то вариант предыдущего случая, так как так же позволят прилично экономить кучу. Не совместим с предыдущей опцией, при этой опции Using dynamic TX/RX buffer блокируется.
  • mbedTLS v3.x related → Enable serialization of the TLS context structures – насколько я понял из справочной системы, эта опция позволяет сохранять один сертификат для последующего использования в куче. Но как это работает на практике, я так и не понял. Справка так же ничего толкового не говорит.
  • mbedTLS v3.x related → Keep peer certificate after handshake completionсохраняет сертификат пира после завершения рукопожатия. Отключение этой опции сэкономит около 4 КБ кучи, но замедлит повторное соединение.
  • mbedTLS v3.x related → Support TLS 1.3 protocol – поддержка протокола TLS 1.3. Требует наличия KEEP_PEER_CERTIFICATE и не совместим с MBEDTLS_DYNAMIC_BUFFER.
  • Using dynamic TX/RX bufferиспользовать динамические TX/RX буферы для TLS-соединений. По умолчанию эта опция отключена. При активации этой опции (и всех вложенных в неё) по окончании соединения буферы приема-передачи, которые были использованы в процессе соединения, будут уничтожены. Это чуть замедляет повторные соединения (так как потребуется повторное выделение памяти под буферы), но позволяет освободить некоторое количество кучи для других целей. Эта опция совместима только с Enable serialization of the TLS context из данного списка, все остальные опции из приведенного здесь списка либо блокируют её, либо сами исчезают при её активации. Эта опция не совместима с глобальными хранилищами сертификатов

Библиотека ESP-TLS и Mbed TLS для работы на ESP32 и ESP-IDF

Как я уже написал выше, эти опции влияют на размер свободной кучи между активными соединениями. Справка обещает нам следующие значения:

Библиотека ESP-TLS и Mbed TLS для работы на ESP32 и ESP-IDF

Однако эти значения приведены, видимо, для каких-то устаревших версий ESP-IDF. Поэтому на практике мои эмпирические данные достаточно сильно отличаются от этих. Либо я чего-то не понимаю…

В следующей статье я проведу настоящий научный эксперимент и на практике постараюсь выяснить влияние этих опций на размер используемой оперативной памяти (кучи) для TLS-соединений для разных версий ESP-IDF – 4.4.3, 5.0.0 и 5.0.1. А также приведу универсальный код примера для этого эксперимента. Если вам интересно – возвращайтесь на канал!

🔶 Архив статей, упорядоченный по категориям 🔶


Ссылки

  1. Справочная система ESP-IDF :: ESP TLS
  2. Справочная система ESP-IDF :: Mbed TLS
  3. Справочная система ESP-IDF :: ESP x509 Certificate Bundle
  4. Репозитории ESP-IDF :: HTTPS Request Example

Пожалуйста, оцените статью:
[ 0 из 5, всего 0 оценок ]

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *