Как предоставить пользовательский пакет TLS CACert с Arduino на ESP32?

С ESP8266 довольно легко передать пользовательский пакет, например:

BearSSL::WiFiClientSecure client;
BearSSL::CertStore certStore;

int numCerts = certStore.initCertStore(FSTYPE, "/certs.idx", "/certs.ar");
client.setCertStore(&certStore);

Конечно, ESP32 не использует BearSSL. Поэтому я пытаюсь найти эквивалент.

Похоже, мне нужно использовать setCACertBundle(), но я не могу найти пример, как это сделать. Есть много материала по Platform.io, но я его не использую.

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

Какой эквивалент кода ESP8266 для ESP32?

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

, 👍0


2 ответа


Лучший ответ:

1

Чтобы использовать коллекцию сертификатов с ESP32, необходимо предоставить центры сертификации в определённом формате, называемом «пакетом». Это двоичная структура данных с отсортированными по порядку сертификатами и заголовком «смещение» для быстрого доступа к каждому сертификату.

Создание этого объекта немного усложняется.

Библиотека Arduino ESP32 v3.0.7 (последняя версия) основана на ESP-IDF 5.1.4

Формат для пакета в этом случае

  uint16_t num_certs
  uint16_t offset * num_certs
  <cert_data>

Обратите внимание, что смещения 16-битные, поэтому полный пакет не может быть больше 64 КБ. Использование https://curl.se/ca/cacert.pem слишком большое (создаёт пакет размером 69 КБ), поэтому некоторые центры сертификации в конце списка (в алфавитном порядке по CN) могут не работать. При предоставлении пользовательского пакета убедитесь, что сгенерированный файл не превышает 64 КБ.

Итак, в esp-idf v5.4 формат изменился. Теперь

  uint32_t offset * num_certs

Количество сертификатов определяется по формуле offset[0]/sizeof(uint32_t)

Это означает, что скрипт gen_crt_bundle.py в ветке master не будет работать с библиотекой Arduino ESP32, и вам придется использовать скрипт из ветки v5.1.

Кроме того, это может означать, что при обновлении библиотеки Arduino до версии 5.4 или выше потребуется изменить код.

Чтобы использовать пользовательский пакет CA, нам нужно использовать генератор из версии 5.1, т.е. из

https://github.com/espressif/esp-idf/tree/release/v5.1/components/mbedtls/esp_crt_bundle

Мы можем сгенерировать файл пакета с

python3 gen_crt_bundle.py -q -i *.pem

Это создаст x509_crt_bundle

Это необходимо включить в вашу программу. Это можно разместить в SPIFFS. или LittleFS и загружается в переменную во время выполнения, или мы можем встроить его непосредственно в нашу программу как константную переменную PROGMEM.

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

например

#!/bin/sh

F=x509_crt_bundle

echo 'static const uint8_t certs_bundle[] PROGMEM = {'
xxd -p $F | sed 's/\(..\)/0x\1,/g' | sed '$s/,$//'
echo '};'

s=`wc -c < $F`

echo "#define SIZE_OF_CERTS $s"

Это создаст вывод, который выглядит следующим образом

static const uint8_t certs_bundle[] PROGMEM = {
0x00,0x8a,0x00,0x36,0x01,0x26,0x30,0x34,0x31,0x0b,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x46,0x52,0x31,0x12,0x30,0x10,0x06,0x03,0x55,0x04,0x0a,

....

0x4f,0x17,0x02,0x03,0x01,0x00,0x01

};
#define SIZE_OF_CERTS 64057

(О, хорошо; 64057 ниже 65535, так что все смещения будут в порядке).

Я назвал этот вывод certs.h.

Теперь это можно использовать

#include <WiFi.h>
#include <time.h>
#include <WiFiClientSecure.h>
#include "certs.h"

...
  WiFiClientSecure client;
  client.setCACertBundle(certs_bundle,SIZE_OF_CERTS);

