Homemade FDE: BoundBoxESP
Если прошлый пост (см. Homemade FDE: Part Zero) в большей степени был вступительным, то в этом я хочу рассказать про BoundBoxESP, как о всратом сердце всей истории с домашним шифрованием :)
База
Но сначала напомню концепцию:
- некий утюг идет по SSH в наш сервис
- отправляет свой секрет
- в ответ получает новый секрет, как результат работы KDF
- использует его для своих утюжных делишек (FDE, unwrapping, etc)
Таким образом, в покое ни один из участников не обладает финальным секретом и нуждается в друг дружке. Визуализация:
Дела хардварные
Невероятно, но с учетом нагрузки на будущий сервис его можно крутить на любой около-современной картошке :) Вариантов по хранению/получению мастер-ключа у нас тоже довольно много. А значит наиболее честный ответ в выборе железа будет “я так захотел”.
Например, это могло бы быть что-то с TPM. Будь-то amd64 или arm64:
И, на мой вкус, это было бы чертовски скучно. А мы тут вообще-то приятное с полезным совмещаем, не до скучных решений знаете ли. Из чуть более приземленных соображений - очень плохая утилизация. Тот же N100 в ZX01 будет во-о-бще ничем не занят примерно все время, захочется или селить что-то рядом (wat?), или как-то совсем глупо получается.
Чуть разбавить скуку и выровнять КПД, можно было бы с помощью одноплатников форм-фактора Raspberry Pi Zero (или Orange Pi Zero) со смарт-картой:
Но, будем честны, веселее становится в основном за счет проблем со скоростью загрузки линуксов. Это решабельно, но я сказал, что хочу больше веселья! :)
И тут в тред врываются разнообразные MCU. Выбор там, к счастью, довольно большой (e.g. nRF52840, ESP32, RP2040, etc), но думаю ESP32 с пин-падом или сенсорным экраном будут отличным стартом. Вон каких красавчиков наалишил:
Так я и поступил, выбрав для текущей версии BoundBoxESP :
- LILYGO T-Display S3 AMOLED как основу всего + средство ввода
- 3.7V 1000mAh 503450 Battery как ответ электрику
- W5500 Ethernet Module, потому что лучший WiFi - проводной WiFi (но это не точно)
Полный комплект обошелся мне в $24.22 + $5.81 + $2.56 = $32.59 или половинка YubiKey 5C Nano.
Если интересно, то доску LILYGO T-Display S3 AMOLED я выбрал по нескольким причинам:
- Чертовски миленькая :)
- T-Display-S3-Pro на момент старта проекта не был широко доступен
- Выведено достаточное количество хороших пинов для подключения W5500 по SPI
Ну и в целом это добротная, современная доска на базе ESP32-S3R8 с двухъядерным Xtensa® LX7, 16MB Flash, 8MB PSRAM, встроенной зарядкой для аккумулятора и 1.91” сенсорным (CST816) IPS Amoled экраном (RM67162) разрешением 240x536 точек. Из минусов - только отсутствие вывода RST пина и какого-либо крепежа (ушки бы там для M2.5, например), но чем-то пришлось пожертвовать. Помимо прочего, ESP32-S3 обещал мне какой-никакой Secure Boot, Flash и NVS encryption, что само по себе довольно не плохо, хоть, конечно, и не так ценно при вводе пароля. Надеюсь, не сплоховал :)
Дела паяльные
К счастью, подключать нам только W5500:
| T-Display | W5500 |
|-------------|----------------|
| 3v3 | 3v3 |
| GND | GND |
| GPIO10 | SPI CS/SS |
| GPIO11 | SPI MOSI |
| GPIO12 | SPI SCK |
| GPIO13 | SPI MISO |
| GPIO14 | INT |
| GPIO15 | RST |
Вариант для визуалов:
Запаиваем и проверяем на весу, что все выглядит рабочим:
“Корпусируем”:
Та-да!
Фуф, можем откладывать в сторону паяльник и DIY набор для детей от 5 до 10 лет, они нам больше не понадобятся. Далее ручной труд, только в привычном для нас смысле ;)
Дела программные
Написан BoundBoxESP на C++ поверх ESP-IDF с использованием LibSSH-ESP32 и LVGL. Почему ESP-IDF, а не Arduino спросите вы? А потому что ESP-IDF:
- предлагает писать на C++23 (см. C++ Support) или C, а не каком-то подмножестве C++ именуемым Arduino “language”
- использует запчасти из FreeRTOS (см. FreeRTOS (ESP-IDF)) с шедулером задач, событийной моделью и прочими благами цивилизации. Больше никаких
if (curMillis - prevMillis >= interval)
на ровном месте (e.g. Multitasking with Arduino) - что логично, своевременно получает поддержку новых Espressif SoC. Так, например, поддержка ESP32 С6/H2 все еще не докатилась через три слоя абстракции (в норме это ESP-IDF -> Arduino-ESP32 -> PlatformIO).
- ощущается целостным фреймворком, со своей идеологией и базовыми принципами. Arduino же просто сборище бибок, которые связывает страсть к
Begin
иLoop
- имеет шикарную документацию, большая часть разделов которой сопровождается полноценными примерами
Конечно, у Arduino тоже есть свои плюсы, и было бы странно их отрицать:
- кроссплатформенность и статус дефолтного фреймворка для многих MCU
- отсюда несравнимо большее комьюнити
- отсюда 1st class support разными производителями
Стоят ли эти преимущества сопутствующих ограничений и проблем, пусть каждый решает сам. Пробовал Arduino в BoundBoxESP v0.0.1 и мне было не вкусно :’(
Но что-то я опять отвлекся, в общем виде текущая логика приложеньки выглядит так: Так мы:
- Инициализируем экран, сеть и прочую периферию
- Проверяем наличие прикопанных в NVS секретов (сейчас это SSH ключ и зигота мастер-ключа). В случае отсутствия - генерируем новые
- Дожидаемся подключения по сети (возможно зря, не решил пока)
- Запрашиваем PIN и генерируем мастер-ключ:
HMAC-SHA-256(NVSKey, EnteredPIN)
- Затем тестовый секрет:
HMAC-SHA-256(MasterKey, VerificationSalt)
- Спрашиваем ОК ли он
- Если нет, то возвращаемся к п4 и снова запрашиваем PIN
- Если все норм, то:
- стартуем SSHD
- обрабатываем входящие команды
- показываем подписочные нотификашки при необходимости
- ну вы поняли, работаем - запросы крутятся, секреты мутятся
Благодаря такой логике мы смогли получить три важных свойства:
- Прикованность мастер-ключа к конкретному устройству, т.к. один и тот же PIN на разных устройствах (точнее с разными NVSKey) породит разные мастер-ключи
- Резервируемость, благодаря хранению секретов в зашифрованном NVS и write-only работе с секретами. Так в случае беды мы можем:
- налить новое устройство
- загрузить в него старые секреты (если генерили ранее на хосте, офк)
- спасти мир
- Антибрутальность, т.к. с т.з. стороннего наблюдателя любая пара PIN + NVSKey валидны. Да, тут чуть пострадал UX, зато не нужно хранить счетчик попыток ввода на флешке, дропать секреты при превышении и вот это все.
Вот, кстати, примеры командочек для админа:
$ ssh -l buglloc bbw0.buglloc.cc -- /help | jq .commands
[
{
"command": "/hmac/secret",
"description": "Generate HMAC secret for req[\"salt\"] into rsp[\"secret\"]"
},
{
"command": "/help",
"description": "Returns commands info"
},
{
"command": "/status",
"description": "Returns BoundBoxESP status"
},
{
"command": "/restart",
"description": "Restart BoundBoxESP in req[\"delay_ms\"]"
},
{
"command": "/secrets/store",
"description": "Store runtime secrets from req[\"secrets\"]"
},
{
"command": "/secrets/reset",
"description": "Reset secrets to it's default values"
}
]
И для обычного работяги:
$ ssh bbw0.buglloc.cc \
-l 'SHA256:C0Q14mSJLVITEyGsP6QLE1Z/GfTwEq1mLzVemnVch0E' \
-- /help | jq .commands
[
{
"command": "/hmac/secret",
"description": "Generate HMAC secret for req[\"salt\"] into rsp[\"secret\"]"
},
{
"command": "/help",
"description": "Returns commands info"
}
]
Ну и то, ради чего мы тут собрались - получение network-bound секрета:
$ echo '{"salt":"cHhrQThGeFdUWDZCMWg2MVFLQTBONEpXCg=="}' | ssh bbw0.buglloc.cc \
-l 'SHA256:C0Q14mSJLVITEyGsP6QLE1Z/GfTwEq1mLzVemnVch0E' \
-- /hmac/secret | jq .
{
"secret": "O+6rFW2UDigIXbzpKUpiigokXu/JeAZXZY8p5Xzm8ZA=",
"ok": true,
"id": "817682-8c926b"
}
Не стану затягивать и ныть о том, как и почему я отказался от использования WolfSSH или о портировании LilyGo-AMOLED под ESP-IDF. Лучше вместо нытья демо покажу ^_^, вот!
В действии
Чтобы ничего не пропустить (и не забыть в будущем, ха-ха) - мы начнем с самого начала. Да-да, с клонирования репо и настройки под себя (i.e. логин/ключ админа):
На этом этапе мы должны получить полностью рабочую коробку, но я предлагаю еще чуть докинуть безопасности, усложнив получение данных из NVS. Для этого включаем релизное шифрование флешки (doc: Flash Encryption: Release Mode) на “хостовом” ключе (doc: Flash Encryption: Using Host Generated Key):
На всякий случай - использование хостового ключа обязательно, т.к. я умышленно отказался от OTA, дабы быть ближе к принципу write-only секретов. Ну а пока мы получили шифрование дефолтных партиций и NVS. Осталось дело за Secure Boot, т.к. иначе какой смысл в таком шифровании? PARTITION_TABLE_OFFSET
я потюнил заранее, поэтому места под распухший бутлоадер нам должно без проблем хватить.
Погнали, нам нужно настроить бутлоадер, зашифровать его + partition table + приложеньку и прошить:
Вуаля! Теперь мы проверяем и подпись бутлоадера, и подпись приложеньки, что не позволит пограничнику загрузить собственные и считать все с флешки. Прямой же доступ мы ограничили ранее, за счет шифрования. Вот и чудненько, теперь главное ключи не потерять, хех :))
Не устали? Самое время полюбоваться итоговым результатом под присмотром Teo:
Дальше-то что?
А дальше не торопливый выход в прод:
- заиспользовать BoundBoxESP в не критичном сервисе
- убрать валенки с пульта
- собрать вторую копию BoundBoxESP (а может и v2)
- ближе к майским перевести все нужные железки на FDE
Пожалуй, на этом пока все, всем кота (づ˶•༝•˶)づ♡