3,1K подписчиков

httpS (SSL/TLS) соединения на Arduino / ESP8266

565 прочитали

Добрый день, уважаемый читатель! Продолжаем тему HTTP-запросов, но сегодня поговорим об защищенных соединениях. В наше время HTTPS обязателен для каждого веб-сайта, браузеры уже давно помечают сайты без TLS как небезопасные.

Впрочем, все сказанное в данной статье относится не только к HTTP(S) протоколу, но и к шифрованным соединениям посредством других "прикладных" протоколов - MQTT, FTP, OTA и так далее.

В этой статье я расскажу как работать с защищенными интернет-запросами применительно только к ESP8266 или ESP32 под управлением фреймворка Arduino, так как на ESP-IDF библиотека mbedTLS довольно сложная и требует отдельного обсуждения.

Источник: Яндекс Картинки
Источник: Яндекс Картинки

Немножко теории

Для начала определимся с терминами.

HTTPS, SSL, TLS - а в чём, собственно разница?

HTTPS не является отдельным протоколом. HTTPS - это самый обычный HTTP, но работающий через шифрованные транспортные механизмы SSL и TLS. То есть заменили "открытое" соединение на "защищенное" и всё - добавили буковку S в конце названия, сам протокол при этом никак не изменился. Изменился только транспортный уровень - то есть как передаются через сеть биты и байты.

Точно так же протокол MQTT может работать как поверх TCP/IP, так и поверх TLS - ему без разницы, в каком виде дойдут байты до сервера - в открытом или зашифрованном. Но вот MQTTS его при этом почему-то никто не называет, хотя термин SFTP есть.

А в чем разница между SSL и TLS? По большому счёту это одно и то же, просто старая и новая версии одного и того же действа (конечно, "внутри" они отличаются друг от друга, но дня нас с вами никакой разницы нет). Термины SSL (secure sockets layer — слой защищённых сокетов) и TLS (transport layer security — протокол защиты транспортного уровня) часто используются как взаимозаменяемые, поскольку TLS пришел на место SSL. На текущий момент SSL считается устаревшим и на большинстве сайтов уже не используется. Но во множестве документов и инструкций по прежнему можно встретить этот термин.

Источник: Википедия
Источник: Википедия

Установка защищенного соединения между клиентом и сервером

Я не специалист в области криптографии, думаю и вы тоже. Но в самых общих чертах понимать как работает SSL / TLS нужно. Основные шаги процедуры создания защищённого сеанса связи (выдержка из вики) - так называемое "рукопожатие":

  • клиент подключается к серверу, поддерживающему TLS, и запрашивает защищённое соединение;
  • клиент предоставляет список поддерживаемых алгоритмов шифрования и хеш-функций;
  • сервер выбирает из списка, предоставленного клиентом, наиболее надёжные алгоритмы среди тех, которые поддерживаются сервером, и сообщает о своём выборе клиенту;
  • сервер отправляет клиенту цифровой сертификат для собственной аутентификации. Цифровой сертификат содержит имя сервера, имя удостоверяющего центра сертификации и открытый ключ сервера;
  • клиент, до начала передачи данных, проверяет полученный сертификат сервера относительно имеющихся у клиента корневых сертификатов удостоверяющих центров (центров сертификации). Клиент также может проверить, не отозван ли серверный сертификат, связавшись с сервисом доверенного удостоверяющего центра;
  • для шифрования сессии используется сеансовый ключ. Получение общего секретного сеансового ключа клиентом и сервером проводится по протоколу Диффи-Хеллмана.

На этом заканчивается процедура подтверждения связи. Между клиентом и сервером установлено безопасное соединение, и все данные, передаваемые по нему, шифруются и расшифровываются с использованием сеансового ключа до тех пор, пока соединение не будет завершено.

Я не зря выделил жирным шаги, связанные с проверкой сертификата сервера - это основное, что потребуется от вас как программиста для установки защищенного соединения с "умного" устройства.
Все остальные шаги скрыты в недрах библиотек и нам не особо важны.