Обратите внимание, что это изменилось в версии 3.0.4, где вам нужно передавать размер массива; в версии 3.0.3 это будет просто client.setCACertBundle(certs_bundle) и, скорее всего, не будет работать из-за ошибки.

Теперь esp-idf v5.1 имеет cacrt_all.pem, который появился во вторник, 10 января, 04:12:06. 2023 GMT вместе с DST ROOT CA X3 (cacrt_local.pem), который может отсутствовать больше не понадобится, но предназначен для старой перекрестной подписи letsencrypt.

Чтобы использовать этот встроенный пакет, встроенный в libmbedtls.a, тоже придётся немного повозиться. Насколько я понимаю,


extern const uint8_t x509_crt_imported_bundle_bin_start[] asm("_binary_x509_crt_bundle_start");
extern const uint8_t x509_crt_imported_bundle_bin_end[]   asm("_binary_x509_crt_bundle_end");

...
  client.setCACertBundle(x509_crt_imported_bundle_bin_start,x509_crt_imported_bundle_bin_end-x509_crt_imported_bundle_bin_start);

Это позволило мне подключиться к серверам с сертификатами от распространенных центров сертификации (например, Amazon, Google, LetsEncrypt).

,

Рад, что вы решили эту проблему., @hcheung

@hcheung Спасибо за ваш ответ; он помог мне найти решение!, @Stephen Harris


1

Как использовать CA Cert Bundle в Arduino ESP32?

Процесс использования setCACertBundle() ESP32 описан в файле README.md библиотеки Arduino из библиотеки WiFiClientSecure для платформы Arduino ESP32 версии 2.x. По какой-то причине в версии 3.x, когда библиотека WiFiCleintSecure была преобразована в библиотеку NetworkClientSecure, это сократилось до простого расплывчатого абзаца README.md. В последнее время Espressif любит это делать и иногда нарушает обратную совместимость или удаляет что-то полезное из своего Github без объяснения причин.

Создать пакет сертификатов CA

Чтобы использовать пакет сертификатов, сначала необходимо сгенерировать его. Вы можете сгенерировать его самостоятельно или получить сертификаты CA, извлеченные из Mozilla, по ссылке здесь. Загружаемое хранилище сертификатов CA Mozilla cacert.pem имеет формат PEM и занимает около 200 КБ в несжатом виде.

В Esp-idf есть утилита Python для генерации двоичной версии пакета CA, размер которой составляет всего около 64 КБ. Загрузите gen_crt_bundle.py (возможно, вам потребуется установить пакет Python cryptography с помощью pip3 install cryptography перед запуском скрипта Python).

Запустите скрипт Python с помощью следующей командной строки для создания двоичного файла:

python3 gen_crt_bundle.py -i cacert.pem

Это должно создать файл с именем x509_crt_bundle, создайте каталог в каталоге проекта и переместите созданный пакет в этот каталог.

mkdir data data/cert
mv x509_crt_bundle data/cert/

Настройка PlatformIO

Код, который я собираюсь показать, работает для PlatformIO, но не работает в Arduino IDE (я не знаю, почему, и на самом деле я сейчас не очень-то использую Arduino IDE, поэтому я не тратил время на то, чтобы выяснить, почему).

Предполагается, что у вас установлен PlatfromIO и создан проект Arduino. Добавьте флаг сборки в platformio.ini:

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
board_build.embed_files = data/cert/x509_crt_bundle
monitor_speed = 115200

Вызовите setCACertBundle() перед установлением безопасного соединения

Код для использования setCACertBundle() на самом деле довольно прост и похож на пример использования WiFiClientSecure, только с двумя отличиями:

  1. добавьте extern const uint8_t rootca_crt_bundle_start[] asm("_binary_data_cert_x509_crt_bundle_bin_start"); в свой скетч;
  2. вызовите client.setCACertBundle(rootca_crt_bundle_start); перед установлением клиентского соединения.

