Устройство без OTA — это бомба замедленного действия
Вы выпустили 10 000 умных датчиков. Через три месяца обнаружилась критическая уязвимость в TLS-библиотеке. Без OTA у вас два варианта: отозвать все устройства (убыток) или оставить уязвимость (риск). С OTA — один push, и все 10 000 устройств обновлены за ночь.
OTA (Over-The-Air) — это не просто «загрузить файл по Wi-Fi». Это система, которая должна гарантировать: обновление дойдёт, установится корректно, не превратит устройство в кирпич и не будет подменено атакующим. Каждое из этих требований — отдельная инженерная задача.
Анатомия OTA-обновления
Процесс обновления выглядит просто, но каждый шаг содержит ловушки:
┌──────────┐ 1. Проверка ┌──────────┐
│Устройство│───────────────────▶│OTA-сервер│
│ │◀───────────────────│ │
│ │ 2. Метаданные │ │
│ │ (версия, размер,│ │
│ │ хеш, подпись) │ │
│ │ │ │
│ │ 3. Скачивание │ │
│ │◀───────────────────│ │
│ │ (по частям) │ │
│ │ │ │
│ │ 4. Верификация │ │
│ │ (подпись, хеш) │ │
│ │ │ │
│ │ 5. Установка │ │
│ │ (запись в flash) │ │
│ │ │ │
│ │ 6. Перезагрузка │ │
│ │ + валидация │ │
│ │ │ │
│ │ 7. Отчёт │ │
│ │───────────────────▶│ │
└──────────┘ └──────────┘
Шаг 1–2: Проверка и метаданные
Устройство периодически (или по push) спрашивает сервер: «есть новая версия?». Сервер отвечает манифестом: версия, размер, SHA-256 хеш, цифровая подпись, совместимость (hardware revision, текущая версия).
Критичные моменты:
- Канал проверки должен быть защищён (TLS). Иначе MITM подменит манифест
- Версионирование: Semantic Versioning (MAJOR.MINOR.PATCH) + hardware compatibility tag
- Rollout policy: не обновлять все устройства сразу — сначала 1%, потом 10%, потом 100%
Шаг 3: Скачивание
Для устройств с Wi-Fi/Ethernet — HTTPS. Для LPWAN (LoRaWAN, NB-IoT) — фрагментация и возобновление:
| Транспорт | Типичный размер прошивки | Время скачивания | Особенности |
|---|---|---|---|
| Wi-Fi | 1–16 МБ | Секунды | Без ограничений |
| LTE/NB-IoT | 100 КБ – 1 МБ | Минуты | Трафик платный |
| LoRaWAN | 10–100 КБ | Часы–дни | Duty cycle, фрагментация (FUOTA) |
| BLE | 100 КБ – 1 МБ | Минуты | Через смартфон (DFU) |
Для LoRaWAN существует стандарт FUOTA (Firmware Update Over The Air), но он сложен: multicast, фрагментация, CRC на каждый блок, часы на обновление.
Шаг 4: Верификация
Перед установкой устройство обязано проверить:
- Целостность: SHA-256 хеш скачанного файла совпадает с манифестом
- Подлинность: цифровая подпись прошивки валидна (Ed25519 или ECDSA-P256)
- Совместимость: прошивка предназначена для этого hardware revision
Публичный ключ для проверки подписи хранится в bootloader или Secure Element — не в самой прошивке (иначе вредоносная прошивка подменит ключ).
Шаг 5–6: Установка и перезагрузка
Здесь решается главный вопрос: что делать, если обновление прервалось (пропало питание, ошибка записи)?
Стратегии обновления
1. A/B (Dual-bank)
Самая надёжная схема. Flash разделён на два слота: A (активный) и B (обновление).
Flash layout:
┌──────────────┐
│ Bootloader │ (не обновляется, проверяет подпись)
├──────────────┤
│ Slot A │ ← текущая прошивка (работает)
│ (active) │
├──────────────┤
│ Slot B │ ← новая прошивка (скачивается сюда)
│ (update) │
├──────────────┤
│ Settings │ (NVS, конфигурация)
└──────────────┘
Процесс:
- Новая прошивка записывается в Slot B, пока старая работает из Slot A
- Bootloader переключается на Slot B и перезагружает
- Новая прошивка стартует и проводит self-test
- Если self-test пройден — подтверждает обновление (confirm)
- Если self-test провален или нет confirm за N секунд — автоматический откат на Slot A
| Плюсы | Минусы |
|---|---|
| Атомарное обновление | Нужен двойной объём Flash |
| Автоматический откат | Дороже для MCU с малой Flash |
| Обновление без простоя | — |
| Прерывание безопасно | — |
2. In-place (Single-bank)
Прошивка обновляется «на месте» — поверх текущей.
Flash layout:
┌──────────────┐
│ Bootloader │
├──────────────┤
│ Firmware │ ← стирается, записывается новая версия
├──────────────┤
│ Staging │ (временный буфер, может быть во внешней Flash)
├──────────────┤
│ Settings │
└──────────────┘
Процесс:
- Новая прошивка скачивается во временный буфер (внешняя Flash, RAM)
- Bootloader стирает основной раздел и записывает новую прошивку
- Если прервалось — кирпич (если нет recovery)
| Плюсы | Минусы |
|---|---|
| Экономия Flash | Нет отката |
| Подходит для MCU с 64–128 КБ | Прерывание = кирпич |
| — | Простой при обновлении |
3. Delta (дифференциальное)
Передаётся не полная прошивка, а разница между текущей и новой версией.
| Параметр | Full image | Delta |
|---|---|---|
| Размер передачи (типичный) | 200 КБ | 20–50 КБ |
| Экономия трафика | — | 70–90% |
| Сложность на устройстве | Низкая | Высокая (применение патча) |
| Требования к RAM | Низкие | Нужен буфер для патча |
| Риск | Низкий | Версионная зависимость |
Delta критичен для LPWAN-устройств (LoRaWAN, NB-IoT), где трафик ограничен или платный. Алгоритмы: bsdiff, detools, SUIT CBOR.
Проблема: delta-обновление зависит от точной текущей версии. Если устройство пропустило промежуточную версию — delta не применится. Нужен fallback на full image.
Безопасный загрузчик (Secure Boot)
Bootloader — единственный компонент, которому устройство доверяет безусловно. Он проверяет подпись прошивки перед запуском. Если подпись невалидна — прошивка не запускается.
Цепочка доверия
ROM bootloader (неизменяемый, в кремнии)
│ проверяет подпись
▼
Bootloader (MCUboot / ESP Secure Boot)
│ проверяет подпись
▼
Firmware (ваша прошивка)
Каждый уровень проверяет следующий. Компрометация ROM bootloader невозможна (он в silicon). Компрометация bootloader требует физического доступа к Flash (и отключённого Flash Encryption).
MCUboot
MCUboot — стандартный загрузчик для Zephyr, Mynewt, Mbed OS, Apache NuttX. Поддерживает:
| Функция | Описание |
|---|---|
| Secure Boot | Проверка подписи (RSA-2048, ECDSA-P256, Ed25519) |
| A/B swap | Атомарное переключение слотов |
| Revert | Автоматический откат при неудачном обновлении |
| Encrypted images | Шифрование прошивки в Flash |
| Serial recovery | Восстановление через UART при полном сбое |
| Hardware keys | Поддержка Secure Element для хранения ключей |
MCUboot занимает 16–32 КБ Flash — приемлемо для большинства MCU.
ESP32 Secure Boot
Espressif предлагает собственную реализацию:
- Secure Boot V2: RSA-3072 или ECDSA-256, ключ в eFuse (one-time programmable)
- Flash Encryption: AES-256, уникальный ключ на каждый чип
- Anti-rollback: счётчик версий в eFuse — нельзя откатить на старую прошивку
Особенность: после активации Secure Boot отключить его невозможно — eFuse пережигается навсегда.
OTA-серверы и платформы
Self-hosted
| Платформа | Протокол | MCU поддержка | Особенности |
|---|---|---|---|
| Eclipse hawkBit | HTTP/HTTPS (DDI API) | Любой с HTTP | Java, open-source, enterprise-grade |
| Mender | HTTPS (REST API) | Yocto, Debian | A/B, delta, dashboard, open-source |
| RAUC | HTTPS + hawkBit | Yocto, Debian | Только Linux-устройства |
| SWUpdate | HTTP/HTTPS + hawkBit | Linux, bare-metal | Гибкий, scriptable |
| Golioth | CoAP/DTLS | Zephyr, ESP-IDF | Cloud-native, MCU-first |
Облачные
| Платформа | Протокол | Стоимость | Особенности |
|---|---|---|---|
| AWS IoT Device Management | MQTT + S3 | $0.05/устройство/мес | Jobs API, fleet management |
| Azure IoT Hub | MQTT + Blob Storage | $0.05–0.15/устройство/мес | Device Twins, ADU |
| Golioth | CoAP | $0.03/устройство/мес | Zephyr-нативный, LightDB |
| Memfault | HTTPS | $0.10/устройство/мес | OTA + мониторинг + crash analytics |
| Particle | Проприетарный | $0.04/устройство/мес | Полная платформа (hardware + cloud) |
Минимальный self-hosted OTA
Для прототипа или малой серии можно обойтись без специализированной платформы:
- S3/MinIO — хранилище прошивок с версионированием
- JSON-манифест — файл с текущей версией, URL, SHA-256, подписью
- HTTPS-сервер (nginx) — отдача манифеста и бинарника
- Скрипт на устройстве — периодическая проверка манифеста, скачивание, верификация
Этого достаточно для сотен устройств. Для тысяч — нужен hawkBit или Mender с rollout-политиками.
Протоколы доставки
Для MCU (bare-metal, RTOS)
| Протокол | Overhead | Подходит для | Безопасность |
|---|---|---|---|
| HTTPS | Высокий (TLS handshake) | Wi-Fi, LTE | TLS 1.3 |
| CoAP/DTLS | Низкий | LPWAN, constrained | DTLS 1.2 |
| MQTT + файл | Средний | Уже есть MQTT-соединение | TLS |
| LwM2M (Object 5) | Низкий | Стандартизированный OTA | DTLS |
| BLE DFU | Низкий | Через смартфон | BLE encryption |
| SUIT (RFC 9019) | Минимальный | IETF стандарт для IoT OTA | COSE подпись |
SUIT (Software Updates for Internet of Things) — новый стандарт IETF, специально разработанный для constrained IoT. Манифест в CBOR, подпись в COSE, минимальный overhead. Поддерживается в Zephyr.
Для Linux-устройств
| Инструмент | Механизм | Delta | Rollback |
|---|---|---|---|
| Mender | A/B full-image | Да (mender-binary-delta) | Автоматический |
| RAUC | A/B или in-place | Через casync | Автоматический |
| SWUpdate | Scriptable handlers | Через librsync | Configurable |
| OSTree (libostree) | Git-like content-addressable | Нативный | Да (reflog) |
| Balena | Docker containers | Да (layer-based) | Container rollback |
OSTree заслуживает отдельного внимания: он работает как git для корневой файловой системы. Обновления — это коммиты, откат — checkout предыдущего коммита. Используется в Fedora IoT, Automotive Grade Linux, Endless OS.
Подводные камни
1. Питание прервалось во время записи Flash
Это самый частый сценарий отказа. Решения:
- A/B-схема: запись идёт в неактивный слот, прерывание безопасно
- Журналирование: bootloader ведёт лог состояния обновления
- Атомарный swap: MCUboot переключает слоты одной записью в trailer
2. Устройство не подтвердило обновление
Новая прошивка загрузилась, но зависла до вызова boot_set_confirmed(). Bootloader ждёт N секунд (watchdog), не дождался — откат.
Обязательно: self-test в первые секунды после обновления. Проверить: сеть работает, датчики отвечают, конфигурация читается.
3. Устройство в поле без связи
Некоторые устройства выходят на связь раз в сутки (или реже). Обновление может занять дни. Нужна:
- Возобновляемая загрузка (resume): скачать 40% сегодня, 60% завтра
- Контроль целостности блоков: CRC на каждый фрагмент, не только на весь файл
- Приоритизация трафика: OTA не должно блокировать передачу данных
4. Rollback-атака
Атакующий заставляет устройство откатиться на старую версию с известной уязвимостью. Защита:
- Anti-rollback counter: монотонный счётчик в OTP/eFuse. Прошивка с версией ниже счётчика отклоняется
- Минимальная версия в bootloader: bootloader знает минимально допустимую версию
5. Обновление bootloader
Обновлять bootloader — самая рискованная операция. Если новый bootloader не работает — устройство кирпич навсегда (bootloader проверяет сам себя, замкнутый круг).
Решения:
- Не обновлять bootloader в поле (самый безопасный вариант)
- Dual bootloader: два bootloader с переключением (как A/B для прошивки)
- ROM-based recovery: recovery-код в неизменяемой ROM (если MCU поддерживает)
Чеклист OTA для продакшена
| # | Проверка | Приоритет |
|---|---|---|
| 1 | Подпись прошивки (Ed25519/ECDSA) проверяется bootloader | Критический |
| 2 | A/B-схема с автоматическим откатом | Критический |
| 3 | TLS/DTLS для скачивания | Критический |
| 4 | Anti-rollback (монотонный счётчик версий) | Высокий |
| 5 | Self-test после обновления (сеть, датчики, конфиг) | Высокий |
| 6 | Возобновляемая загрузка для нестабильных каналов | Высокий |
| 7 | Rollout policy (1% → 10% → 100%) | Высокий |
| 8 | Dashboard: версии всех устройств в реальном времени | Средний |
| 9 | Delta-обновления для LPWAN | Средний |
| 10 | Шифрование прошивки в Flash | Средний |
| 11 | Bootloader НЕ обновляется в поле (или dual bootloader) | Высокий |
| 12 | Максимальный таймаут на обновление (аварийный откат) | Высокий |
| 13 | Логирование всех этапов обновления | Средний |
| 14 | Оповещение при массовом откате (возможная проблема в релизе) | Средний |
Итого
OTA — не фича, а требование для любого IoT-устройства, подключённого к сети. Без OTA вы не закроете уязвимости, не исправите баги и не добавите функции после выпуска.
Три ключевых решения при проектировании OTA:
- A/B или in-place: если Flash позволяет — всегда A/B. Кирпич в поле обходится дороже, чем лишние 128 КБ Flash
- MCUboot или vendor-specific: MCUboot — портируемый, стандартный, с поддержкой Zephyr/NuttX. ESP Secure Boot — только для ESP32, но глубже интегрирован
- Self-hosted или облако: до 1 000 устройств — self-hosted (hawkBit, nginx + S3). Больше — Mender, Golioth или AWS IoT
Самая частая ошибка: «добавим OTA потом». OTA влияет на разметку Flash, bootloader, криптографию, сетевой стек — это архитектурное решение, которое закладывается на старте проекта, а не прикручивается сбоку.
Проектируете IoT-устройство и не уверены в архитектуре OTA? Мы делаем экспресс-аудит IoT-идеи за 25 000 ₽: архитектура, OTA-стратегия, безопасность, компоненты — за 5 рабочих дней. Или посмотрите наши услуги по IoT и embedded-разработке.