7 мин на чтение

Здравствуйте, меня зовут Андрей и у меня есть какое-то количество железа без шифрования диска:

protectable-hardware

Кхм, точнее, пока без шифрования диска! Не подумайте дурного, Full Disk Encryption (FDE) все еще не самоцель, а лишь средство некого успокоения :) Осознавая, что ни один пост полного потока сознания не выдержит, я решил перестать пытаться уместить все в один и начать мини-серию зарисовок о шифровании дисков со вкусом домашних пирожков (ммм!). Этот пост, как вы могли догадаться, в больше степени вступительный :)

О задачке

Люблю, когда задачки звучат банально - эта не исключение:

  • при утере багажа хочу выбирать новую железку, а не переживать за “нюдсы”
  • при этом сами нюдсы тонким слоем размазаны по:
    • гибридному (arm64/amd64) k8s кластеру
    • парку minipc с Proxmox
    • всякой сопутствующей мелочевки, типа OpenWrt и малиновых шлюзов
  • оберегать нюдсики будем шифрованием, а ему нужен ключ
  • инфраструктура при этом должна максимально самостоятельно восстанавливаться

Последнее и будет краеугольным камнем всей этой истории, как и основным отличием от привычного шифрования ноута или печи:

  • если с ноутом нет проблемы ввести пароль, то тут банально не набегаешься
  • большая часть домашних инков происходит в моё отсутствие

В прочем, это уже пошли какие-то подсказки к решению задачки, а о них ниже.

О концепции

Жаль, но магии в нашем мире не существует, поэтому мы можем…

Хранить ключ локально

Вариант с пустой тратой тактов CPU при хранении ключа в открытом виде в initramfs мы рассматривать не будем, сорий. А вот вставить TPM/PIV/FIDO2/YetAnotherHwStorage мы можем, но тогда придется уверовать как минимум в:

  • secure boot (особенно хочу напомнить про arm64 хосты)
  • не доступность консоли, см. Mashing Enter to bypass full disk encryption
  • не возможность поснифать SPI/I2C шину в случае с TPM, см. TPM sniffing attacks
  • или USB в случае с PIV токеном, см. APDU-level attacks
  • еще ворох всяких внезапностей

На мой вкус, Secure и Measured boot - однозначное благо, просто строить FDE только на этом я бы не рискнул. Конечно, всегда удобно, когда железка сама бутается, главное чтобы не в чужих руках ;)

В остальном же:

  • эта довольно запарная (и дорогая, кстати) история
  • сильно ограничивает в железе (куда-то просто нечего вставить)
  • зато можем использовать для любых секретиков или в роли device identity
  • но о адекватных логах можно забыть by design

В общем, этот способ нам совсем не подходит и мы идем дальше.

Или приносить ключ

Под “приносить” я понимаю удаленный принос, например, с помощью dracut-crypt-ssh. Вот, кстати, еще неплохая зарисовка на тему: Remotely unlock a LUKS-encrypted Linux server using Dropbear. Любители побегать с виртуальной клавиатурой или FIDO2 токеном извините, но мы с вами не подружимся.

Ключевые особенности этого способа:

  • пограничник не получит нюдсы, потому что никто не принесет ключ - это лайк
  • скорее всего понадобится stateful сервис приноса ключей - а это дизлайк
  • требует приседаний на конечном устройстве
  • скорее всего подходит только для FDE
  • зато имеет богатейшее логирование (!)
  • и возможность полностью удаленно открыть диск

Вот принос ключа по SSH нам уже подходит. Со всех сторон хороший варик, если смочь придумать какой-то мониторинг (или вебхук, хех?) + навести порядки с инвентаризацией. Единственное, не покидает чувство перегруженности - stateful сервис, усложнение процесса загрузки, вот это все. Да и порядок с инвентаризацией не мой конек, чо уж. Оставим этот способ на потом :)

А можно и сходить за ключем

Под “сходить” я понимаю сетевой сервис, в который можно сходить как для формирования ключа (e.g. McCallum-Relyea key exchange), так и для получения оного (e.g. Escrow, KMS, etc). Сейчас это не так важно, а важно то, что таким образом мы как бы привязываем ключ к доступности некого независимого сервиса по сети - имеем рубильник, так сказать. А рубильник это хорошо, рубильник это надежно.

Ключевые особенности:

  • пограничник не получит нюдсы, потому что некуда будет сходить за ключем
  • как и в предыдущем варике с приносом, - храним мастер-ключ в одном месте
  • как и с локальным хранением, - можем использоваться не только для FDE
  • при этом сервис может быть полностью stateless
  • и с т.з. конечного устройства все максимально просто: нужен ключ -> сходи за ним
  • имеем аудитные логи. Не богатейшие, но приличные

Звучит так, что мы за приемлемую цену собрали плюсы предыдущих способов. Это ли не заявка на победу? Чудный компромисс между простотой и эффективностью реализации - то, чего сильно не хватает домашней инфраструктуре :) Да, чуть страдает observability и debugability, зато мы не ходим на потенциально скомпрометированный хост (это он ходит к нам!).

Конкретрики бы

Раз уж с самой концепцией мы определились - настало время ее конкретизировать, не все же руками в воздухе водить. И так, мы хотим, чтобы конечное устройство само ходило по сети в наш сервис, который…

Хранил бы ключи

Как эталонный пример такого решения можно рассматривать Vault by HashiCorp и разнообразные попытки дружить его с FDE (e.g. luks-vault). Но нам такое, в принципе, не нравится (stateful < stateless ), поэтому не останавливаемся и движемся дальше.