Вот полный пример наброска, который я использовал для тестирования. Он обращается к тестовому сайту httpbin.org (вы можете попробовать другие сайты), а сервер httpbin возвращает ответ в формате JSON.

#include <WiFiClientSecure.h>

const char* ssid     = "wifi ssid";
const char* password = "wifi password";

const char*  server = "httpbin.org";

extern const uint8_t rootca_crt_bundle_start[] asm("_binary_data_cert_x509_crt_bundle_bin_start");

WiFiClientSecure client;

void setup() {
  Serial.begin(115200);
  delay(100);

  Serial.print("Connecting to SSID: ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(1000);
  }
  Serial.println("Connected");

  // client.setCACert(ISRG_Root_X1_CA);
  client.setCACertBundle(rootca_crt_bundle_start);
  // client.setCertificate(test_client_cert); // for client verification
  // client.setPrivateKey(test_client_key);  // for client verification

  Serial.println("\nStarting connection to server...");
  if (!client.connect(server, 443))
    Serial.println("Connection failed!");
  else {
    Serial.println("Connected to server!");
    // Make a HTTP request:
    client.printf("GET https://%s/get HTTP/1.1\n", server);
    client.printf("Host: %s\n", server);
    client.println("Connection: close");
    client.println();

    while (client.connected()) {
      String line = client.readStringUntil('\n');
      if (line == "\r") {
        Serial.println("headers received");
        break;
      }
    }
    // read the response body
    while (client.available()) {
      char c = client.read();
      Serial.write(c);
    }

    client.stop();
  }
}

void loop() {
  // do nothing
}

На снимке экрана показана общая структура каталогов настройки platformIO, а также ответ, полученный от тестового сервера (httpbin.org).

Для справки, вот системные настройки моего PlatformIO, показываемые во время компиляции.

PLATFORM: Espressif 32 (6.9.0) > Espressif ESP32 Dev Module
HARDWARE: ESP32 240MHz, 320KB RAM, 4MB Flash
DEBUG: Current (cmsis-dap) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
PACKAGES: 
 - framework-arduinoespressif32 @ 3.20017.0 (2.0.17) 
 - tool-esptoolpy @ 1.40501.0 (4.5.1) 
 - tool-mkfatfs @ 2.0.1 
 - tool-mklittlefs @ 1.203.210628 (2.3) 
 - tool-mkspiffs @ 2.230.0 (2.30) 
 - toolchain-xtensa-esp32 @ 8.4.0+2021r2-patch5
,

Всё это замечательно, за исключением вопроса: «Есть много всего, связанного с Platform.io, но я этим не пользуюсь». Я ищу что-то, что можно собрать с помощью arduino-cli или (в худшем случае) Arduino IDE. Что такое rootca_crt_bundle_start в вашем решении? Это сам пакет сертификатов или какой-то указатель на файловую систему флеш-памяти?, @Stephen Harris

Это указатель на двоичный пакет сертификатов CA в data/cert, @hcheung

Дайте мне знать, если найдёте что-то подходящее для Arduino IDE. Второй вариант, описанный в README, ссылку на который я дал, должен работать и для Arduino IDE, но для него потребуется загрузить файл Pen из SPIFFS, что займёт более 200 КБ оперативной памяти..., @hcheung

Если я ничего не упускаю, _храните пакет как файл SPIFFS, но тогда придётся загружать его в оперативную память во время выполнения и тратить драгоценные 64 КБ памяти._ Но ни в README версии 2, ни версии 3 не сказано, что мне делать с пакетом. Мне просто передать его напрямую в setCACertBundle()? Похоже, сейчас все используют PlatformIO, поэтому я не могу найти ни одного примера без него!, @Stephen Harris

Кажется, я разобрался. Не помогло то, что формат структур данных в esp-idf изменился, а arduino-esp32 использует более старую версию! Но, похоже, работает :-), @Stephen Harris