Из всего этого следует, что для TLS и HTTPS потребуется сертификат. Но сертификат самого сервера нам вообще не интересен, так как его сервер и так вышлет клиенту при "рукопожатии", нас интересует корневой сертификат, которому мы безусловно доверяем.

Все сертификаты сайтов выданы и подписаны какими-либо центрами сертификации, которые так же имеют свой сертификат. Центр сертификации не обязательно должен быть один, их может быть несколько в цепочке, подписанных один за другим. Например: сертификат для сайта wqtt.ru был выдан ЦС R3, а тому, в свою очередь, выдал сертификат ISRG Root X1.

Добрый день, уважаемый читатель! Продолжаем тему HTTP-запросов, но сегодня поговорим об защищенных соединениях.-3

Первый сертификат в списке называется "корневым". Если корневому сертификату мы доверяем целиком и полностью, то остальные "вложенные" сертификаты мы сможем проверить просто "по цепочке". Поэтому для проверки сертификата любого сайта мы должны иметь "всего лишь" список доверенных корневых сертификатов, которым мы доверяем абсолютно. Его и потребуется указать библиотеке (любой - что для esp8266 / arduino, что для esp32 / esp-idf) перед началом защищенного соединения.

У вас может возникнуть вопрос - "а почему на компьютерах с windows или linux, например, мы не указываем никаких корневых сертификатов, а "замочек" в браузере есть"? Да просто потому что на взрослых компьютерах "в тихую" ведется специальный реестр актуальных корневых сертификатов, исходя из которого браузеры и остальные программы проверяют сертификаты сайтов.
Хранилище сертификатов в windows - certmgr.msc
Хранилище сертификатов в windows - certmgr.msc

Но список корневых сертификатов (на компьютере) достаточно большой и занимает довольно много памяти, что для относительно "маленьких" микроконтроллеров абсолютно не приемлемо. Приходится себя ограничивать, и указывать только те, что требуются только для конкретной программы.

Впрочем, у ESP32 памяти побольше, и если вам её не жалко, то можно "закачать" в прошивку и весь список корневых сертификатов - ESP-IDF начиная с версии 4.3 (если не ошибаюсь) это позволяет. Но об этом будет рассказано в следующей статье.

Как получить файл корневого сертификата

Итак, нам нужен файл корневого сертификата. Самое простое в windows - использовать обычный браузер, например хром. Если у вас установлен антивирус, придется его отключить на время, так как антивирусные программы подменяют сертификаты ЦС своими собственными и это ни к чему хорошему не приведет. Затем откройте в браузере нужный вам сайт и кликните на замочек в адресной строке:

Добрый день, уважаемый читатель! Продолжаем тему HTTP-запросов, но сегодня поговорим об защищенных соединениях.-5

Затем кликните на строку "Безопасное подключение", а затем на "Действительный сертификат":

Добрый день, уважаемый читатель! Продолжаем тему HTTP-запросов, но сегодня поговорим об защищенных соединениях.-6

Откроется окошечко просмотра сертификата, где мы должны перейти на вкладку "Подробнее", выделить в иерархии верхний сертификат и нажать "Экспорт":

Добрый день, уважаемый читатель! Продолжаем тему HTTP-запросов, но сегодня поговорим об защищенных соединениях.-7

Затем просто указываем, где бы мы хотели сохранить файл и нажимаем ОК. В итоге у вас в выбранной папке должен появится новый файлик с вот таким примерно содержанием:

Добрый день, уважаемый читатель! Продолжаем тему HTTP-запросов, но сегодня поговорим об защищенных соединениях.-8

Это и есть то, что нам нужно - корневой сертификат! Как его "прописать" в прошивку, я расскажу чуть ниже. Можно устанавливать TLS-соединение? Не совсем!

Срок действия сертификата

У всех сертификатов (и корневых и не только) имеется встроенная головная боль для embedded-программистов, и называется она "срок действия сертификата". После заранее определенного в сертификате срока он считается "испорченным". Кроме того, сертификат может быть отозван досрочно, если есть подозрения на его компрометацию. И тогда таким "порченным" корневым сертификатом ничего и никого проверить уже будет нельзя.

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

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

