Компонент ESP-TLS предоставляет упрощенный интерфейс API для доступа к функционалу TLS. Он поддерживает общие сценарии использования, такие как проверка сертификата сервера (CA certification validation), SNI, ALPN-переговоры, неблокирующая реализация соединения и т. п. Вся конфигурация может быть задана в структуре данных esp_tls_cfg_t.
Примечание: здесь приведен перевод документации [1] на библиотеку TLS для микроконтроллеров ESP32 от компании Espressif. Незнакомые термины и сокращения см. в разделе "Словарик", в конце статьи.
После того, как конфигурация была определена в переменной структуры esp_tls_cfg_t, коммуникации TLS могут быть осуществлены с помощью следующих API-функций:
esp_tls_init(): инициализирует дескриптор соединения TLS. esp_tls_conn_new_sync(): откроет новое блокирующее выполнение текущего потока соединение TLS. esp_tls_conn_new_async(): функция для открытия не блокирующего поток соединения TLS. esp_tls_conn_read(): функция для чтения из соединения. esp_tls_conn_write(): функция для записи в соединение. esp_tls_conn_destroy(): функция для освобождения соединения.
Замечания по поводу форматов сертификатов:
· Структура esp_tls_cfg (esp_tls_cfg_t) включает сертификаты центра сертификации (Certificate Authority, CA), клиента или сервера, а также приватные ключи, которые могут быть в формате PEM или DER. В случае формата PEM буфер должен представлять собой блок данных, завершающийся нулем (NULL terminated; символ NULL должен быть включен а размер сертификата).
· При использовании формата PEM сертификат из Certificate Authority может быть цепочкой сертификатов, но в случае формата DER может быть только один сертификат.
· Имена переменных сертификатов, и буферы приватного ключа, и их размеры определены как объединения (union) для предоставления обратной совместимости с устаревшими именами *_pem_buf и *_pem_bytes, которые подразумевали только поддержку формата PEM. Рекомендуется использовать обычные имена, такие как cacert_buf и cacert_bytes.
Поверх слоя защищенного соединения TLS соединения может быть реализован любой протокол наподобие HTTP1, HTTP2 и т. д. Простой пример клиента HTTPS, который использует ESP-TLS для установки соединения защищенного сокета: protocols/https_request.
Компонент ESP-TLS подключается к проекту заголовочным файлом esp-tls/esp_tls.h, который содержит объявления публичных заголовков и API-функций для компонента. Внутри себя компонент ESP-TLS для своей работы использует одну из двух библиотек SSL/TLS: mbedtls и wolfssl. Соответствующие этим библиотекам API-функции объявлены в заголовочных файлах esp_tls_mbedtls.h и esp_tls_wolfssl.h.
[Проверка сервера TLS]
ESP-TLS предоставляет несколько опций для верификации сервера TLS на стороне клиента. Клиент ESP-TLS может проверить сервер путем проверки его сертификата, или с помощью предварительного обмена ключами (pre-shared keys). Пользователь должен выбрать одну из следующих опций в структуре esp_tls_cfg_t, чтобы определить опцию проверки сервера TLS. Если не выбрана ни одна из опций, то клиент выполнит возврат с фатальной ошибкой по умолчанию в момент попытки настройки соединения TLS.
· cacert_buf и cacert_bytes: сертификат CA может быть предоставлен в буфере для структуры esp_tls_cfg_t. ESP-TLS будет использовать сертификат CA, предоставленный в этом буфере, чтобы верифицировать сервер. Для этой цели в структуре должен быть установлен указатель на буфер данных CA-сертификата cacert_buf, и размер данных сертификата в этом буфере cacert_bytes.
· use_global_ca_store: может быть однократно установлен и инициализирован global_ca_store. Тогда это может использоваться для верификации сервера для всех соединений ESP-TLS, у которых установлено use_global_ca_store = true в своей соответствующей структуре esp_tls_cfg_t. См. далее справочник по API для получения информации по инициализации и настройке global_ca_store.
· crt_bundle_attach: ESP x509 Certificate Bundle API предоставляет простой способ подключения пакета пользовательских корневых сертификатов x509 (custom x509 root certificates) для верификации сервера TLS. Подробную информацию по библиотеке ESP x509 Certificate Bundle см. в документации [2].
· psk_hint_key: предварительно распределенные между клиентом и сервером секретные ключи (pre-shared keys), предназначенные для верификации сервера. Для этого варианта проверки сервера должна быть разрешена опция CONFIG_ESP_TLS_PSK_VERIFICATION в ESP-TLS menuconfig (idf.py menuconfig → Component config → ESP-TLS → Enable PSK verification). Затем в структуре esp_tls_cfg_t должны быть предоставлены PSK hint и key. ESP-TLS будет использовать PSK для верификации сервера только когда не была выбрана никакая другая опция, относящаяся к верификации сервера.
· skip server verification: это небезопасная опция, предоставленная в ESP-TLS для целей тестирования. Эта опция может быть установлена путем разрешения menuconfig-опций CONFIG_ESP_TLS_INSECURE (idf.py menuconfig → Component config → ESP-TLS → Allow potentially insecure options) и CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY (idf.py menuconfig → Component config → ESP-TLS → Skip server certificate verification by default). Когда эта опция разрешена, компонент ESP-TLS будет по умолчанию пропускать верификацию сервера, когда в структуре esp_tls_cfg_t не выбрана никакая другая опция верификации сервера TLS. ПРЕДУПРЕЖДЕНИЕ: разрешение этой опции вносит потенциальный риск установки соединения TLS с поддельным сервером при условии, что идентификация этого сервера не была проверена ни через предоставленный сертификат (см. описание функции []), ни через другой механизм проверки наподобие ca_store, и т. п.
ESP-TLS Server cert selection hook. Компонент ESP-TLS предоставляет опцию установки функции перехвата момента выбора сертификата сервера (server cert selection hook), когда используется стек mbedTLS. Это дает возможность конфигурирования и использования callback-функции выбора сертификата во время установки обмена с сервером (server handshake), чтобы выбрать сертификат для представления клиенту на основе TLS-расширений, предоставленных в hello клиента (ALPN, SNI и т. д.). Чтобы разрешить эту функцию, разрешите CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK в ESP-TLS menuconfig. Callback-функция выбора сертификата может быть сконфигурирована в структуре esp_tls_cfg_t следующим образом:
У компонента ESP-TLS есть опции использования mbedtls или wolfssl в качестве своей нижележащей библиотеки шифрования трафика (SSL/TLS Library). По умолчанию доступна и используется только mbedtls, библиотека wolfssl SSL/TLS публично доступна в репозитории Git [4]. Этот репозиторий предоставляет компонент wolfssl в двоичном формате, и также предоставляет несколько примеров, полезных для понимания его API. Для информации о лицензировании и других опций см. файл README.md из этого репозитория.
Примечание: поскольку выбор варианта библиотеки SSL/TLS влияет на внутреннее поведение ESP-TLS, переключение библиотек не меняет код проекта, специфичный для использования ESP-TLS.
Существует 2 способа использования wolfssl в проекте.
1. Непосредственное добавление wolfssl в проект как компонента следующими командами (предварительно командой cd нужно установить текущей директорию вашего проекта):
mkdir components cd components git clone https://github.com/espressif/esp-wolfssl.git
2. Добавление wolfssl в проект как внешнего компонента (EXTRA COMPONENT).
2b. Подключите esp-wolfssl в ESP-IDF установкой EXTRA_COMPONENT_DIRS в файле CMakeLists.txt своего проекта, как это сделано в wolfssl/examples. Для справки см. секцию "Опциональные (не обязательные) переменные проекта" в описании системы сборки ESP-IDF [5].
После выполнения шагов 2a и 2b у вас появится опция выбора wolfssl в качестве нижележащей библиотеки SSL/TLS library в меню конфигурации проекта:
Сравнение mbedtls и wolfssl. В следующей таблице показано типовое сравнение использования wolfssl и mbedtls в примере protocols/https_request (в котором имеется аутентификация сервера). Этот пример запускался с обоими библиотеками SSL/TLS в контексте использования конфигурации по умолчанию (длина mbedtls IN_CONTENT и OUT_CONTENT были установлены в 16384 и 4096 байт соответственно).
Свойство
Wolfssl
Mbedtls
Общий расход кучи
~19 килобайт
~37 килобайт
Использование стека задачи
~2.2 килобайт
~3.6 килобайт
Размер бинарного кода приложения
~858 килобайт
~736 килобайт
Примечание: значения в этой таблице могут меняться в зависимости от опций конфигурации и версий используемых библиотек.
[Цифровая подпись в ESP-TLS]
ESP-TLS предоставляет поддержку для использования цифровой подписи (Digital Signature, DS) вместе с микроконтроллером ESP32-C3. Использование DS для TLS поддерживается только когда ESP-TLS работает вместе с mbedTLS в качестве нижележащей библиотеки SSL/TLS (выбранный по умолчанию стек шифрования). Более подробно про библиотеку, использующую аппаратуру цифровой подписи микроконтроллера см. документацию [6]. Технические подробности технологии DS, такие как вычисление параметров приватного ключа, можно найти в главе "21 Digital Signature (DS)" технического руководства ESP32-C3 [7] (документ PDF). Периферийное устройство DS должно быть предварительно сконфигурировано, чтобы его можно было использовать для цифровой подписи, см. главу "Configure the DS peripheral for a TLS connection" документации [6].
Периферийное устройство DS должно использоваться с требуемыми параметрами приватного шифрованного ключа (encrypted private key), получаемыми при конфигурировании периферийного устройства DS. Библиотека ESP-TLS внутренне инициализирует периферийное устройство DS, когда ей предоставляется требуемый контекст цифровой подписи (параметры DS). Ниже показан кусок кода, демонстрирующий передачу контекста DS в контекст esp-tls. Контекст DS, переданный в контекст esp-tls, не должен освобождаться до тех пор, пока не будет удалено соединение TLS.
#include "esp_tls.h"
esp_ds_data_ctx_t*ds_ctx;
/* Инициализирует ds_ctx параметрами приватного ключа шифрования, который может быть
прочитан из энергонезависимой памяти (NVS [8]), или предоставлен в коде приложения. */
Примечание: когда используется цифровая подпись для соединения TLS, вместе с другими требуемыми параметрами требуется только сертификат клиента (clientcert_buf) и параметры DS (ds_data), и ключ клиента (clientkey_buf) может быть установлен в NULL.
Пример взаимной аутентификации с помощью периферийного устройства DS можно найти приложении esp-idf/examples/protocols/mqtt/ssl_mutual_auth/ [9], которое использует ESP-TLS для соединения TLS.
[Справочник по API библиотеки ESP TLS]
Заголовочный файл: components/esp-tls/esp_tls.h.
Ниже в таблице приведен общий список API-функций библиотеки ESP TLS. Подробное описание параметров API-функций, структур, типов данных, перечислений и макросов см. в документации [1].
Функция
Описание
esp_tls_init
Создает соединение TLS. Эта функция выделяет и инициализирует структуру esp_tls_t дескриптора esp-tls, и возвращает указатель на этот дескриптор.
esp_tls_conn_http_new
Создает новое соединение TLS/SSL в блокирующем режиме по предоставленной ссылке (HTTP URL)(1).
esp_tls_conn_new_sync
Создает новое соединение TLS/SSL в блокирующем режиме с указанным хостом.
esp_tls_conn_http_new_sync
Создает новое соединение TLS/SSL в блокирующем режиме по предоставленной ссылке (HTTP URL)(1). Поведение функции такое же, как у esp_tls_conn_new_sync, однако она принимает URL хоста.
esp_tls_conn_new_async
Создает новое соединение TLS/SSL без блокировки вызвавшей эту функцию задачи. Эта функция инициирует не блокирующее поток соединение TLS/SSL с указанным хостом, но из-за своей асинхронной природы, в отличие от версий API с блокировкой, она не ждет момента установки соединения.
esp_tls_conn_http_new_async
Не блокирующая версия создания нового соединения TLS/SSL по указанной ссылке (HTTP URL). Поведение этой функции такое же, как у esp_tls_conn_new_async. Однако она принимает URL хоста.
esp_tls_conn_write
Запишет данные буфера (параметр data) в указанное соединение TLS.
esp_tls_conn_read
Читает данные из указанного соединения TLS и записывает их в буфер data.
esp_tls_conn_destroy
Закроет соединение TLS/SSL и освободит любые выделенные для этого ресурсы. Эта функция должна вызываться комплементарно для каждого открытого соединения TLS, которое было открыто либо esp_tls_conn_new_sync (или esp_tls_conn_http_new_sync), либо esp_tls_conn_new_async (или esp_tls_conn_http_new_async).
esp_tls_get_bytes_avail
Вернет количество байт данных приложения, которые осталось прочитать из текущей записи. Эта API-функция является оберткой поверх API-функции mbedtls_ssl_get_bytes_avail.
esp_tls_get_conn_sockfd
Вернет дескриптор файла сокета соединения из сессии esp_tls.
esp_tls_get_ssl_context
Вернет контекст SSL.
esp_tls_init_global_ca_store
Создаст глобальное хранилище сертификатов (global CA store), изначально пустое. Эта функция должна быть вызвана, если приложение хочет использовать одно и то же хранилище CA для нескольких соединений. Эта функция инициализирует global CA store, которое может быть затем установлено вызовом esp_tls_set_global_ca_store(). Для эффективности эта функция должна быть вызвана перед любым вызовом esp_tls_set_global_ca_store().
esp_tls_set_global_ca_store
Установит global CA store с буфером, предоставленным в формате PEM. Эта функция должна быть вызвана, если приложение хочет установить глобальное хранилище сертификатов (global CA store) для нескольких соединений, например для добавления сертификатов в предоставленном буфере в цепочку сертификатов. Эта функция неявно вызовет esp_tls_init_global_ca_store(), если она не была еще вызвана. Приложение должно вызывать esp_tls_set_global_ca_store перед вызовом esp_tls_conn_new().
esp_tls_free_global_ca_store
Освободит текущее используемое глобальное хранилище сертификатов (global CA store). При этом освободится память глобального хранилища, которая использовалась для хранения всех обработанных сертификатов. Приложение может вызывать эту функцию, если ей больше не нужно global CA store.
esp_tls_get_and_clear_last_error
Вернет последнюю ошибку в esp_tls, описывающую причину ошибки кодами детализации mbedtls. После возврата информация об ошибке очищается.
esp_tls_get_and_clear_error_type
Вернет последнюю ошибку определенного типа, захваченную в esp_tls. После возврата информация об ошибке очищается.
esp_tls_get_error_handle
Вернет код ошибки ESP-TLS.
esp_tls_get_global_ca_store
Получит указатель на используемое в настоящий момент глобальное хранилище сертификатов (global CA store). Приложение должно сначала вызвать esp_tls_set_global_ca_store(). Затем одно и то же хранилище сертификатов может использоваться приложением для другого API, не относящегося к esp_tls(2).
esp_tls_plain_tcp_connect
Создает чистое соединение TCP, возвращая в случае успеха корректный дескриптор файла сокета.
Примечания:
(1) Функция esp_tls_conn_http_new предоставлена для обеспечения обратной совместимости с предыдущими версиями библиотек. Альтернативная функция, обладающая таким же функционалом, esp_tls_conn_http_new_sync (и её асинхронная, не блокирующая версия esp_tls_conn_http_new_async). (2) Модификация указателя может вызывать сбой при проверке сертификатов.
[Словарик]
ALPN Application-Layer Protocol Negotiation, согласование протокола прикладного уровня — расширение протокола TLS, которое позволяет на прикладном уровне модели OSI согласовать протокол безопасного соединения, что позволяет избежать дополнительных циклов обмена, и который не зависит от целевого протокола прикладного уровня. ALPN используется для установления соединений HTTP/2 без дополнительных циклов согласования протокола обмена данными. Клиент и сервер при этом могут обмениваться данными HTTP/2 через порты, назначенные для HTTPS с HTTP/1.1, или продолжать использовать HTTP/1.1, не создавая новое соединение и не закрывая исходное (из Википедии).
CA Certificate Authority, хранилище доверенных сертификатов, или публичный сервер для выдачи доверенных сертификатов.
TLS Transport Layer Security, протокол защиты транспортного уровня[1]), как и его предшественник SSL (secure sockets layer — слой защищённых сокетов) — криптографические протоколы, обеспечивающие защищённую передачу данных между узлами в сети Интернет. TLS и SSL используют асимметричное шифрование для аутентификации, симметричное шифрование для конфиденциальности и коды аутентичности сообщений для сохранения целостности сообщений (из Википедии).
PSK Pre-Shared Key, пара согласованных ключей, которая распределена между участниками соединения (клиентом и сервером). В TLS это используется как один из вариантов проверки сервера на стороне клиента.
SNI Server Name Indication — расширение компьютерного протокола TLS[1], которое позволяет клиенту сообщать имя хоста, с которым он желает соединиться во время процесса «рукопожатия». Это позволяет серверу предоставлять несколько сертификатов на одном IP-адресе и TCP-порту, и, следовательно, позволяет работать нескольким безопасным (HTTPS) сайтам (или другим сервисам поверх TLS) на одном IP-адресе без использования одного и того же сертификата на всех сайтах. Это эквивалентно возможности основанного на имени виртуального хостинга из HTTP/1.1. Запрашиваемое имя хоста не шифруется, что позволяет злоумышленнику его перехватить (из Википедии).