Или совместно вырабатывал ключ

Например, как то делается с типичным envelope encryption в Key Management Service (KMS):

  • клиент генерирует ключ шифрования данных (DEK), он же keyfile в LUKS
  • ходит в KMS для ширования/расшифрования DEК на ключе шифрования ключей (KEK)
  • а сам KEK или формируется из пароля, или хранится в TPM/на смарт-карте

Вот и stateless сервис получился. Бонусом имеем не экспортируемый KEK (при использовании смарт-карты) и простоту его ротации (достаточно двигать версию KEK в зашифрованном DEK). Осталось только решить, что делать с безопасностью транспорта - должен ли это быть TLS или что-то еще. А так приличное решение, не зря все уважающие себя облачные провайдеры имеют KMS с поддержкой envelope encryption (сложно отказаться от поддержки, так-то ^^).

Или же воспользоваться алгоритмом McCallum-Relyea и его эталонной реализацией в демоне Tang (отличный пост на тему: Network-Bound Disk Encryption). Как и в случае с KMS на выходе получаем stateless сервис, с помощью которого клиент может выработать DEK. Ну а KEK, точно так же складываем в TPM/Smart Card, и дело в шляпе. Кстати, в случае использования Tang коллеги из Red Hat заранее подумали и о безопасности транспорта - это хорошо, это нам нравится.

Или же можно сделать некий аналог HMAC Secret Extension из FIDO2 или HKDF, только в виде сервиса. Ну а чо, в нашем случае будет ваще ничем не хуже KMS, зато сервис можно крутить хоть на картошке.

Но это все слова, а когда пришла пора выбирать - я опешил. Все варианты по-своему и хороши, и плохи. Смотрите сами:

  • полноценный KMS пилить долго, а польза от огрызка не очевидна
  • связка Tang + Clevis всем хороша, даже финальный ключ по сети не полетит. Но:
    • требует или кастомного клиента (привет поделки на ESP), или сборки jose
    • не выглядит удобно расширяемым (вопрос необходимости дискуссионный)
  • HKDF и производные как-будто не достаточно прогрессивны %)

Потому устав от мук выбора, я решил сделать все разом :) Если точнее, то начать с HMAC-based деривации ключа, а уж если не устроит в эксплуатации или по соображениям безопасносте, то пойти делать поддержку McCallum-Relyea key exchange. В результате этой могучей мысли и родился проект BoundBox.

Project “BoundBox”

Ну вот, теперь-то все встало на свои места! Осталось прикинуть план:

  1. сначала мутим какой-то безопасный транспорт
  2. поверх него строим расширяемый протокол
  3. в рамках которого реализуем сервисные ручки + HKDF*
  4. а уже потом McCallum-Relyea key exchange и возможно даже простенький KMS

Идем по порядку и разбираемся с транспортом. Несмотря на то, что около дефолтный ответ будет TLS (или mTLS, если с аутентификацией клиента), в эту концепцию он довольно скверно ложится:

  • требует PKI, что и усложнит наливку и эксплуатацию
  • без хаков проверка сертификата требует корректного времени, а время это ОЧЕНЬ сложно. Trust Me, I’m an Engineer!

Так, окей, если не TLS, то что еще:

  • умеет во взаимную аутентификацию клиента и сервера
  • обеспечивает шифрование канала
  • исповедует Trust On First Use (TOFU)
  • не требует корректного времени на клиенте
  • реализован под любой утюг

Ну конечно же SSH. Может я немного предвзят, но выглядит идеальным кандидатом, который закрывает примерно все вопросы.

Остается решить, что же будем делать с протоколом приложения. Но SSH и тут норм, зацените:

  • SSH предоставляет двунаправленный канал связи клиента с сервером
  • клиент может запросить запуск команды (см. RFC 4254)
  • а сервер вернуть как выхлоп, так и статус выполнения

Что это мне напоминает? А HTTP/1.0 без заголовков мне это напоминает (ну или HTTP/0.9 с телом запроса) -> вот она, родная гавань :)

А мы начинаем мутить JSON API поверх SSH (ну не красота ли?):

$ echo '{"request": "body"}' | ssh boundbox.local -- /some/path
{"response": "body"}

И собирать все в кучу: bound-box-draft

Словами это звучит так:

  • сначала клиент производит всякие приготовления:
    • локально генерирует приватный ключ
    • локально генерирует одну и более соль
    • прикапывает отпечаток публичного ключа сервера (или получает его с наливкой)
  • затем каждый раз, когда ему нужен DEK:
    • берет ранее сгенерированный приватный ключ и соответствующую соль
    • идет с этим в BoundBox по SSH (попутно аутентифицируя его)
  • в это время BoundBox:
    • аутентифицирует клиента
    • выполняет HMAC-SHA-256(BoundBoxKEK, HMAC-SHA-256(clientSalt, clientKey))
    • возвращает результат

Таким образом мы получаем привязку соли как к конкретному клиентскому ключу (сервер убеждается в его владении клиентом), так и к мастер-ключу самого BoundBox. Довольно пряминько на мой взгляд.

Пока считаю вступление свершившимся. Думаю, в следующей части будет сильно больше технических подробностей а не как тут.

Closure

Дальше я планирую написать еще, как минимум, два поста:

  • один про аппаратно-софтовую реализацию BoundBox (upd: а вот и он)
  • другой о пусконаладочных работах, готовке initramfs и дальнейших планах

Но это потом, а пока всем кота (づ˶•༝•˶)づ♡

Разделы:

Дата изменения: