PKI для IOT, архитектура защищенной сети ESP32 + Mosquitto SSL и Flash Encryption для хранения сертификатов

от автора

Цель статьи — показать вариант построения защищенной 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 и т.п.). 

Переходим к созданию сертификатов

Что нам нужно? 

  1. Создать CA (очень секретный ключ и очень публичный сертификат)

  2. Создать ключ и сертификат сервера (подписанные CA)

  3. Создать процесс подготовки ключей-сертификатов для клиентов.

Пп 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. 

Что мы будем делать:

  1. Подготовим схему партиций и соответствующую partition tables, с отдельной партицией fctry, в которую в дальнейшем поместим сертификаты клиента

  2. Сгенерируем глобальную для всего проекта подпись для приложения-бинарника ESP32 и дайджест для прошивки eFuse и активации Secure Boot 2

  3. Сгенерируем ключи для Flash encryption

  4. Сгенерируем ключи для NVS encryption

  5. Подготовим нашу партицию с сертификатами, зашифруем ее соответствующими ключами 

  6. Создадим обязательные шифрованные партиции для nvs key и otadata

  7. Подпишем и зашифруем программные файлы

  8. Прошьем в аппаратный eFuse открытый дайджест для Secure Boot 2 и ключ для Flash encryption, безвозвратно прошьем в eFuse активацию Secure Boot 2 и Flash Encryption 

  9. Прошьем бинарники с софтом,  сертификатами и другими системными данными 

  10. Переведем в Release Mode, т.е. установим запрет отладки и запрет возможности доступа к шифрованным данным; оставим (несколько снижая безопасность по мнению производителя) только возможность загрузки через UART предварительно зашифрованных партиций и подписанных-зашифрованных программных файлов

  11. Пример на языке с использования сертификатов в защищенном хранилище

Все это сделаем и на борту дивайса появится партиция с нашими сертификатами. Далее, используя наш пример кода работы с защищенной партицией и публичные примеры кода для MQTT из репозиториев производителя мы отправим сообщение на сервер.

  1. Подготовка таблицы партиций (планирование флеш-хранилища)

Описанный выше кейс с сертификатами я включаю в схему партиций следующим образом (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-хранилища.

  1. Создадим подпись для 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

  1. Создаем уникальный (должен быть уникальным для каждого дивайса) ключ для Flash encryption:

espsecure.py generate_flash_encryption_key f:\dev1s12345\dev1s12345_flash_encryption_key.bin

Уникальный ключ для каждого дивайса нужен в целях исключения компрометации защиты всей сети устройств в случае компрометации одного ключа. Использование уникальных ключей обнуляет риски по известным уязвимостям ESP32.

  1. Создадим ключи защиты nvs_key:

nvs_partition_gen.py generate-key --keyfile dev1s12345_nvs_key.bin --outdir .\dev1s12345
  1. Создадим исходник партиции 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.

  1. Подготовим системную партицию 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
  1. Подпишем 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
  1. Переходим к прошивке ключа 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
  1. Теперь нужно прошить бинарный образ флеш-памяти:

esptool.py --port COM1 -b 460800 --after no_reset write_flash --force 0x0 .\dev1s12345\dev1s12345_merged_flash.bin
  1. И финальный шаг — перевод устройства в 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() 
  1. В финале статьи приведу пример кода для использования сертификатов из защищенных хранилищ 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; }

На этом все, спасибо за прочтение и комментарии.


ссылка на оригинал статьи https://habr.com/ru/articles/797819/


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *