Программирование ARM ESP32-C3: компонент TLS Fri, March 29 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

ESP32-C3: компонент TLS Печать
Добавил(а) microsin   

Компонент 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.

Эта структура содержит параметры конфигурации ESP-TLS.

typedef struct esp_tls_cfg
{
   const char **alpn_protos; /*!< Протоколы приложения, требуемые для HTTP2.
                                  Если требуется поддержка HTTP2/ALPN, должны
                                  произойти переговоры по утверждению списка
                                  протоколов. Формат списка: длина, за которой
                                  идет имя протокола. Для большинства случаев
                                  подойдет следующий вариант:
                                  const char **alpn_protos = { "h2", NULL };
                                  Здесь 'h2' это имя протокола */
 
   union
   {
      const unsigned char *cacert_buf; /*!< Сертификат CA в буфере. Формат может
                                            быть PEM или DER, в зависимости от
                                            выбора поддержки mbedtls.
                                            Этот буфер в случае PEM должен
                                            завершаться символом NULL. */
      const unsigned char *cacert_pem_buf; /*!< Устаревшее имя сертификата CA,
                                            оставленное для совместимости. */
   };
 
   union
   {
      unsigned int cacert_bytes;       /*!< Размер сертификата CA, на который
                                            указывает cacert_buf (размер включает
                                            и символ NULL, если используется
                                            формат PEM). */
      unsigned int cacert_pem_bytes;   /*!< Устаревшее имя длины сертификата CA,
                                            оставленное для совместимости. */
   };
 
   union
   {
      const unsigned char *clientcert_buf; /*!< Сертификат клиента в буфере.
                                                Формат может быть PEM или DER,
                                                в зависимости от выбора поддержки
                                                mbedtls. Этот буфер в случае PEM
                                                должен завершаться символом NULL. */
      const unsigned char *clientcert_pem_buf; /*!< Устаревшее имя сертификата клиента,
                                                оставленное для совместимости. */
   };
 
   union
   {
      unsigned int clientcert_bytes;       /*!< Размер сертификата клиента, на который
                                                указывает clientcert_pem_buf (размер
                                                включает и символ NULL, если используется
                                                формат PEM). */
      unsigned int clientcert_pem_bytes;   /*!< Устаревшее имя размера сертификата клиента,
                                                оставленное для совместимости. */
   };
 
   union
   {
      const unsigned char *clientkey_buf;  /*!< Буфер для ключа клиента. Формат буфера
                                                может быть PEM или DER, в зависимости
                                                от выбора поддержки mbedtls. Этот буфер
                                                в случае PEM должен завершаться
                                                символом NULL. */
      const unsigned char *clientkey_pem_buf; /*!< Устаревшее имя ключа клиента. */
   };
 
   union
   {
      unsigned int clientkey_bytes;        /*!< Размер ключа клиента, на который указывает
                                                clientkey_pem_buf (размер включает и символ
                                                NULL, если используется формат PEM). */
      unsigned int clientkey_pem_bytes;    /*!< Устаревшее имя размера ключа клиента. */
   };
 
   const unsigned char *clientkey_password;/*!< Строка пароля расшифровки ключа клиента. */
 
   unsigned int clientkey_password_len;    /*!< Длина строки пароля, на которую указывает
                                                clientkey_password. */
 
   bool non_block;                         /*!< Конфигурирует не блокирующий режим. Если
                                                установлено в true, то нижележащий сокет
                                                будет сконфигурирован в non blocking режиме
                                                после установки сессии TLS. */
 
   bool use_secure_element;                /*!< Установите эту опцию для использования элемента
                                                безопасности чипа atecc608a (интегрирован
                                                на плату разработчика ESP32-WROOM-32SE). */
 
   int timeout_ms;                         /*!< Таймаут сети в миллисекундах. */
 
   bool use_global_ca_store;               /*!< Использует глобальное хранилище сертификатов
                                                ca_store для всех соединений, в которых
                                                этот параметр установлен в true. */
 
   const char *common_name;                /*!< Если не NULL, то CN сертификата сервера должен
                                                совпадать с этим именем. Если NULL, то CN
                                                сертификата сервера должен совпадать с hostname. */
 
   bool skip_common_name;                  /*!< Пропускать любую проверку поля CN сертификата
                                                сервера. */
 
   tls_keep_alive_cfg_t *keep_alive_cfg;   /*!< Разрешает таймаут TCP keep-alive для соединения SSL. */
 
   const psk_hint_key_t* psk_hint_key;     /*!< Указатель на PSK hint и key. Если не NULL (и сертификаты
                                                NULL), то сконфигурированной настройкой разрешена
                                                аутентификация PSK. Важное замечание: для соединения
                                                этот указатель должен быть достоверным. */
 
   esp_err_t (*crt_bundle_attach)(void *conf);
                                           /*!< Указатель на функцию esp_crt_bundle_attach. Разрешает
                                                использование пакета сертификатов (certification
                                                bundle) для верификации сервера, что должно быть
                                                разрешено в menuconfig. */
 
   void *ds_data;                          /*!< Указатель на данные цифровой подписи контекста
                                                периферийного устройства DS. */
 
   bool is_plain_tcp;                      /*!< Использование соединения без TLS: когда установлено
                                                в true, то esp-tls использует чистый транспорт TCP
                                                вместо соединения TLS/SSL. Обратите внимание,
                                                что можно соединиться чистым транспортом TCP
                                                напрямую, с помощью esp_tls_plain_tcp_connect(). */
 
   struct ifreq *if_name;                  /*!< Имя интерфейса, через который проходят данные.
                                                Без этой установки используется интерфейс
                                                по умолчанию. */
 
#ifdef CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS
   esp_tls_client_session_t *client_session; /*! Указатель на контекст тикета сессии клиента. */
#endif /* CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS */
} esp_tls_cfg_t;