Кроме того, для проверки сертификата, вам обязательно потребуется правильные системные дату и время. Казалось бы - зачем устанавливать правильные дату и время? Только для логов? А вот и нет - как минимум чтобы проверить сроки действия сертификатов.

После сброса микроконтроллера системное время в нём - 01 января 1970 00:00:00 GMT, и оно абсолютно не годится для проверки сертификатов. А это значит, что даже имея "на руках" все козыри и туз бубновый, мы всё равно не сможем установить защищенное соединение.

Что же делать, как же быть? Варианта как минимум два - использовать внешние или встроенные часы RTC или получить актуальное время через SNTP. Как получить время через сеть интернет, я уже рассказывал ранее.

И только после того, как системные "часики" вашего микроконтроллера тикают правильно, можно приступать к активным действиям. Поехали!

Использование TLS на Arduino и ESP8266

Как я уже писал в прошлой статье цикла, класс WiFiClient обеспечивает только транспортный уровень, то есть тот самый пресловутый стек TCP/IP.

Впрочем, все сказанное в данной статье относится не только к HTTPS протоколу, но и к шифрованным соединениям посредством MQTT, FTP, OTA и так далее.

Шаг 1 - изменяем переменные и добавляем новую

Для использования защищенных соединений нам потребуется использовать другой аналогичный класс - WiFiClientSecure. Это тот же самый WiFiClient, но с поддержкой SSL и TLS. Достаточно заменить переменную в вашей программе, и всё - поддержка TLS уже имеется.

Кроме того, потребуется создать дополнительную глобальную или статическую переменную под хранилище корневых сертификатов в программе, в примере это объект класса X509List:

Добрый день, уважаемый читатель! Продолжаем тему HTTP-запросов, но сегодня поговорим об защищенных соединениях.-9

Шаг 2 - синхронизируем время

После подключения к сети рекомендуется сразу же получить правильное системное время с SNTP, сделать это можно примерно так:

Пример подключения к сети WiFi и получения системного времени
Пример подключения к сети WiFi и получения системного времени

Шаг 3 - добавляем содержимое корневого сертификата в текст программы

Добавим к нашему проекту строковую константу, например так:

static const char ISRG_Root_x1[] PROGMEM = R"EOF()EOF";

а затем скопируйте содержимое из файла сертификата и вставьте между круглых скобок. У вас должно получиться нечто вроде этого:

Добрый день, уважаемый читатель! Продолжаем тему HTTP-запросов, но сегодня поговорим об защищенных соединениях.-11

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

Только не забудьте вставить в код новый сертификат, когда истечёт срок действия текущего

Шаг 4 - добавляем сертификат в список корневых доверенных сертификатов

После этого уже можно смело добавить корневой сертификат в список доверенных:

Добрый день, уважаемый читатель! Продолжаем тему HTTP-запросов, но сегодня поговорим об защищенных соединениях.-12

Вот теперь всё готово к TLS-соединению!

Шаг 5 - укажите другой порт на сервере, который соответствует защищенному протоколу

Как правило, для открытых и защищенных соединений сервер использует разные порты. Для протокола HTTP стандартным незащищенным является порт 80, а для защищенных соединений - уже другой - 443. У MQTT это могут быть 1883 и 8883, но могут быть использованы и любые другие. Поэтому для корректной работы программы придется изменить и номер порта тоже.

Если взять пример из предыдущей статьи, то изменений будет не так уж и много:

Добрый день, уважаемый читатель! Продолжаем тему HTTP-запросов, но сегодня поговорим об защищенных соединениях.-13

Как то так, на мой взгляд не так уж и сложно.

Как сделать то же самое, но для MQTT-клиента - я уже рассказывал в другой статье, отличий там очень не много:

В следующий раз поговорим о том же самом, но для ESP-IDF.

_______________

На этом пока всё, до встречи на сайте и на dzen-канале!

👍 Понравилась статья? Поддержите канал лайком или комментарием! Каналы на Дзене "живут" только за счет ваших лайков.

📌Подпишитесь на канал и вы всегда будете в курсе новых статей.

🔶 Полный архив статей вы найдете здесь