Industrial IoT — это мониторинг, диспетчеризация и автоматизация инженерных систем промышленных объектов, зданий, бизнес-объектов. Датчики разных параметров, счетчики и контроллеры собирают данные с этих объектов, например, температуру и влажность воздуха в серверной, показания счетчиков воды в многоквартирных домах, уровень углекислого газа в помещениях. Контроллеры обрабатывают эту информацию и отправляют все в «облако».
Компания Wiren Board производит контроллеры с Linux для industrial IoT. Устройства собирают данные с нефтедобывающих скважин и банковских отделений, следят за микроклиматом в серверных и супермаркетах. Контроллеры интегрируются с системами верхнего уровня партнеров компании. Системы аутентифицируют устройства — понимают, что разговаривают со своим датчиком, а не с чужим, а потом авторизуют. На этом этапе возникает проблема — контроллеров тысячи, клиентов сотни, а единой системы интеграции нет. Простые традиционные способы, например, пары логин/пароль, уязвимы к атакам и неудобны в развёртывании.
Поэтому в компании разработали аутентификацию в системах верхнего уровня по аппаратным ключам — на основе стандартной асимметричной криптографии с использованием аппаратного защищённого элемента для хранения ключей. Теперь единая система интеграции не нужна —аутентификация и авторизация защищены, и работают «из коробки». Как это удалось сделать расскажет Евгений Богер: как выбирали «крипточип», как прикручивали его к железу и к Linux, как заставили с ним дружить распространённые библиотеки и ПО. Особый упор на развёртывание: внедрение инициализации устройств на производстве, внедрение поддержки разного софта верхнего уровня, в том числе в чужого и закрытого.
О спикере: Евгений Богер (evgeny_boger) — технический директор и сооснователь Wiren Board. Занимается встраиваемыми системами и, в особенности, встраиваемыми Linux.
Проблемы
Начну с того, что мы делаем и откуда у нас эта проблема возникла. Мы в Wiren Board разрабатываем и производим оборудование в России. Раньше это называли M2M, а сейчас — industrial IoT. Это автоматизация инженерных систем зданий, мониторинг и диспетчеризация. Кратко вся работа выглядит так: датчики разных параметров, исполнительные устройства, счетчики и контроллеры (edge-computing или IoT-gateway) собирают разные данные с объектов, обрабатывают их, исполняют локальную логику, а потом собираются в одной большой системе диспетчеризации, мониторинга или управления.
У нас нет цельной экосистемы, в отличии от некоторых конкурентов. Мы производим оборудование, которое интегрируется с несколькими системами верхнего уровня наших партнеров. Компаний-партнеров много и они разделяют между собой ответственность. Без хороших технических средств интеграция не будет работать — просто договориться не получается.
Для решения этих проблем есть два простых решения. Первый — выдать логин/пароль клиенту, как делают все, а второй — генерировать и зашивать «секрет» на производстве. Оба варианта нам не подошли — расскажу почему.
Простые решения
Первое решение — выдача логина и пароля клиенту. Так делают все и мы до недавнего времени.
Для аутентификации устройства, которое отправляет данные в какую-то систему, можно сделать секретный ключ — условно логин/пароль («секрет»). Он будет общий на контроллерах и на системе верхнего уровня, которая собирает данные с нескольких контроллеров.
Пару логин/пароль (общий «секрет») нужно как-то выдать клиенту — компании или человеку. Кто-то должен сгенерировать секретную пару, отправить по e-mail, аутентифицировать клиента по номеру счета. Это стандартная процедура — низкие технологии.
Проблема. Она в том, что у нас много таких систем. Клиент наш, а данные он может отправлять в систему нашего партнера. Это сложное взаимодействие между всеми причастными сторонами.
Кроме проблемы множества систем, есть еще другие.
- Плохая выдача и доставка до клиента.
- Логины и пароли хранят на сервере. Если будем хранить еще и хэши, это нас немного обезопасит от утечек. Но даже при этом возникает неприятное чувство, когда на сервере хранятся секретные ключи для всех контроллеров клиентов. Некоторые из них могут заниматься критическими задачами: наружным освещением, мониторингом нефтяных вышек.
- Синхронизация между сервисами.
- Восстановление при потере. Непонятно, что делать при потерях, когда клиент стер память контроллера — в какую память писать? Придется повторять все заново.
- Защита от копирования реквизитов. Есть платные системы мониторинга, которые предоставляют клиенту сервис и берут с него оплату по подписке. Не хочется, чтобы конечный клиент смог как-то обходить систему через нас — заплатить один раз, а использовать два.
Второе решение — генерировать и зашивать «секрет» на производстве. Это улучшение предыдущего решения.
Схема такая: мы, как производитель контроллеров, заранее генерируем логины и пароли для всех, зашиваем в свою систему и заносим в оборудование. Логины и пароли из оборудования нельзя ни прочитать, ни поменять. Это лучше предыдущего варианта, потому что не требует взаимодействия между людьми.
Проблемы. Остались все проблемы, кроме первой, но главная из них — синхронизация между сервисами и интранет. Сервисов много и непонятно, как их синхронизовать — из-за этого мы не смогли внедрить второе решение. У нас есть клиенты, которые используют оборудование в своих закрытых сетях. Мы выпустили новый контроллер, продали клиенту, а его система закрытая. Она настроена, один раз работает, а донести «секреты» дальше сложно. Доносить партиями? В организациях все сложно, хотя технически просто.
Оба решения нам не подошли. Поэтому мы решили пойти по другому пути. Но перед этим решили обозначить общие задачи и цели.
Задачи и цели
Сначала общие задачи.
Аутентификация. Это способ понять, кто разговаривает с системой верхнего уровня, кто именно подключается к системе диспетчеризации.
Аутентификация — это не предоставление или разграничение прав доступа, а способ понимания, кто с нами говорит.
Задача отправки данных. Наши контроллеры — это компьютеры с Linux, разработанные под специальную задачу. Нам нужно, чтобы они отправляли данные в системы верхнего уровня, подключались по VPN. При этом мы хотим, чтобы отправка работала «из коробки» — без настроек и взаимодействия наших клиентов и конечных пользователей системы с нами и с клиентами.
Другие задачи. Это надежность соединения, шифрование канала данных, но отдельный вопрос — авторизация. Задача авторизации связана с внешними сервисами и делится на три части.
- Бесплатный сервис производителя. Предоставлять доступы по серийному номеру устройства.
- Белые списки серийных номеров для сервиса наших партнеров — привязывать покупки и доступы к аккаунту клиента.
- Лицензии. Разрешать или запрещать доступ на основе опций, которые указаны в сертификате.
Цели — это то, что мы хотим достичь, когда решим задачи.
Выдача и доставка до клиента. Без участия людей — информация зашивается роботами на производстве.
Восстановление при потере. Мы хотим, чтобы вообще не было потерь секретных реквизитов.
Доставка от производства до сервисов. Хотим обойтись без неё, чтобы не требовалось ничего доставлять в сервисы. При запуске нового оборудования мы не хотим обновлять базы всех сервисов, которые должны аутентифицировать эти устройства.
Хранение на сервере. Желательно вообще ничего там не хранить.
Синхронизация между сервисами и интранет. Также желательно ничего не синхронизовать — ведь ничего хранить не будем.
Защита от копирования реквизитов. Хотим, чтобы что-то секретное, за что берутся деньги, нельзя было скопировать и получить бесплатно.
Цифровая подпись спешит на помощь
Электронная цифровая подпись (ЭЦП) — это технология, вокруг которой у нас все работает.
Это как обычная подпись, только цифровая. ЭЦП легко проверить, но сложно подделать. Знакомые всем прописные истины криптографии, которым десятки лет.
Электронная подпись — это что-то, что можно посчитать по сообщению, если знать секретный приватный ключ (private key). Если знать публичный ключ (public key), легко проверить, что электронная подпись для сообщения правильная. По названию понятно — публичный принято сообщать всем, а секретный только у того, кто подписывает.
Все подписи и ключи — это просто числа.
В нашем случае это 32 байта данных, которая работает на математической «магии». Математика гарантирует, что подпись просто проверить, но сложно подделать.
Мы используем подпись ECDSA-256+SHA-256:
e = HASH(m)
— криптографическая хеш-функция необратимо преобразовывает сообщение m в число e;private key (dA)
— случайное число;public key (QA)
— генерируется из private key, но не наоборот;signature (r,s) = sign(private key, e)
— подпись;verify(public key, signature, e)
— проверка подписи.
Аутентификация по ЭЦП. Первая попытка
Что можно сделать для нашей задачи, используя этот хитрый механизм, который в одну сторону работает просто, а в другую сложно?
Выдача и доставка до клиента. Генерируем случайный приватный ключ для каждого устройства на производстве. Никому не говорим, потому что даже сами его не знаем, и записываем в устройство.
Доставка от производства до сервисов. Дальше используем только публичный ключ этого устройства для аутентификации на сервисах. На сервисах храним только список публичных ключей вместо паролей.
Стандартный алгоритм проверки работоспособности:
- сервис отправляет контроллеру случайное сообщение
m
; - контроллер:
sign(private key, m)
; - контроллер отправляет сервису подпись;
- сервис:
verify(public key, signature, m)
.
Единственное, что мы решили таким способом — это то, что больше не храним на своих сервисах общие «секреты» в открытом или закэшированном виде. Это не то, что нам хочется.
Аутентификация по ЭЦП. Вторая попытка
Мы не хотим что-то хранить на сервисах. Чтобы этого добиться, мы можем заставить наши устройства отправлять свои публичные ключи сервису.
На прошлом этапе мы решили две задачи. Первая — проверили, что выдали сервису ключ. У нас есть публичный ключ, значит приватный тоже делали мы. Вторая — убедились, что устройство владеет приватным ключом, который лежит где-то на флешке. Если устройство могло что-то подписать, значит у него есть приватный ключ.
Теперь устройство отправит сервису еще и публичный ключ. Как проверить, что никто его не перехватил, не подделал, и что все работает?
Проверка публичного ключа. Создаем себе еще один публичный ключ. Он будет нашим ключом, как производителя. Это корневой ключ «root private key + public key». Этим корневым секретным ключом на производстве подпишем публичный ключ устройства (device public key) и будем хранить эту подпись на устройстве. Устройство должно отправить свой публичный ключ и подпись своего публичного ключа сервису. Теперь сервис может проверить публичный ключ устройства. Если он подписан корневым ключом (root private key), значит мы выдали этот ключ.
Создать и хранить подпись на устройстве может только производитель — мы, а проверить — все.
Публичный ключ мы публикуем на сайте в разделе «Контакты». Кто угодно может его взять, и проверить публичный ключ устройства, который отправило устройство на сервис. Дальше можно проверить, что само устройство обладает собственным приватным ключом.
Общий алгоритм выглядит так.
(once) random root private key
;factory: random device private key
;factory: sign(root private key, device public key) = signature_1
;device->service: отправляет device public key + signature_1
;service: verify(root public key, signature_1, device public key)?
Результат второй попытки
Мы решили проблему с доставкой до клиента — информация зашивается на производстве, и восстанавливать ничего не надо.
Важно, что мы решили проблему с доставкой «секретов» до сервисов верхнего уровня, потому что все, что нужно хранить на сервисе — это публичный ключ производителя. Весь ключ занимает 33 байта. С их помощью и математической магии можно дальше провести handshake-соединение и проверить, что устройство обладает соответствующим приватным ключом.
На сервере мы храним только ключ производителя (root public key).
Синхронизация между сервисами и интранета у нас нет, о чем мы уже говорили. Также у нас нет защиты от копирования реквизитов.
Единственное, о чем мы забыли, так это об аутентификации. Устройство отправило приватный ключ, а мы проверили, что это мы его сделали и выдали, и проверили, что устройство им владеет. Но мы не знаем, что это за устройство, а мы их тысячи производим.
Поэтому мы применили трюк, который называется «Сертификат».
Аутентификация и сертификаты
На этом шаге во всю математическую магию с подписями и их проверками мы добавляем дополнительную информацию — сертификат. Для этого подпишем на фабрике не только публичный ключ (device public key), а ключ с дополнительной информацией.
Дополнительная информация в нашем случае.
- Дата производства и производитель.
- Модель и аппаратная конфигурация.
- Серийный номер, по которому можно аутентифицировать устройство.
- Опции: аппаратные и программные. Разные комплектации физически могут ничем не отличаться друг от друга, но сертификат будет содержать данные о том, за что заплатил клиент.
- Имя клиента и номер счета.
Всю эту информацию вместе с публичным ключом мы будем подписывать своим ключом производителя — root public key. После этого информация попадет на сервисы и они смогут удостовериться, что она правильная. Поскольку это сервисы наши и наших партнеров, нам они доверяют.
Статус по целям
Информация также зашивается на производстве, а доставка до сервисов не нужна. На сервере храним только ключ производителя.
Восстановление при потере. Всю информацию из сертификатов зашиваем во флеш-память устройства. Теоретически ее можно случайно или намеренно удалить, но в этой информации в сертификате нет ничего секретного. Даже сама подпись не секретна — там есть публичный ключ и подпись нашим ключом. Единственный секрет в сертификате — объемы продаж устройств с разными опциями.
Сертификат можно хранить на фабрике и высылать клиенту, если он его потерял. Клиенты редко специально стирают служебную область памяти. Обычно это делаем мы во время процедуры восстановления устройства: пришло устройство от клиента, оно полностью пропускается через инициализацию, все стирается, загружается заново, а сертификат копируется из заводской базы.
У нас нет восстановления при потере, защиты от копирования и синхронизации между сервисами.
На этапе аутентификации мы получаем и проверяем сертификат. Нам понятно, что это за устройство — мы знаем производителя, модель и серийный номер, что ему можно, а что нельзя.
Авторизация
Сертификат позволяет хранить информацию для авторизации.
Бесплатный сервис производителя. Зная серийный номер устройства, можно давать доступ всем. В своих сервисах мы даем доступ всем нашим базовым клиентам.
Белые списки серийных номеров. Для сервиса наших партнеров можно завести таблицу с белым списком серийных номеров: «Клиент Василий купил у нас два контроллера с такими-то серийниками, которые привязаны к его аккаунту»
Лицензии. Можно заранее что-то продать, а потом разрешать или запрещать доступ на основе опций, которые указаны в сертификате — контроллер с лицензией на систему X.
Никакой общей базы между сервисами, производителем или производителем систем нет. Все работает исключительно на информации от контроллера которая подписана нами, как производителем, когда он аутентифицирует в системе.
Промежуточный сертификат
Еще одна техническая задача, которую мы решали по дороге. В схеме, про которую я сейчас рассказал, есть корневой сертификат производителя — root private key. Он физически нужен каждый раз, когда вы создаете устройство. Но если устройств много, то к этому ключу нужен постоянный доступ для ограниченного круга лиц. Это плохо, потому что если его потерять, то придётся обновлять публичные ключи на всех сервисах, а к злоумышленникам он попасть не должен. Это большие организационные проблемы. Но есть решение.
Вводим промежуточные ключи на партию устройств, которые не так страшно потерять.
Мы сделали так же, только цепочка длиннее.
Сертификатом производителя подписываем промежуточный ключ. Физически это «флешка», которая на день выдается бригадиру на производстве. Аппаратными средствами ограничено число устройств, которые может подписать ключ. В середине схемы мы добавили промежуточный сертификат, в остальном ничего не менялось.
Защищённое хранилище ключей
Во всем этом нам не хватает защиты приватного ключа устройства — это все еще файл, который лежит на флешке. Злоумышленник может его скопировать, но скорее всего его потеряют или случайно откроют доступ.
В идеальном случае приватный ключ устройства хорошо бы защитить от копирования — положить его в черный ящик.
Черный ящик выполняет 4 операции:
- внутри себя генерирует ключ по запросу, но не отдает;
- отдает публичный ключ;
- подписывает сообщение;
- проверяет подпись.
Для проверки подписи нужен только публичный ключ, поэтому достаточно трех операций.
В моем понимании это должно быть аппаратное решение, желательно, отдельное от процессора. Есть несколько вариантов, лучший из которых — специальный криптопроцессор внутри SoC или в виде отдельного чипа.
Первый вариант черного ящика, который мы рассматривали — это модуль CAAM в процессорах NXP i.mx 6, 7, 8, которые мы используем. Проблема в том, что он реализован программными способами в Boot ROM процессора.
В нем могут быть баги, которые можно найти и даже эксплуатировать через другую функциональность процессора. Пару лет назад в этом модуле нашли дыру, которая позволяла обходить проверку подписи при загрузке прошивки. Это не та функциональность, которая нужна нам, но осадок остался. Другая проблема — процессоры с этим модулем тяжело ввозить в Россию, бумажки требуют заполнять.
Поэтому мы взяли отдельный чип. Признаюсь честно, я рассчитывал на то, что если мы не сможем привезти его в Россию, то что-нибудь придумаем — чип маленький, стоит 1 доллар. Но все получилось удачно — нашли чип Microchip ATECC, у которого все бумажки уже есть.
Microchip ATECC608A
Это отдельная маленькая микросхема, которая стоит копейки. Чип подключается по I2C — две «ножки» процессора, которые вы можете поделить еще и с другой периферией. У чипа стандартная распиновка. Мы использовали чип в первых версиях оборудования и просто паяли его поверх другой микросхемы с таким же протоколом и распиновкой, потому что она стандартная.
Чип умеет то, что нам нужно от такой микросхемы: считать подписи, хранить ключи и многое другое.
Характеристики:
- 16 слотов для ключей;
- умеет считать подписи ECSDSA, хеши, MAC, и шифровать AES, умеет DH;
- имеет генератор случайных чисел и криптографические счётчики;
- корпуса: SOIC-8, DFN6;
- протоколы: I2C, single wire;
- ~0.7$@1000pcs.
Как работать с микросхемой
К нему есть приличная документация, но под NDA. Если вы напишете сразу в gamma.spb.ru, то вам дадут её через 2 недели. Если в другую компанию — через 3 месяца. Мы написали в две компании, и когда уже все сделали, нам ответил другой дилер Microchip.
Аппнотов мало и они хуже среднего. Есть софт на GitHub — библиотека с HAL. Это забавно — документация под NDA, а софт, который по ней написан, на GitHub. Софт не поддерживает Linux, а поддерживает Raspberry Pi и МК от Atmel — это немного не то. Разработчики считают, что на всем оборудовании есть только одна I2C шина, например, ножки называются как на Raspberry Pi.
Есть интеграция с OpenSSL — плохо работает, но есть. Нет примеров под Linux и нет работы с персонализацией.
Персонализация чипа
Персонализация — это самая большая головная боль с чипом.
Проблема в том, что чип умеет очень много всего. У него есть 16 слотов, в которых хранится 16 ключей: пользовательские данные, или публичные ключи, или временные хранилища для других слотов — много вариантов.
К слотам надо как-то ограничивать доступ, и там тоже много конфигурационных вариантов: ограничить по паролю, по аутентификации в другом слоте, по времени обращения на фабрики.
В таблице тип ключа, доступ на чтение и запись, отношения между слотами — SlotConfig, KeyConfig.
В битовой маске (по 16 бит) каждого ключа, который мы используем, везде разные числа.
Самое грустное, что конфигурационная зона одноразовая, которая устанавливает функции слотов. Мы испортили 50 чипов, прежде чем все сделали правильно. Чип работает только после блокирования конфигурации. Отдельно есть блокировка отдельных слотов
Документации нет ни в примерах, ни в софте. Есть документация на отдельные биты, но там все сложно. Во всех примерах от Microchip написано: «Такой блок загрузите, и у вас как-то будет работать как в примере отправки данных в Amazon».
На это ушло много времени, но в процессе сделали классную утилиту.
Утилита atecc-util
Это консольная утилита, которая умеет выполнять большинство функций чипа и позволяет работать чуть проще. Она есть на GitHub под MIT-лицензией.
Утилита использует CryptoAuthLib. Она умеет дружественнее работать с конфиг-зоной, умеет работать с SHA, MAC, ECDSA, DH. Интерфейс дружественный к batch, потому что мы создавали утилиту для использования в скриптах, в первую очередь. То, что ее может вызвать человек — побочная фича. Утилитой можно составить список — план команд: «Сначала персонализируй эту зону, потом запиши такой ключ».
Пример вызова утилиты достаточно человекочитаемый.
atecc - b 10 - c 'serial' - c 'read-config /tmp/config.dump'
Утилита собрана под Linux, под AMD64 — есть в Debian-пакете.
Другие инструменты персонализации
У нас есть Excel-табличка, чтобы считать биты. Если вы покажете нам скан NDA c Microchip, мы вам ее отдадим.
Мы все покрыли тестами, поскольку много вариантов, когда можно забыть один бит, и ваш приватный ключ прочитает какая-нибудь служебная команда. Тесты проверяют реальное устройство. Они обращаются к микросхеме и проверяют правильность конфигурации на устройстве: можно ли этот слот можно прочитать, можно ли сделать такую подпись?
Параллельно с битами мы создали список гарантий, которым должно удовлетворять это устройство, и проверяли, как все работает. Используем фреймворк bats — очень интересная штука. Выглядит это так.
Список тестов для примера. Верхние пройдены, а нижние нет.
Настройки в устройствах
Для себя мы используем всего два слота для задачи, о которой я сейчас рассказываю. В обоих мы храним приватный ключ устройства. Разница в том, что с первым связан постоянный сертификат, который выписан в 1970 году на 200 лет.
Это сделано из-за того, что в IoT время не всегда синхронизованное. Инфраструктура сертификатов предполагает проверку срока валидности сертификата. Если синхронизация на устройствах сломана, то какой-нибудь жизненно важный сервис может отказать, например, VPN.
Поэтому один слот бесконечный — постоянный. Он генерируется однократно и не меняется на протяжении жизни устройства. Для этого ключа генерируется сертификат на 200 лет — для закрытых сетей.
Другой слот обновляется. Максимальный срок жизни сертификатов — год. Это сделано на всякий случай, если что-то будет скомпрометировано. Приватный обновляемый ключ устройства генерируется по мере истечения срока валидности сертификата устройства. Используется для аутентификации в открытых сетях, обновляется раз в месяц или реже, вместе с сертификатом.
Для пользователей мы сгенерировали разные комбинации, включая несколько слотов для приватных ECDSA ключей. Пользователи могут сгенерировать свой ключ в отдельном слоте, если не доверяют нашему приватному ключу. Для этого нужно доверять только Microchip. Пользователи могут считать подписи, сделать шифрование — мы отдали все, что умеет микросхема.
Пока, к сожалению, никто не использовал, но мы надеемся на это.
Инфраструктура: промежуточные ключи
Я уже говорил, что в какой-то момент мы внедрили промежуточные сертификаты, чтобы не светить корневым сертификатом, который нельзя терять. Он никогда не появляется на фабрике.
Физически промежуточные сертификаты — это чип ATECC508A. От 608 отличается незначительно, но в 508 есть функциональность, которая пригодилась для ключей, а в 608 её уже нет.
Чип подключается через USB-I2C переходник. Это USBISP с прошивкой tiny-usb-i2c — программатор, который можно перепрошить в USB-I2C бридж. Промежуточные сертификаты подписывают своим приватным ключом сертификаты устройств.
Для нас оказались полезны две возможности микросхемы.
Аппаратная защита паролем слота. Микросхему можно запрограммировать, чтобы она считала подпись только при выполнении двух условий:
- когда устройство воткнули в компьютер;
- введен пароль.
Бригадиру на производство мы выдаем промежуточный ключ и пароль на какое-то количество контроллеров. Соответственно, надо украсть и ключ, и пароль, чтобы получить доступ. Эту возможность мы получили бесплатно, но она повышает безопасность системы.
Аппаратное ограничение на количество использований. Криптографический счетчик внутри может только увеличиваться. Когда он достигает заранее заданного один раз предела, микросхема больше ничего не подписывает.
OpenSSL на клиенте
Рассмотрим как все работает на клиенте. На контроллере у нас OpenSSL. Мы не стали ничего изобретать — это обычный TLS, обычный PKI. Нам дополнительно понадобилась клиентская библиотека. В подавляющем большинстве софта Linux она используется для защищенного соединения.
Мы взяли код от Microchip, его немножко дописали, поддержали свежий OpenSSL
1.1. В итоге он умеет работать с аппаратным ключом — аппаратно поддерживает пароли для приватных ключей.
Выглядит это примерно так.
openssl req -new -engine ateccx08 -keyform engine -key ATECCx08:00:04:C0:00 -subj
"/CN=wirenboard-AP6V5MDG" -out device AP6V5MDG.csr
Это вызов обычного OpenSSL и указание использовать соответствующий модуль engine. Здесь задается ключ: адрес, модель, а последние два байта — это номер слота, который используется. Все передается так, будто это файл ключа, но это не файл — надо зайти в устройство.
SSL на сервере
На сервере работает любой SSL, в том числе OpenSSL. Никаких доработок и нестандартных сборок на серверной части уже не нужно. Все, что нужно на сервере, это уметь проверять цепочку certificate bundle (device cert + intermediate cert), и хранить наш публичный ключ, который мы опубликовали на сайте — Wiren Board ROOT CA.
Стандартный TLS говорит о том, что обе стороны должны аутентифицировать друг друга. В теории клиент — наш контроллер — аутентифицирует и сервер. Это достается бесплатно — в том самом handshake.
Для этого у сервера тоже должна быть ключевая пара: приватный и публичный ключ. Её можно сделать самоподписанной, и тогда на все клиенты можно раздать публичный ключ. Можно использовать letsencrypt или сертификат в SSL, как на сайтах, чтобы домен был в браузере зеленым.
В трех сервисах мы это сейчас сделали, первый из них — MQTT.
MQTT: mosquitto на клиенте
Это стандартная и популярная шина для передачи данных от IBM. У нас она используется для связи контроллеров с нашим облаком и с некоторыми партнерскими системами.
Mosquitto — это распространенная реализация брокера и клиентских библиотек, в том числе, под Linux. Нам пришлось его пропатчить, чтобы использовать OpenSSL engine (передать одну опцию) и парсить опцию «keyfile», чтобы указывать там аппаратный ключ. Патчи на клиент маленькие, по 20 строк.
Дальше клиент передает страшный bundle.
После этого используется аппаратный приватный ключ и проверяется сертификат сервера в другую сторону.
mosquitto_sub -h mqtt.wirenboard.com -p 8884 -cert /etc/ssl/device/device_bundle.crt.pem --key 'engine:ateccx08:ATECCx08:00:04:C0:00' --capath /etc/ssl/certs/ -t /# -v
Здесь сначала идет консольная утилита. Дальше указан сертификат устройства — он начинается с -cert
. Сертификат идет bundle-файлом — метаинформация. Приватный ключ идет после сертификата --key
. За ключом нужно идти в аппаратное хранилище.
Последняя часть, которая начинается с --capath
, это проверка нашего сервера. Мы его проверяем по общемировому списку SSL-ключей, потому что у нас letsencrypt.
Так выглядит настройка бриджа.
root@wirenboard-AXXVJI62:~# cat /etc/mosquitto/conf.d/bridge-hw.conf
connection wb_devices_cloud.wirenboard-AXXVJI62
address contactless.ru:8884
bridge_capath /etc/ssl/certs/
bridge_certfile /etc/ssl/device/device_bundle.crt.pem
bridge_keyfile engine:ateccx08:ATECCx08:00:04:C0:00
notifications true
notification_topic /client/wirenboard-AXXVJI62/bridge_status
topic/# both 1 ""/dient/wirenboard-AXXVJI62
Mosquito-бридж также настраивается и передаются данные.
На сервере стандартный Mosquitto — ничего патчить не надо.
per _listener_settings true
listener 8884 0.0.0.0
cafile/etc/mosquitto/certs/WirenBoard_Root_CA.crt
certfile /etc/letsencrypt/live/contactless.ru/fullchain.pem keyfile/etc/letsencrypt/live/contactless.ru/privkey.pem
require.certificate true
use_identity_as_username true
password_file /etc/mosquitto/passwd.conf
allow_anonymous false
acl_file /etc/mosquitto/ad.conf
:~$ cat /etc/mosquitto/acl.conf
pattern write /client/%u/#
pattern read /client/%u/#
Вся настройка — несколько шагов.
- Зашиваем Root CA и letsencrypt-сертификаты от домена — прописать в настройках. Дальше указываем команду проверять сертификаты.
- Дальше классная опция Mosquitto. Она позволяет без всяких допиливаний использовать метаинформацию сертификата в качестве
username
для дальнейших листов доступа к MQTT. - После того, как клиент отправил свой сертификат, например, с серийником (CN) wirenboard-AXXVJI62, у нас написано, к каким подтопикам разрешать доступ клиенту.
per_listener_settings:
на отдельном порту, чтобы не мешать аутентификации по логину/паролю (>1.5.5).
Это вся настройка MQTT-брокера нашего Wiren Board IoT Cloud Platform.
OpenVPN
OpenVPN нам нужен, чтобы ходить к клиентам на контроллер, когда они все себе сломали. Для этого нужно, чтобы он поднимался по одному клику из настроечного интерфейса и нам давался доступ к клиентскому устройству.
Для OpenVPN на клиенте тоже нужен патч, но он простой. Дальше в опции запуска добавляется то же, что и выше: bundle, проверка сервера, engine.
openvpn --capath /etc/ssl/certs/ --cert /etc/ssl/device/device_bundle.crt.pem --key engine:ateccx08:ATECCx08:00:04:C0:00
На сервере мы также используем letsencrypt.
ca /etc/openvpn/WirenBoard_Root_CA.crt cert
/etc/letsencrypt/live/vpn1.wirenboard.com/fullchain.pem key
/etc/letsencrypt/live/vpn1.wirenboard.com/privkey.pem
Развернуть другой сервер легко — не надо передавать никакие файлы на контроллеры. Это все подписано по стандартной эко-системе публичных ключей.
Nginx
Наша заключительная фича. Nginx сделан для партнеров, у которых сложный сервис, или если не хочется прикручивать проверку клиентских сертификатов, SSL. Часто у них уже стоит nginx перед сложным сервисом на web-сервере, как reverse-proxy. Если нет — ставится одной строчкой в конфиге nginx.
nginx проверяет клиентский сертификат, отдает HTTP-запрос, а дальше своему бэкенду или сложному сервису. Если всё ОК, то выставляет хедеры перед передачей запроса ниже: common name, разрешено ли клиенту использовать этот сервис, заплатил ли он за него при покупке контроллера. Если не ОК, то ошибка 400.
ssl_client_certificate WirenBoard_Root_CA.crt;
ssl_verify_client on;
nginx на клиенте. У нас есть агенты — приложения, которые отправляют данные по HTTP. На Linux-контроллерах мы используем nginx на клиенте, чтобы агенты тоже ничего не знали о SSL, наших аппаратных ключах, работу с OpenSSL.
Дальше можно обычным wget без ключей, bash или вашим любимым языком, завернуть любой HTTP-запрос в TLS с аппаратным ключом. Все настраивается в 10 строчек.
server {
listen 8080;
location / {
proxy_pass https://example.com;
proxy_ssl_name example.com;
proxy_ssl_server_name on;
proxy_ssl_certificate/etc/ssl/device/device_bundle.crt.pem;
proxy_ssl_certificate_key
engine:ateccx08;ATECCx08:00:04:C0:00;
}
}
Где посмотреть
Криптомодуль есть во всех контроллерах Wiren Board 6, которые выпускаем уже полтора года. В инфраструктуре уже был задел, но развернули и настроили все только недавно.
У нас это работает для удаленного доступа к web-интерфейсу контроллера на cloud.wirenboard.com для OpenVPN для контроллера. Также мы подняли среду Grafana с InfluxDB, в которой все работает по MQTT. Можете посмотреть в системе saymon.info — это система мониторинга (MQTT) наших партнеров.
Если вы производитель систем верхнего уровня, делаете что-то для себя, или хотите поднять удаленный доступ, Grafana, MQTT-брокер, как у нас, то пишите, звоните. Поделимся инструкциями — поможем.
Всю работу, о которой я рассказал, выполняли два человека: Олег Сериков — настройка OpenSSL на клиентах и серверах, и Никита Маслов — всё остальное. Спасибо им!
Это была статья на основе одного из лучших докладов конференции InoThings Conf 2019. Подписывайтесь на YouTube-канал с остальными лучшими докладами с конференции 2019 года. Также вступайте в канал и чат в Telegram. В чате участники задают вопросы, делятся наработками, опытом и обсуждают свежие материалы по IoT.
Автор: Константин Рисков