Поверх слоя защищенного соединения TLS соединения может быть реализован любой протокол наподобие HTTP1, HTTP2 и т. д. Простой пример клиента HTTPS, который использует ESP-TLS для установки соединения защищенного сокета: protocols/https_request.

Дерево структуры компонента TLS:

├── esp_tls.c
├── esp_tls.h
├── esp_tls_mbedtls.c
├── esp_tls_wolfssl.c
└── private_include
    ├── esp_tls_mbedtls.h
    └── esp_tls_wolfssl.h

Компонент 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 следующим образом:

int cert_selection_callback(mbedtls_ssl_context *ssl)
{
   /* Код, который выполнит callback */
   ...
   return 0;
}
 
esp_tls_cfg_t cfg = {
   cert_select_cb = cert_section_callback,
};

[Выбор библиотеки SSL/TLS нижнего уровня]

У компонента 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).

2a. Загрузите wolfssl командой:

git clone https://github.com/espressif/esp-wolfssl.git

2b. Подключите esp-wolfssl в ESP-IDF установкой EXTRA_COMPONENT_DIRS в файле CMakeLists.txt своего проекта, как это сделано в wolfssl/examples. Для справки см. секцию "Опциональные (не обязательные) переменные проекта" в описании системы сборки ESP-IDF [5].

После выполнения шагов 2a и 2b у вас появится опция выбора wolfssl в качестве нижележащей библиотеки SSL/TLS library в меню конфигурации проекта:

idf.py menuconfig → ESP-TLS → choose SSL/TLS Library → mbedtls/wolfssl

Сравнение 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]), или предоставлен в коде приложения. */
esp_tls_cfg_t cfg = {
   .clientcert_buf = /* сертификат клиента */,
   .clientcert_bytes = /* длина сертификата клиента */,
   /* другие опции конфигурации */
   .ds_data = (void *)ds_ctx,
};

Примечание: когда используется цифровая подпись для соединения 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. Запрашиваемое имя хоста не шифруется, что позволяет злоумышленнику его перехватить (из Википедии).

[Ссылки]

1. ESP TLS site:espressif.com.
2. ESP x509 Certificate Bundle site:espressif.com.
3. ESP32-C3: клиент HTTP.
4. ESP-WOLFSSL.
5. ESP-IDF Build System.
6. Digital Signature (DS) site:espressif.com.
7. ESP32­C3 Technical Reference Manual site:espressif.com.
8. ESP32: библиотека энергонезависимого хранилища данных.
9. ESP-MQTT SSL Sample application (mutual authentication).

 

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


Защитный код
Обновить

Top of Page