Что может злоумышленник, знающий исходники прошивки, но не имеющий доступа к секретам устройства.
Прошивка публична, поэтому форматы пакетов, длины и алгоритмы (X25519, HMAC-SHA256, HKDF) известны. BLE — открытый эфир, поэтому объявления и записи GATT можно слушать. Ни в прошивке, ни в эфире нет ни одного секрета.
Где живут ключи:
owner_secret (32 байта) — только в NVS контроллера и на телефоне владельца. Выводится через ECDH X25519 при сопряжении и никогда не ходит по эфиру в открытом виде.guest_key — на телефоне гостя. Приходит с сервера внутри бандла по защищённому каналу WSS, а не по BLE.| Attack | Defence |
|---|---|
| Подделать FIRE | HMAC-SHA256. Без owner_secret или guest_key это перебор 2128 — перебор неосуществим. |
| Подделать запись TIME | Тот же HMAC по bleId + epochMs. |
| Повторить подслушанное объявление | Счётчик монотонный: при повторе или уменьшении HMAC не сходится. |
| Повторить подслушанный fire | Кольцо nonce гасит уже использованные значения. |
| Отвести часы контроллера назад | Храповик: любое TIME < храповика отвергается. Храповик лежит в NVS и переживает отключение питания. |
| Использовать просроченный бандл | После работы контроллера храповик не меньше последнего реального времени. У просроченного бандла validUntil ниже храповика, поэтому fire отсекается по сроку. |
| Перевести часы на год вперёд и отрезать всех гостей | Ограничение +24 ч на одну гостевую запись TIME. У владельца ограничения нет — владельцу доверяем. |
| Спариться вторым владельцем поверх первого | После сопряжения контроллер держит owner_secret в NVS и не входит в режим сопряжения без сброса к заводским настройкам. |
Requires: действующий непросроченный гостевой бандл.
Action: каждый раз в зоне действия писать TIME со скачком на 24 часа вперёд.
Limit: +24 ч за сеанс, причём нужен свежий fire, если последняя синхронизация была меньше часа назад. После десяти визитов храповик уйдёт на десять дней вперёд, и токены остальных законных гостей будут выглядеть просроченными.
Обратная сторона: токен самого злоумышленника сверяется с тем же фальшивым временем и из-за этого протухает раньше срока.
Что делать: владелец подходит и пишет настоящее время. Храповик не сдвинется — назад он не ходит, — но новые гостевые токены выдаются с актуальным validUntil и дальше работают у всех. Для восстановления достаточно обновить срок действия на сервере.
Severity: временный отказ в обслуживании до прихода владельца. Не критично.
Requires: физически находиться в зоне действия контроллера в 90-секундном окне после того, как владелец включил сопряжение.
Action: подменить в рукопожатии сопряжения собственный открытый ключ X25519.
Result: контроллер спарится со злоумышленником, а не с владельцем. Владелец будет считать сопряжение удавшимся, но с телефона ничего не откроет.
Текущая защита: только временное окно и физическое присутствие владельца — он видит, что сопряжение не подтвердилось, и повторяет его.
Hardening: добавить простой код подтверждения — четыре цифры на экране контроллера или в Serial, — который владелец подтверждает в приложении. Сегодня такого кода нет.
Severity: окно узкое и требует одновременно близости и попадания в те самые 90 секунд. На практике трудноисполнимо.
Requires: физический доступ к контроллеру и долгое нажатие кнопки.
Action: NVS стирается, owner_secret удаляется, контроллер возвращается в режим сопряжения.
Result: злоумышленник спарит контроллер с собой, а владелец потеряет доступ.
Defence: ставить контроллер в защищённом месте — в коробе, под крышкой.
Severity: то же самое, что сорвать домофон с двери, — это не криптографическая задача.
Requires: физический доступ и дамп флеша при выключенных secure boot и шифровании флеша — сейчас они выключены.
Action: прочитать NVS, где лежит owner_secret.
Result: полная компрометация устройства и всех гостевых токенов.
Defence: включить secure boot и шифрование флеша ESP32 (efuse, необратимо). Сегодня не включено.
Severity: приемлемо для бытового применения. На ответственных объектах secure boot нужен.
Requires: находиться в зоне действия BLE.
Action: подключиться и не отключаться. NimBLE держит лишь ограниченное число одновременных соединений.
Result: законные клиенты не могут подключиться, пока злоумышленник держит слот.
Текущая защита: только тайм-ауты NimBLE.
Hardening: добавить тайм-аут простоя: если клиент за 5 секунд не выполнил корректный FIRE, рвать соединение.
Severity: временный отказ в обслуживании, пока злоумышленник рядом.
Action: забрасывать контроллер мусорными записями.
Result: контроллер их отбрасывает и пишет в Serial. В его состоянии ничего не меняется, а стоит злоумышленнику уйти — всё работает как обычно.
Три места выглядят защитой, но ею не являются. Лучше назвать их прямо, чем дать кому-то на них опереться.
Байт состояния в объявлении несёт 16-битный тег. Этого хватает против случайного искажения в эфире и не более: упорный подделыватель переберёт 16 бит за секунды. Байт управляет только индикатором открыто/закрыто на карточке и никаких прав не даёт, поэтому тег и остаётся коротким — каждый лишний байт стоит батареи на каждом пробуждении.
Ключи только для приложения опираются на секрет, вшитый в APK. Кто распакует файл, тот получит секрет и подделает аттестацию. Это поднимает планку перед случайным веб-клиентом, не более. Настоящий контроль для такого ключа — привязка к отпечатку одного устройства: эта проверка живёт на сервере и не зависит от того, хранит ли приложение секрет.
Срок действия гостевого ключа для BLE обеспечивает сам контроллер: в токене есть срок, а у контроллера есть часы. У остальных типов объектов срока нет вовсе — ключ живёт, пока владелец его не отзовёт. Приложение обратного и не утверждает, но если ограничение по времени когда-нибудь появится в интерфейсе и для них, обеспечивать его придётся на сервере, а не рисовать на карточке.
Стандартное усиление для промышленного применения:
Ничего из этого не критично для сегодняшних сценариев, но ответственные объекты усиления заслуживают.