Цель статьи - показать вариант построения защищенной Iot-инфраструктуры для сети устройств на базе ESP32 и обменяться опытом.
Общую идею и весь проект разделил на темы:
развертывание mosquitto SSL/TLS из docker-контейнера
создание сертификатов для брокера Mosquitto SSL и клиентов ESP32
архитектура хранилища сертификата для ESP32 и практические способы защиты
подготовка прошивки устройства с применение встроенных способов защиты для ESP32 - Secure Boot 2, Flash Encryption, NVS Encryption
Итак, основные отличия, на которые нужно обратить внимание в данном примере. Для аутентификации MQTT-клиентов на ESP32 применим сертификаты. Мы исключаем инфраструктуру клиентских логинов/паролей для аутентификации в Mosquitto. Вместо пары логин/пароль Mosquitto позволяет использовать уникальные CN в клиентских сертификатах. На устройствах будем хранить сертификаты в отдельной защищенной партиции (Flash encryption + NVS encryption) и прошивать их совместно с прошивкой основной программы (бинарник которой мы также защитим подписью с помощью Secure Boot 2). В этой статье нет цели углубиться в типовые процессы информационной безопасности, такие как применение аппаратных средств, задачи снижения рисков компрометации CA и связанные с этим вопросы организации безопасного производственного контура.
Присту��им к практике.
Первым этапом поднимаем Mosquitto SSL
Мне нравится вариант развертывания с использованием docker compose. Это позволяет уже с первых пробных попыток легко использовать, развивать и документировать инфраструктуру. Ниже я ориентируюсь на то, что читатели имеют представление о базовых вопросах администрирования и использования docker.
Подготовим файл инфраструктуры mosquitto docker-compose.yml.
В этой конфигурации мы определяем порт 8883 для связи с внешним миром. А также укажем расположение хранилищ mosquitto.
version: '3.8' services: mosquitto: image: eclipse-mosquitto:2 ports: - 8883:8883 - 9001:9001 volumes: - ./mosquitto/config:/mosquitto/config/ - ./mosquitto/data:/mosquitto/data/ - ./mosquitto/log:/mosquitto/log/ - ./mosquitto/certs:/mosquitto/certs/ networks: - mosquitto networks: mosquitto: name: mosquitto driver: bridge
Далее, переходим к конфигурированию mosquitto, с учетом нашей специфики.
Файл конфигурации mosquitto.conf
Определим основные важные конфигурационные параметры:
# храним все сообщения не только в памяти, но и на диске persistence true # определим нужные нам типы логов (если они отличаются от дефолтных error, warning, notice and information) log_type # В логах установим удобный формат времени log_timestamp_format %Y-%m-%dT%H:%M:%S # Запретим анонимные подключения: allow_anonymous false # Установим прослушивание порта 8883 на нашем сервере: # MQTT over TLS listener 8883 # Установим требование клиентских сертификатов: require_certificate true # Исключим клоны при подключении клиентов: use_username_as_clientid true # Теперь нам нужно указать брокеру, что мы используем CN в качестве username: use_identity_as_username true # Укажем сертификаты CA, сервера, а также ключ сервера: cafile /mosquitto/certs/ca.crt certfile /mosquitto/certs/server.crt keyfile /mosquitto/certs/server.key
На этом основные настройки завершены.
Если у нас есть готовая топология топиков, то mosquitto позволяет навести красоту в вопросе изоляции топиков. Для этого применяется ACL-файл с правилами. Имя файла указывается в mosquitto.conf
acl_file my_acl_file_01
В этом файле указываются паттерны для топиков и что для меня оказалось особенно полезно - возможность указывать в паттерне подстановку client id или username. Пример разрешения записи в топик, содержащий username:
pattern write my_sensor/%u/data
Эти возможности ACL позволяет сервису-обработчику сообщений получать в топике достоверный идентификатор отправителя (который, напомню, брокер извлекает из CN сертификата) и установить на системном уровне (т.е. без разработки своего программного кода валидации) строгие правила для формирования топиков на стороне устройств. Эти преимущества во многом могут раскрыться при масштабировании сети с применением способов и решений типа mosquitto-bridge, NiFi и/или трансляции потока сообщений в глобальную стрим-экосистему (Kafka и т.п.).
Переходим к созданию сертификатов
Что нам нужно?
Создать CA (очень секретный ключ и очень публичный сертификат)
Создать ключ и сертификат сервера (подписанные CA)
Создать процесс подготовки ключей-сертификатов для клиентов.
Пп 1 и 2 выполняются руками администратора и являются по сути типовой задачей поднятия CA и сервера. Мы используем самоподписанные сертификаты. Для закрытой сети этого более чем достаточно. Напомню, что самоподписанные сертификаты отличаются от авторизованных тем, что цепочка авторизованных сертификатов включается в публичное ПО, такое как браузеры и т.п. Однако, для целей создания своей закрытой сети использование авторизованных (подписанных каким-то субъектом, совершенно неочевидно, что дружественным и постоянным) цепочек не только не обязательно, но и на мой взгляд и практический опыт - вредно.
Создадим CA
Расположение всех сертификатов, относящихся к серверной инфраструктуре положим в ~/server/. Для CA будет~/ca/.
Ключ CA (без passphrase):
openssl genrsa -out ca/ca.key 2048
Сертификат CA (на 30 лет):
openssl req -new -x509 -days 10950 -key ca/ca.key -out ca/ca.crt
You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:RU State or Province Name (full name) [Some-State]:Moscow Locality Name (eg, city) []:Moscow Organization Name (eg, company) [Internet Widgits Pty Ltd]:Leo4 Organizational Unit Name (eg, section) []:iot Common Name (e.g. server FQDN or YOUR name) []:iot.leo4.ru Email Address []:iot@leo4.ru
Перенесем сертификат CA в отдельный каталог для дальнейшего использования:
cp ca/ca.crt certs/ca.crt
Сделаем ключ и сертификат брокера:
mkdir server sudo openssl genrsa -out server/server.key 2048
Подготовим запрос в CA:
oleg@leo4broker2:sudo openssl req -new -out server/server.csr -key server/server.key You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:RU State or Province Name (full name) [Some-State]:Moscow Locality Name (eg, city) []:Moscow Organization Name (eg, company) [Internet Widgits Pty Ltd]:Leo4 Organizational Unit Name (eg, section) []:mqtt-broker Common Name (e.g. server FQDN or YOUR name) []:iot.leo4.ru Email Address []:iot@leo4.ru Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []:. An optional company name []:.
Выдадим сертификат сервера на основе запроса:
oleg@leo4broker2:~$ sudo openssl x509 -req -in server/server.csr -CA ca/ca.crt -CAkey ca/ca.k ey -CAcreateserial -out server/server.crt -days 10950 Certificate request self-signature ok subject=C = RU, ST = Moscow, L = Moscow, O = Leo4, OU = mqtt-broker, CN = iot.leo4.ru, emailAddress = iot@leo4.ru
Копируем сертификаты в целевую папку, проверяем:
oleg@mqtt:~/server$ ls server.crt server.csr server.key oleg@mqtt:~$ cp ca/ca.crt mosquitto/certs/ oleg@mqtt:~$ cp server/server.crt mosquitto/certs/ oleg@mqtt:~$ cp server/server.key mosquitto/certs/ cp: cannot open 'server/server.key' for reading: Permission denied oleg@mqtt:~$ sudo cp server/server.key mosquitto/certs/ oleg@mqtt:~$ cd mosquitto oleg@mqtt:~/mosquitto$ ls certs config data etc log oleg@mqtt:~/mosquitto$ cd certs oleg@mqtt:~/mosquitto/certs$ ls ca.crt server.crt server.key
Запускаем сервис Mosquitto:
oleg@mqtt:~$ sudo docker compose up -d [+] Running 4/4 ✔ mosquitto 3 layers [⣿⣿⣿] 0B/0B Pulled 4.5s ✔ c926b61bad3b Pull complete 0.6s ✔ a87e6a8c3b38 Pull complete 0.6s ✔ 4e2ff55c815a Pull complete 0.5s [+] Running 1/2 Network mosquitto Created 1.0s ✔ Container oleg-mosquitto-1 Started
Проверим работу сервиса:
oleg@mqtt:~$ sudo docker compose ps NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS oleg-mosquitto-1 eclipse-mosquitto:2 "/docker-entrypoint...." mosquitto 43 minutes ago Up 12 minutes 0.0.0.0:8883->8883/tcp, :::8883->8883/tcp, 1883/tcp, 0.0.0.0:9001->9001/tcp, :::9001->9001/tcp
Проверим подключение к сервису из внешнего мира и сертификаты сервера:
D:\OpenSSL-Win64\bin>openssl s_client -connect iot.leo4.ru:8883 CONNECTED(000001E0) depth=1 C = RU, ST = Moscow, L = Moscow, O = Leo4, OU = IT, CN = iot.leo4.ru, emailAddress = iot@leo4.ru verify error:num=19:self-signed certificate in certificate chain verify return:1 depth=1 C = RU, ST = Moscow, L = Moscow, O = Leo4, OU = IT, CN = iot.leo4.ru, emailAddress = iot@leo4.ru verify return:1 depth=0 C = RU, ST = Moscow, L = Moscow, O = Leo4, OU = mqtt, CN = iot.leo4.ru, emailAddress = iot@leo4.ru verify return:1 --- Certificate chain 0 s:C = RU, ST = Moscow, L = Moscow, O = Leo4, OU = mqtt, CN = iot.leo4.ru, emailAddress = iot@leo4.ru i:C = RU, ST = Moscow, L = Moscow, O = Leo4, OU = IT, CN = iot.leo4.ru, emailAddress = iot@leo4.ru a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 v:NotBefore: Jan 25 13:56:50 2024 GMT; NotAfter: Jan 17 13:56:50 2054 GMT 1 s:C = RU, ST = Moscow, L = Moscow, O = Leo4, OU = IT, CN = iot.leo4.ru, emailAddress = iot@leo4.ru i:C = RU, ST = Moscow, L = Moscow, O = Leo4, OU = IT, CN = iot.leo4.ru, emailAddress = iot@leo4.ru a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256 v:NotBefore: Jan 25 13:50:25 2024 GMT; NotAfter: Jan 17 13:50:25 2054 GMT --- Server certificate -----BEGIN CERTIFICATE----- ..
Итоговое дерево с файлами mosquitto и сертификатами:
oleg@mqtt:~$ tree . . ├── ca │ ├── ca.crt │ └── ca.key ├── docker-compose.yml ├── mosquitto │ ├── certs │ │ ├── ca.crt │ │ ├── server.crt │ │ └── server.key │ ├── config │ │ └── mosquitto.conf │ ├── data │ │ └── mosquitto.db │ ├── etc │ └── log │ └── mosquitto.log └── server ├── server.crt ├── server.csr └── server.key 8 directories, 12 files
* папку server со всем содержимым можно удалить за ненадобностью.
Файл ~/mosquitto/config/mosquitto.conf из работающего примера:
persistence true persistence_location /mosquitto/data/ #logs log_type subscribe log_type unsubscribe log_type websockets log_type error log_type warning log_type notice log_type information log_dest file /mosquitto/log/mosquitto.log connection_messages true log_timestamp true log_timestamp_format %Y-%m-%dT%H:%M:%S #log to the console log_dest stdout #log to the topic $SYS/broker/log/# log_dest topic #password_file /mosquitto/passwd_file allow_anonymous false # MQTT over WebSockets listener 9001 0.0.0.0 protocol websockets # MQTT over TLS listener 8883 require_certificate true use_identity_as_username true use_username_as_clientid true cafile /mosquitto/certs/ca.crt certfile /mosquitto/certs/server.crt keyfile /mosquitto/certs/server.key
Далее, нам нужно наладить процесс подготовки клиентских сертификатов. Покажу синтаксис для ОС Линукс, а в дальнейшем перенесем файлы и работу на компьютер Windows т.к. там развернуто IDE.
Пройдем путь создания первого сертификата. Перед началом зафиксируем одно правило - идентификатор устройства = имя папки для файлов клиента = имена файлов сертификатов и ключей = username = client Id = CN=префиксы файлов с бинарными образами ключей и прошивками устройства. В нашем случае это будет строка, сформированная по шаблону <dev><порядковый номер><s><случайные 5 цифр>. Например: dev1s12345. Шаблон придуман на ходу, исключительно для целей данной статьи. Итак, готовим первый сертификат “ручками”.
Генерируем ключ:
oleg@mqtt:~$ mkdir dev1s12345 oleg@mqtt:~$ cd dev1s12345 oleg@mqtt:~/dev1s12345$ openssl genrsa -out dev1s12345.key 2048
Подготовим запрос на сертификат:
oleg@mqtt:~/dev1s12345$ openssl req -new -out dev1s12345.csr -key dev1s12345.key You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:RU State or Province Name (full name) [Some-State]:Moscow Locality Name (eg, city) []:Moscow Organization Name (eg, company) [Internet Widgits Pty Ltd]:IT Organizational Unit Name (eg, section) []:IT Common Name (e.g. server FQDN or YOUR name) []:dev1s12345 Email Address []:device@leo4.ru Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []: An optional company name []:
Эти операции для серии устройств нужно автоматизировать с помощью производственных скриптов и/или производственных веб-сервисов. Более того, напомню, что генерация ключа, формирование запроса к CA - эти функции и синтаксис openssl являются платформенно независимыми и могут быть автоматизированы отдельно, взаимодействуя через свои АПИ и веб-сервисы. Сгенерированные выше ключи для устройства попадают в разряд “чувствительной” информации в контексте информационной безопасности. В тоже время, эти файлы должны быть доступны на стадии подготовки и прошивки устройства.
Получаем сертификат клиента на основе файла-запроса:
oleg@mqtt:~/dev1s12345$ sudo openssl x509 -req -in dev1s12345.csr -CA ~/ca/ca.crt -CAkey ~/ca/ca.key -CAcreateserial -ou t dev1s12345.crt -days 10950 Certificate request self-signature ok subject=C = RU, ST = Moscow, L = Moscow, O = IT, OU = IT, CN = dev1s12345, emailAddress = device@leo4.ru
Пойдем дальше, скопировав на локальную машину полученные файлы для нашего первого клиента:
dev1s12345.key, dev1s12345.crt, ca.crt
ESP32, готовим инфраструктуру и подключаемся к брокеру
Мое рабочее место на базе Windows + Espressif IDE (ESP-IDF) + openssl.
Что мы будем делать:
Подготовим схему партиций и соответствующую partition tables, с отдельной партицией fctry, в которую в дальнейшем поместим сертификаты клиента
Сгенерируем глобальную для всего проекта подпись для приложения-бинарника ESP32 и дайджест для прошивки eFuse и активации Secure Boot 2
Сгенерируем ключи для Flash encryption
Сгенерируем ключи для NVS encryption
Подготовим нашу партицию с сертификатами, зашифруем ее соответствующими ключами
Создадим обязательные шифрованные партиции для nvs key и otadata
Подпишем и зашифруем программные файлы
Прошьем в аппаратный eFuse открытый дайджест для Secure Boot 2 и ключ для Flash encryption, безвозвратно прошьем в eFuse активацию Secure Boot 2 и Flash Encryption
Прошьем бинарники с софтом, сертификатами и другими системными данными
Переведем в Release Mode, т.е. установим запрет отладки и запрет возможности доступа к шифрованным данным; оставим (несколько снижая безопасность по мнению производителя) только возможность загрузки через UART предварительно зашифрованных партиций и подписанных-зашифрованных программных файлов
Пример на языке с использования сертификатов в защищенном хранилище
Все это сделаем и на борту дивайса появится партиция с нашими сертификатами. Далее, используя наш пример кода работы с защищенной партицией и публичные примеры кода для MQTT из репозиториев производителя мы отправим сообщение на сервер.
Подготовка таблицы партиций (планирование флеш-хранилища)
Описанный выше кейс с сертификатами я включаю в схему партиций следующим образом (my_part_table.csv):
# Name, Type, SubType, Offset, Size, Flags nvs, data, nvs,0x11000, 0x4000, otadata, data, ota, , 0x2000, encrypted phy_init, data, phy, , 0x1000, factory, app, factory, , 0x110000, ota_0, app, ota_0, , 0x110000, ota_1, app, ota_1, , 0x110000, fctry, data, nvs, , 0x10000, db1, data, nvs, , 512K, nvs_key, data, nvs_keys, , 0x1000, encrypted
В этом наборе партиций нам нужны nvs_key и fctry.
nvs_key - это системная партиция для хранения ключей шифрования NVS. Особенность ESP32 с ее системой защиты хранилищ в том, что аппаратно, с помощью flash encryption защищается ключ nvs_key для nvs-партиций, который, в свою очередь защищает все NVS-хранилища.
Создадим подпись для Secure Boot 2:
espsecure.py generate_signing_key --version 2 --scheme rsa3072 f:\global_secret\secure_boot_signing_key.pem
... И дайджест этой подписи, который нам потребуется для прошивки соответствующего сектора eFuse в ESP32:
espsecure.py digest_sbv2_public_key --keyfile f:\global_secret\secure_boot_signing_key.pem --output f:\secure_boot2\digest.bin
Подготовим папку для первого дивайса (клиента):
.\dev1s12345
Создаем уникальный (должен быть уникальным для каждого дивайса) ключ для Flash encryption:
espsecure.py generate_flash_encryption_key f:\dev1s12345\dev1s12345_flash_encryption_key.bin
Уникальный ключ для каждого дивайса нужен в целях исключения компрометации защиты всей сети устройств в случае компрометации одного ключа. Использование уникальных ключей обнуляет риски по известным уязвимостям ESP32.
Создадим ключи защиты nvs_key:
nvs_partition_gen.py generate-key --keyfile dev1s12345_nvs_key.bin --outdir .\dev1s12345
Создадим исходник партиции fctry в формате csv для загрузки сертификатов
(файл dev1s12345_fctry_partition.csv):
key,type,encoding,value mqtt_ns,namespace,, server_uri,data,string,mqtts://iot.leo4.ru:8883 device_id,data,string,dev1s12345 ca_cert,file,binary,ca.crt cert,file,binary,dev1s12345.crt priv_key,file,binary,dev1s12345.key
Обратите внимание, что в партицию мы, помимо сертификатов, сразу добавили URI нашего mqtt-сервера и включили строку с идентификатором дивайса device_id для использования подстановок в имена топиков и т.п. Если заранее известна топология топиков, то шаблоны или имена топиков следует включить в этот же файл. Таким образом, мы на 100% исключим из кода программы зависимость от конкретного брокера.
Конвертируем исходник партиции в бинарный формат и шифруем с ранее созданными ключами для nvs_key:
nvs_partition_gen.py encrypt --inputkey .\dev1s12345\keys\dev1s12345_nvs_key.bin .\dev1s12345\dev1s12345_fctry_partition.csv .\dev1s12345\dev1s12345_fctry_partition_enc.bin 0x10000
Обращаю внимание, что партиции с типом NVS шифруются однократно, исключительно “своими” ключами nvs_key. Дополнительно помечать такие партиции в partition tables флагом encrypted не нужно. А также, нельзя шифровать такие партиции ключами для Flash Encryption.
Подготовим системную партицию nvs key с ключами защиты NVS партиций:
espsecure.py --keyfile .\dev1s12345\dev1s12345_flash_encryption_key.bin --address 0x3e0000 --output .\dev1s12345\dev1s12345_nvs_key_partition_enc.bin .\dev1s12345\keys\dev1s12345_nvs_key.bin
Пояснения по настройке IDE.
В menuconfig или в интерфейсе IDE - sdkconfig укажем кастомную партицию my_part_table.csv, далее - включаем опции Secure Boot 2 и Flash Encryption. Отключаем опцию “подписывать при сборке”. Проверяем опцию NVS Encryption (должна быть включена автоматически при включении Flash Encryption).
Исходное расположение файлов после сборки относительно корневой папки проекта:
.\build\myapp.bin .\build\bootloader\bootloader.bin .\build\partition_table\partition-table.bin .build\ota_data_initial.bin
Эти 4 файла после сборки и перед манипуляциями по подписанию и шифрованию нужно скопировать на наш “производственный” диск в папку .\app
Продолжим подготовку - теперь шифруем системную партиции otadata:
espsecure.py --keyfile .\dev1s12345\dev1s12345_flash_encryption_key.bin --address 0x15000 --output .\dev1s12345\dev1s12345_otadata_enc.bin .\app\ota_data_initial.bin
Следующий артефакт - таблица партиций (partition table):
espsecure.py --keyfile .\dev1s12345\dev1s12345_flash_encryption_key.bin --address 0x10000 --output .\dev1s12345\dev1s12345_partition_table_enc.bin .\app\partition-table.bin
Подпишем bootloader и основное приложение:
espsecure.py sign_data --version 2 --keyfile .\global_secret\secure_boot_signing_key.pem --output .\app\bootloader_signed.bin .\app\bootloader.bin espsecure.py sign_data --version 2 --keyfile .\global_secret\secure_boot_signing_key.pem --output .\app\myapp_signed.bin .\app\myapp.bin
Шифруем подписанные бинарники bootloader и приложения:
espsecure.py --keyfile .\dev1s12345\dev1s12345_flash_encryption_key.bin --address 0x1000 --output .\dev1s12345\dev1s12345_bootloader_enc.bin .\app\bootloader_signed.bin espsecure.py --keyfile .\dev1s12345\dev1s12345_flash_encryption_key.bin --address 0x20000 --output .\dev1s12345\dev1s12345_myapp_enc.bin .\app\myapp_signed.bin
В результате, у нас подготовлены все бинарники для прошивки ключей шифрования, сертификатов и программы. Дерево “производственного” раздела выглядит так:
F: ├── secure_boot2 │ └── digest.bin ├── global_secret │ ├── secure_boot_signing_key.pem │ ├── app │ ├── bootloader.bin │ ├── ota_data_initial.bin │ ├── myapp.bin │ ├── partition-table.bin │ ├── bootloader_signed.bin │ └── myapp_signed.bin ├── ca │ └── ca.crt ├── prepare_stage1.cmd ├── burn_stage1.cmd ├── my_part_table.csv ├── dev1s12345 ├── keys │ └── dev1s12345_nvs_key.bin ├── mqtt │ ├── dev1s12345.key │ └── dev1s12345.crt ├── dev1s12345_flash_encryption_key.bin ├── dev1s12345_fctry_partition.csv ├── dev1s12345_fctry_partition_enc.bin ├── dev1s12345_nvs_key_partition_enc.bin ├── dev1s12345_otadata_enc.bin ├── dev1s12345_bootloader_enc.bin ├── dev1s12345_myapp_enc.bin └── dev1s12345_partition_table_enc.bin
Бинарники с суффиксом enc предназначены для прошивки в отдельные партиции устройства. Каждая партиция прошивается отдельной командой. Иногда, для удобства прошивки (одной командой) бинарники нужно объединить (“мерджить”). Например, вот так:
esptool.py --chip ESP32 merge_bin -o^ .\dev1s12345\dev1s12345_merged_flash.bin --flash_mode dio --flash_size 4MB^ 0x1000 .\dev1s12345\dev1s12345_bootloader_enc.bin^ 0x10000 .\dev1s12345\dev1s12345_partition_table_enc.bin^ 0x20000 .\dev1s12345\dev1s12345_myapp_enc.bin^ 0x15000 .\dev1s12345\dev1s12345_otadata_enc.bin^ 0x3e0000 .\dev1s12345\dev1s12345_nvs_key_partition_enc.bin^ 0x350000 .\dev1s12345\dev1s12345_fctry_partition_enc.bin
Переходим к прошивке ключа flash encryption и дайджеста подписи ПО.
Одной командой прошиваем все ключи и флаги, нужные для активации secure boot 2 и flash encryption. Запись ключей является необратимой операцией. При этом, нужно обратить внимание на то, что прошивка дайджеста и защита области efuse от записи, а также необратимая прошивка флагов доступа READONLY ко всей области ключей при активации secure boot 2 сделает невозможной вторичную прошивку ключей flash encryption. Рекомендуется прошивать ключи flash encryption либо в первую очередь (до прошивки secure boot 2), либо в пакетном режиме, как показано в моем примере.
espefuse.py --port COM1 burn_key flash_encryption .\dev1s12345\dev1s12345_flash_encryption_key.bin^ burn_key secure_boot_v2 .\secure_boot2\\digest.bin^ burn_efuse FLASH_CRYPT_CNT 127^ burn_efuse FLASH_CRYPT_CONFIG 0xF^ burn_efuse ABS_DONE_1
Теперь нужно прошить бинарный образ флеш-памяти:
esptool.py --port COM1 -b 460800 --after no_reset write_flash --force 0x0 .\dev1s12345\dev1s12345_merged_flash.bin
И финальный шаг - перевод устройства в Release mode.
Здесь произойдет отключение прозрачного использования встроенного криптопровайдера (отключение Development node), а также отключение встроенных функций отладчика JTAG и защита от перезаписи блока с сетевым MAC. С этого момента загрузка через UART возможна только для предварительно подписанного и зашифрованного бинарника.
espefuse.py --port COM3 burn_efuse DISABLE_DL_ENCRYPT 0x1 burn_efuse DISABLE_DL_DECRYPT 0x1 burn_efuse DISABLE_DL_CACHE 0x1 burn_efuse JTAG_DISABLE 0x1 write_protect_efuse MAC write_protect_efuse RD_DIS write_protect_efuse DISABLE_DL_ENCRYPT
Стоит отметить два важных фактора для дальнейшей эксплуатации устройства. Если замена сертификатов и/или локальная перепрошивка ПО в течение жизненного цикла устройства не будет необходима, то рекомендуется удалить все производственные файлы устройства. Для дальнейших обновлений прошивки ПО посредством OTA потребуется только подписывать бинарник с приложением (шифровать не требуется). В другом случае, например, при необходимости замены сертификатов потребуется пересоздание партиции dev1s12345_fctry_partition_enc.bin. Для этого нужно будет вновь использовать ключи для NVS-партиции конкретного устройства, которые находятся в файле .\dev1s12345\keys\dev1s12345_nvs_key.bin. Все остальные файлы и ключи рекомендуется удалить.
Для усиления мер защиты устройства в случаях, когда обновление ПО планируется исключительно по “воздуху” (OTA) производитель микроконтроллеров ESP32 предусмотрел (даже рекомендует) и возможность полного отключения загрузки/прошивки ПО через UART. Для этого есть удобная возможность в рантайме вызвать функцию:
esp_efuse_disable_rom_download_mode()
В финале статьи приведу пример кода для использования сертификатов из защищенных хранилищ ESP32.
Инициализация защищенной NVS-партиции:
static esp_err_t custom_nvs_part_init(const char *name) { #if CONFIG_NVS_ENCRYPTION esp_err_t ret = ESP_FAIL; const esp_partition_t *key_part = esp_partition_find_first( ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS_KEYS, NULL); if (key_part == NULL) { ESP_LOGE(TAG, "CONFIG_NVS_ENCRYPTION is enabled, but no partition with subtype nvs_keys found in the partition table."); return ret; } nvs_sec_cfg_t cfg = {}; ret = nvs_flash_read_security_cfg(key_part, &cfg); if (ret != ESP_OK) { /* We shall not generate keys here as that must have been done in default NVS partition initialization case */ ESP_LOGE(TAG, "Failed to read NVS security cfg: [0x%02X] (%s)", ret, esp_err_to_name(ret)); return ret; } ret = nvs_flash_secure_init_partition(name, &cfg); if (ret == ESP_OK) { ESP_LOGI(TAG, "NVS partition \"%s\" is encrypted.", name); }else ESP_LOGE(TAG, "Failed to init NVS partition: [0x%02X] (%s)", ret, esp_err_to_name(ret)); return ret; #else return nvs_flash_init_partition(name); #endif }
Функция извлечения сертификатов из открытого NVS-хранилища и размещения их в памяти:
int alloc_and_read_from_nvs(nvs_handle handle, const char *key, char **value) { size_t required_size = 0; if ((nvs_get_blob(handle, key, NULL, &required_size)) != ESP_OK) return -1; *value = calloc(1, required_size + 1); /* The extra byte is for the NULL termination */ if (*value) { nvs_get_blob(handle, key, *value, &required_size); return 0; } return -1; }
На этом все, спасибо за прочтение и комментарии.
