Очень много сообщений про DNS-over-HTTPS в Firefox, про то, что внедрение этого протокола, якобы, “позволяет обходить любые блокировки и DPI”. Между тем, DNS-over-HTTPS (DoH) в Firefox – это способ сокрытия DNS-запросов и DNS-ответов от третьей стороны, причём скрываются только запросы от браузера до рекурсивного резолвера (подразумевается, что до резолвера Cloudflare). Заметьте, что использование DoH не скрывает рекурсивные запросы, источником которых является браузер Firefox. Например, если некоторую уникальную DNS-метку встроить в веб-страницу, то на авторитативном сервере будет видно, откуда пришёл рекурсивный запрос, соответствующий конкретной сессии конкретного браузера. По IP-адресу источника запроса (рекурсивного резолвера), по некоторым другим признакам, можно определить, используется ли клиентом штатный DNS от провайдера доступа, или это та или иная реализация DoH. В качестве источника DNS-меток (такой источник принято называть “праймером”) с большим охватом может работать любой популярный веб-сервис или веб-сайт: например, какой-нибудь веб-счётчик (что-то подобное “Яндекс.Метрике”), страница популярной социальной сети и т.д.

Однако на пути от браузера до рекурсивного резолвера, действительно, запросы и ответы DNS не будут видны третьей стороне, просматривающей трафик, так как они в HTTPS защищены шифрованием. Но к “обходу блокировок” это относится весьма косвенно.

Предположим, что блокировка доступа к веб-ресурсу осуществляется провайдером на уровне DNS. Смысл подобного метода блокирования в том, чтобы браузер (или другие программы-клиенты) не мог определить подлинный IP-адрес, с которым требуется установить соединение. Как такая блокировка работает?

В простом варианте, провайдер так настраивает резолвер, обслуживающий клиента, что в ответ на запрос о заблокированном ресурсе (по имени домена) – резолвер возвращает не подлинный IP-адрес, а либо адрес сервера-заглушки, либо заведомо недоступный адрес. Чтобы это работало, клиент должен использовать DNS провайдера. Эта схема реализуется самыми элементарными средствами. Для её преодоления не требуется ничего зашифровывать, а достаточно использовать другой DNS-резолвер, не провайдерский.

В продвинутом варианте, уже система DPI обнаруживает все DNS-запросы и DNS-ответы, вне зависимости от того, к каким DNS-серверам они отправлены и от каких получены. Фильтрующий узел вмешивается в трафик в том случае, если силами DPI обнаружены запросы, относящиеся к заблокированным именам. Вмешательство в трафик может выражаться как в подмене ответов, так и в блокировании запросов; конечно, можно заблокировать и ответы. В этом случае DoH помогает, так как DPI перестаёт видеть DNS-трафик. Однако тот же фильтрующий узел и DPI можно настроить так, что они будут блокировать трафик DoH. Блокировать придётся весь трафик, а DPI потребуется очень серьёзно доработать. При этом в Firefox по умолчанию будут встроены средства, позволяющие предотвратить автоматическое включение использования DoH. Эти средства предназначены для корпоративных сетевых сред, где фильтрация DNS нередко является обязательным требованием “политик безопасности”. Такое поведение браузера пользователь может преодолеть, если включит использование DoH вручную.

(Отмечу, в скобках, что все описанные выше методы с подменой информации DNS противоречат DNSSEC и, соответственно, будут обнаружены, если клиент поддерживает DNSSEC.)

Защита от просмотра DNS-трафика никак не влияет на блокировку доступа непосредственно по IP-адресу узла: если соединение с конкретным адресом установить не удаётся, то не важно, как этот адрес был получен – через “открытый DNS” провайдера или через “защищённый DNS-over-HTTPS”. Да, нетрудно предложить вариант, в котором IP-адреса постоянно изменяются, а “верный адрес” передаётся только при использовании сервиса DoH. Так можно устроить, если авторитативные серверы DNS соответствующей доменной зоны как-то связаны с провайдером сервиса DoH. Однако при этом активная система блокирования может узнавать “верные адреса”, просто используя свой экземпляр браузера Firefox. Конечно, всегда остаётся экстенсивный вариант развития данной схемы, при котором, с одной стороны, сотни тысяч IP-адресов случайно распределяются по DNS-ответам, а с другой стороны – какие-то, – возможно те же, – сотни тысяч и миллионы адресов попадают под превентивную блокировку. При этом DoH здесь помогает только тем сервисам, у которых очень много IP-адресов.

Нередко можно услышать, что данный метод, применительно к проблеме блокирования доступа, хорош тем, что его трафик, по внешним признакам, не отличается от “обычного HTTPS” (то есть, от HTTPS для веб-сайта). Мало кто готов блокировать весь HTTPS-трафик. Конечно, приложив достаточно вычислительных мощностей, попытаться отличить трафик DoH от работы с веб-сайтами – можно: есть IP-адреса, есть характеристики отправляемых и принимаемых пакетов, продвинутая система блокирования умеет делать проверку сервисов (connection probe) и так далее. Другое дело, что ресурсов для блокирования потребуется действительно много и будут ложные срабатывания. Более того, в качестве следующего шага по защите возможно заворачивание DNS-трафика в “самый настоящий” HTTPS-сеанс работы с обычным веб-сайтом: DNS-запросы могут передаваться браузером в качестве нагрузки, в специальных HTTP-заголовках; и в таких же HTTP-заголовках сервер пришлёт ответы.

В целом, сверхидея DNS-over-HTTPS хорошо укладывается в самую современную концепцию в области информационной безопасности. В этой концепции “доверенными” являются только приложения – клиентское и серверное. То есть, даже операционная система не относится к доверенным. Криптография позволяет двум приложениям надёжно идентифицировать (и аутентифицировать) друг друга: браузер Firefox, используя принесённые с собой TLS-сертификаты, идентифицирует и аутентифицирует серверное приложение, которое исполняется на узлах Cloudflare и реализует сервис рекурсивного опроса доменных имён. Для схемы не важно, каким образом, по каким транзитным сетям, приложения устанавливают между собой соединение. Да, тут есть масса оговорок – про аппаратуру, которая исполняет команды; про ядро ОС, имеющее полный доступ к памяти приложений; и так далее. Но, тем не менее, логическая концепция именно такая. Развитие этой идеи в ближайшем будущем приведёт к тому, что появятся “различные интернеты”, работающие внутри того или иного приложения. Но это другая история.

Вернёмся к DoH в браузере Firefox. Данный инструмент, сам по себе, не является универсальным средством, “позволяющим обходить все блокировки”, но он защищает от утечки информации о DNS-запросах/DNS-ответах на “последней миле”: то есть, на пути от резолвера до браузера. При этом браузер замыкает на стороне пользователя некоторый особый контур, в который теперь входит и защищённая доставка контента (TLS на веб-сайтах), и собственный сервис доменных имён. “Интернет – это то, что показывается в браузере”.



Комментарии (4) »

Что может сделать небольшой “гражданский дрон” (беспилотник) в случае, если из-за помех нет связи с пультом управления и также потерян сигнал спутниковой навигации? Понятно, что самое простое – это попытаться относительно медленно спуститься вниз и приземлиться. Такой вариант обычно и запрограммирован. Но оператору хотелось бы, чтобы дрон вернулся к нему в любом случае, если не точно в точку старта, то хотя бы оказался неподалёку от неё.

Понятно, что если навигационная полностью система полагается на сигналы спутников (пусть это GPS, не так важно), то в условиях, когда эти сигналы недоступны из-за помех, беспилотник уже не может не то что вернуться в точку старта, но и нормально продолжать полёт. Конечно, проблему решает автономная инерциальная система навигации. Это самый надёжный вариант.

Качественная и надёжная инерциальная система заметно повысит стоимость беспилотника: комплектующие для точной и лёгкой системы могут оказаться дороже, чем сам аппарат-носитель – речь ведь идёт об относительно недорогом устройстве. Но, с другой стороны, можно взять дешёвые массовые сенсоры, используемые в смартфонах.

Да, точность в таком случае окажется низкой, будет накапливаться ошибка. Накопление ошибок – это основная проблема инерциальной навигации (для всех аппаратов, а не только для “гражданских беспилотников”). Даже небольшое, но непредсказуемое, “мгновенное отклонение” показателей датчиков, за несколько десятков минут полёта с интенсивным маневрированием вполне может привести к уводу измеряемых координат на сотни метров от реального положения аппарата. Но нам-то нужно решить довольно узкую задачу: автономное возвращение к оператору в критическом случае. Так что условия использования инерциальной навигации – тепличные: во-первых, пока работает спутниковая навигация, инерциальную систему можно эффективно корректировать, а история коррекции поможет фильтровать ошибки и после того, как аппарат перейдёт на полностью автономный полёт; во-вторых, возвращение к оператору должно происходить кратчайшим путём и без излишнего маневрирования, поэтому, в большинстве сценариев, автономный полёт займёт всего несколько минут, а аппарат будет стараться сохранять оптимальные для обеспечения точности “аварийной навигации” параметры ускорения. В общем, даже простая и не очень точная инерциальная система – справится.

Тут есть ещё один, весьма важный, момент: дрон мог находиться за каким-то препятствием, например, за углом здания – поэтому вернуться по прямой не выйдет, а для того, чтобы проложить безопасную траекторию, нужно знать, где возможен безопасный полёт. Это означает, что на борту требуется карта, на которой обозначены коридоры безопасного возвращения. Это, впрочем, не слишком сложная проблема: просто, перед началом полёта, придётся разметить эти самые коридоры, ну или надеяться на то, что дрону повезёт.

Современный дрон содержит камеру, часто – не одну. Это хорошее подспорье для создания автономной навигации. Так, параметры движения можно определять по перемещению в поле зрения объектива “текстур” поверхности, над которой происходит полёт. Этот приём некоторые разработчики любительских дронов уже используют. Другой вариант – применение простого машинного зрения: у оператора может быть с собой некая визуальная метка (табличка с QR-кодом, например), в случае потери связи, оператор показывает эту метку в сторону дрона – если последний находится в прямой видимости, то он сможет обнаружить метку с помощью камеры и лететь в её сторону (дальность легко вычислить, зная оптические параметры объектива). Понятно, что метка должна быть не слишком маленькой, а объектив и камера – позволять её обнаружить.

Неплохим развитием этой идеи является какой-либо активный оптический канал, например, лазерный фонарик, который светит в сторону дрона некоторым модулированным сигналом. Во-первых, подобному сигналу на практике сложно поставить помеху (из-за того, что приёмник может быть выполнен узконаправленным, а помехопостановщик не сможет принимать подавляемый сигнал, если только не находится между дроном и источником, либо не видит каких-то отражений); во-вторых, сам сигнал может передавать дрону значение дальности до источника, а азимуты – определит приёмник.

Итак, даже у любительского дрона может быть целый арсенал средств, обеспечивающих более или менее надёжный возврат к оператору и в полностью автономном режиме, и в режиме, когда оператор подаёт аварийный опорный оптический сигнал. Но, конечно, в коммерческих гражданских дронах эти методы вряд ли реализуют.



Комментарии (1) »

(Записка с перечнем поддерживаемых элементов протокола TLS 1.3. Чтобы не потерялось.)

Тестовый сервер TLS 1.3 на tls13.1d.pw:

1. Версия протокола: 1.3 (0x0304), Draft-28 (совпадает с 1.3, кроме номера версии) и Draft-23 (в этой версии есть небольшие отличия в алгоритмах от 1.3, версия до сих пор встречается на практике). Вообще, draft-версии соответствуют черновикам RFC, протокол реализовывался (в статусе экспериментального) параллельно с разработкой RFC. Например, Draft-23 был очень распространён: его поддерживали и Firefox, и Chrome. Когда я запускал тестовый сервер, RFC ещё не было, и именно версия Draft-23 была основной.

2. Криптосистема подписи: сейчас используется ECDSA в группе P-384; раньше был вариант в группе P-256 (это названия кривых), можно сделать оба варианта (попеременно выбирать для каждого запроса), но тогда нужна обработка второго сертификата – возможно, добавлю. А вот поддержку RSA пока решил не добавлять.

3. Поддержка DH (при согласовании общего секрета в рамках установления соединения): P-256, P-384, x25519, а также “классический” FFDH с разрядностью 3072 бита (поддерживается, например, Firefox).

4. Шифры: AES-128-GCM-SHA256, AES-256-GCM-SHA384, ChaCha20Poly1305. Нет CCM-режимов. Update 12/01/20: добавлено динамическое изменение приоритетов шифров на стороне сервера; добавлена поддержка сообщений KeyUpdate и, соответственно, ротация симметричных сессионных ключей.

5. Поддержка HelloRetryRequest: сервер, с некоторой вероятностью, отвечает на ClientHello сообщением пересогласования параметров – HelloRetryRequest (HRR), запрашивая другую группу из поддерживаемых клиентом; это происходит только в том случае, если набор групп, заявленный клиентом, позволяет. Это довольно богатое для целей исследования реализаций TLS направление, потому что здесь, в том или ином виде, требуется сохранение состояния соединения. Пример: предположим, что клиент заявляет для DH поддержку x25519, P-256 и FFDHE-3072, но ключ передаёт только для x25519 (это обычная ситуация в TLS 1.3); в таком случае сервер может ответить HelloRetryRequest, запросив либо P-256, либо FFDHE-3072.

6. Поддержка TLS cookie: при ответе с HelloRetryRequest – сервер передаёт клиенту TLS cookie. Это специальное расширение ClientHello/HelloRetryRequest, в котором передаётся некоторое значение – клиент должен вернуть это же значение в новом запросе ClientHello; у TLS cookie две основных роли: 1) “выгрузить” клиенту представление состояния начинающейся сессии, чтобы сервер не хранил дополнительные записи на своей стороне; 2) проверить, что клиент действительно активен и отвечает на запросы по адресу, с которого получено сообщение ClientHello. (Тестовый сервер совпадение значений TLS cookie не проверяет.)

7. Поддержка ESNI: поддерживается два варианта криптосистем DH – P-384, x25519 (в ESNI используется протокол Диффи-Хеллмана со статическими ключами, которые публикуются в DNS); шифр для ESNI – один: AES-128-GCM.



Комментарии (7) »

Существенная часть информации о TLS-соединении доступна системам инспекции трафика (DPI). Чаще всего, конечно, упоминается поле SNI (Server Name Indication), в котором передаётся имя сервера. Для маскировки SNI в TLS 1.3 уже предложен дополнительный механизм Encrypted SNI.

Версии TLS ниже 1.3 подразумевают передачу серверных TLS-сертификатов в открытом виде (на начальном этапе установления соединения). В состав сертификата входят и имена сервера (может использоваться несколько), и открытый ключ сервера. Открытый ключ так же идентифицирует сервер, однако в некоторых случаях один и тот же ключ может использоваться с разными именами. Серверный сертификат является важным признаком классификации TLS-соединений, с другой стороны, сертификат имеет существенный размер и требует наличия в составе DPI-системы функций, которые могут разобрать сертификаты по полям и проанализировать – всё это заметная дополнительная вычислительная нагрузка, особенно, если соединений много.

В открытом виде (версии меньше 1.3) на начальном этапе передаются и клиентские сертификаты, которые в TLS используются для аутентификации клиента сервером. Клиентские сертификаты часто применяются в решениях VPN, соответственно, анализ клиентского сертификата в этих случаях позволяет точно распознать начало VPN-соединения.

В TLS 1.3 – сертификаты уже передаются в зашифрованном виде (и серверные, и клиентские), таким образом, переход на TLS 1.3 закрывает данную утечку информации в сторону DPI. (В принципе, передать клиентский сертификат в зашифрованном виде можно и в TLS предыдущих версий, но для этого потребуется сначала установить TLS-соединение, а потом – отправить  с сервера запрос аутентификации, что не всегда совпадает с логикой использования протокола приложением.)

Особенно мощная система DPI может вести статистику соединения: в TLS-трафике присутствует открытая последовательность TLS-записей, поэтому анализирующая поток данных система видит размеры записей, а также их типы. Здесь, опять же, существенный шаг вперёд проделан в версии TLS 1.3. А именно: реальные типы записей в TLS 1.3 скрыты – виден только “исторический” тип Application, назначаемый всем записям в фиктивном заголовке, это делает поток типов однородным, полностью убирая важный источник метаданных для классификации трафика. В более ранних версиях TLS типы записей передаются в открытом виде, так что анализатор потока может обнаружить состояние TLS-сеанса (так как протокол использует записи разных типов для передачи сигналов, сообщений об ошибках и пр.)

DPI видит длину отдельных TLS-записей. Однако TLS позволяет приложению разными способами маскировать реальную длину передаваемых данных. А в TLS 1.3 имеется специально для этой цели предназначенный механизм на уровне защищённого транспорта: длина записей может выравниваться с помощью дополнения. Это означает, что реализация в версии 1.3, минимизирующая утечки метафинформации, может превратить поток данных в последовательность TLS-записей одинакового типа и одинаковой длины – DPI будет сложно за что-то зацепиться.

Подобный статистический анализ трафика требует существенных ресурсов: DPI необходимо не только собирать отдельные пакеты в сессии, но и выделять заголовки, накапливать данные для каждой сессии.

Естественно, хорошо защищённый протокол должен свести к минимуму утечки метаинформации. Это, частично, уже сделано в TLS 1.3, однако TLS является весьма универсальным протоколом, который, к тому же, проектировался с учётом повышения его эффективности в роли транспорта для массовых соединений, а не как протокол, скрытый от DPI. Поэтому пассивные анализаторы трафика всё ещё получают из TLS-соединения дополнительную информацию об узлах и состоянии приложений (дополнительную – по сравнению, например, с информацией уровня TCP, к которой относятся адреса узлов, номера портов, порядок и размер TCP-пакетов и др.). Методы криптографии позволяют спроектировать хорошо замаскированный протокол, но каждый шаг маскировки снижает эффективность, в частности, увеличивает затраты на установление соединения (это перебор адресов, генерация дополнительных секретов и т.д.). Тем не менее, ситуация тут такова, что даже небольшой, но верно спланированный шаг маскировки – существенно затрудняет работу DPI. В ряде случаев, рост “сравнительной сложности” на стороне DPI оказывается, как минимум, экспоненциальным: представьте, что специальный протокол использует перемешивание UDP-пакетов разных потоков, отправляемых по различным адресам серверов.



Комментировать »

Речь о протоколе, который скрывает метаинформацию о самом факте обмена сообщениями. Какими свойствами должен обладать такой протокол? Можно ли что-то подобное вообще реализовать на практике? Прежде чем такие вопросы более или менее содержательно формулировать, нужно, конечно, выбрать модель угроз.

Предположим, что стоит задача устойчивого обмена короткими сообщениями (тексты и фотографии низкого разрешения) через тот или иной вариант “глобальной” Сети. “Глобальной” в кавычках, потому что ситуация за пару десятков лет изменилась очень существенно: есть все основания не только использовать здесь кавычки, но и рассматривать возможность потери связности и разделения Сети на сегменты (которые, к тому же, не работают и внутри, но это другая история). Чтобы излишне не сгущать тучи, предполагаем, что связность всё же есть, какие-то данные передаются, однако некая третья сторона полностью контролирует трафик, просматривает его, может произвольно подменять узлы, передаваемые данные, и старается прервать все сеансы обмена, которые не были прямо санкционированы автоматом фильтрации (это вариант “белого списка протоколов”, который, например, я упоминал в недавней статье про фильтрацию трафика). Система фильтрации/блокирования пытается обнаружить трафик скрытого протокола, выявить узлы, его использующие, и работает в автоматизированном режиме: то есть, фильтры и блокировки включаются автоматом, но правила могут задаваться вручную.

Скрытый протокол, по условиям задачи, должен работать на базе распределённой сети и не требовать строгого центрального управления всеми узлами при обмене сообщениями. Это обусловлено риском компрометации такого “центра управления”, а также тем, что центральный вариант гораздо более уязвим к сегментации сети (да, есть варианты с автоматическим выбором нового “центра” и так далее, но мы пока просто примем, что протокол использует распределённую сеть).

Сразу же возникает вопрос о транспорте данных (может быть, этот транспорт – TCP?), но так как мы практически сразу столкнёмся с необходимостью стеганографии и маскировки узлов, то с транспортом всё окажется не так просто, поэтому конкретный протокол в условия задачи не включаем, оговорим только, что “какой-то транспорт” между “какими-то узлами” должен быть доступен.

Теперь нужно определить, что подразумевается под термином “скрытый”, а также то, какую задачу решает протокол. В сугубо теоретическом смысле определение такое: протокол позволяет двум узлам обменяться произвольными сообщениями небольшого размера (то есть, передать некие данные в режиме “запрос-ответ”), при этом другие узлы сети, а также активная третья сторона, просматривающая и модифицирующая трафик, по результатам сеанса не получат никакой новой информации, ни о сообщениях, но об узлах, ими обменявшихся.

Итак, какими свойствами и механизмами должен обладать скрытый протокол в таких (весьма жёстких) условиях?

1.

Очевидно, сообщения должны быть зашифрованы, а также – аутентифицированы. Это самая простая часть: стороны применяют симметричные ключи, распределённые тем или иным способом, и стойкий шифр с механизмом проверки подлинности. Зашифровать сообщения нужно не только и не столько для того, чтобы скрыть “полезную нагрузку”, сколько с целью удаления из потока статистически значимых признаков, позволяющих распознать факт обмена сообщениями.

2.

Для сокрытия трафика необходимо использовать стеганографию. В принятой модели угроз, если один из узлов просто передаёт зашифрованное сообщение вне прочих сеансов, то этот факт тут же оказывается обнаружен. Стеганографический канал маскирует сеанс обмена сообщениями под трафик другого типа. Это означает, что протокол не может прямо использовать привычные виды транспорта, а должен подразумевать наличие дополнительного трафика. Простой пример: сообщения могут быть скрыты внутри текстов веб-страниц и веб-форм, в качестве базового трафика используется имитация работы с веб-сайтом. В данном случае, веб-трафик может передаваться по TCP или UDP, с использованием TLS/HTTPS или QUIC, экзотические варианты вместо веб-трафика используют непосредственно TLS, или даже служебные заголовки IP-пакетов и DNS, всё это не так важно для стеганографии. Важны достаточный объём данных и наличие симметричных ключей, позволяющих организовать стойкий стеганографический канал. При наличии ключей – такой канал организовать всегда можно. (Ключи используются для задания псевдослучайной последовательности, которая необходима для работы механизма встраивания скрытых данных в поток трафика.)

3.

Так как третья сторона может подменять узлы, протокол должен предусматривать аутентификацию узлов. При этом процесс аутентификации оказывается сложным: узел не может просто так назваться узлом, использующим скрытый протокол, да ещё и подтвердить это подписью – подобное решение раскрывает всю секретность, так как третья сторона получает возможность простым способом “проиндексировать” все узлы, поддерживающие протокол, что даёт дополнительную информацию о сеансах, в которых данные узлы участвовали. Простые алгоритмы, основанные на знании секретного ключа, очень эффективны, но они работают только в ограниченном случае – например, для закрытого списка корреспондентов. Пример такого алгоритма, реализованного на уровне веба: проверяющий полномочия узла корреспондент отправляет запрос HTTP GET, где в качестве имени документа указан уникальный код, снабжённый меткой подлинности, вычисленной на основе секретного ключа; узел, обнаружив такой запрос и убедившись в его валидности, отвечает соответствующим значением, также с меткой подлинности; если же запрос невалидный, то узел отвечает стандартной ошибкой HTTP – тем самым он оказывается неотличим от обычного веб-сервера. Однако, действительно хорошо скрытая от активного “индексатора” аутентификация, – или, если говорить строго, семантически стойкая аутентификация, – возможна только на базе нескольких итераций “запрос-ответ” и, похоже, оказывается весьма сложной. Другими словами, с аутентификацией как раз и могут возникнуть основные проблемы на практике.

4.

Необходим механизм миграции узлов и обнаружения ранее не известных адресов (имён) узлов, поддерживающих скрытый протокол. Понятно, что простая публикация списка не подходит. В принципе, возможны варианты, в которых успешный сеанс обмена сообщениями завершается передачей некоторых “секретов”, которые позволяют построить список вероятных адресов (или имён), а также ключей аутентификации, подходящих для следующих сеансов. Соответственно, для обнаружения новых узлов потребуется перебрать адреса из этого списка, пытаясь аутентифицировать их. Здесь возникает ещё одна отдельная задача – построение скрытого механизма сканирования узлов.

5.

Выбранная модель угроз гарантирует, что третья сторона может использовать подставные узлы, имитирующие узлы-участники скрытого обмена сообщениями. Если протокол поддерживает возможность создания новых каналов обмена сообщениями и подключения новых узлов (а это часто необходимо), то возникает ещё одна непростая задача: как отличить деятельность атакующей третьей стороны от настоящих участников обмена? Одним из вариантов решения является удостоверение ключей новых участников ключами уже работающих узлов (напоминает практику PGP). Однако, в случае скрытого протокола, процедура такого удостоверения может оказаться весьма сложной: как и где обмениваться подписями, не раскрывая факта такого обмена и идентификаторов узлов? Предположим, что скрытым протоколом хотят воспользоваться два участника, которые раньше сообщениями не обменивались – понятно, что у них, в общем случае, нет возможности отличить подставной узел от подлинного корреспондента. Можно было бы заблаговременно раздать всем некоторый общий открытый ключ, который будет удостоверять новые узлы, но в таком случае возникает центральная схема, которой все должны доверять, в том числе, новые участники. К тому же, схема неустойчива к утрате соответствующего секретного ключа (и кто его будет хранить?) Похоже, кроме варианта с предварительным распределением многих личных ключей, остаётся только офлайновый обмен (опять же, вспоминается PGP).

Несмотря на существенные сложности, хорошо скрытые протоколы можно разработать, а также и реализовать. Но они настолько отличаются от используемых сейчас, например, в распространённых мессенджерах, что быстрого внедрения ожидать не приходится.



Комментарии (1) »

Подробное описание того, как работает (на практике) технология ESNI (зашифрованное поле SNI TLS), в текущей draft-версии. В качестве примера я использую тестовый сервер tls13.1d.pw, где ESNI поддерживается (а поддерживающий браузер – это Firefox свежих версий, но ESNI там нужно включить, вместе с DNS-over-HTTPS).

Введение

ESNI требует публикации данных в DNS, а также наличия дополнительного секретного ключа на сервере (это, обычно, другой ключ, а не соответствующий TLS-сертификату). В DNS публикуется запись, содержащая публичный серверный ключ ESNI. Для получения общего секрета – используется протокол Диффи-Хеллмана (см. ниже). Кроме того, в DNS-записи публикуются: интервал валидности ключа (и других параметров) ESNI (начальная и конечная даты); тип используемого симметричного шифра (используется для зашифрования имени сервера); контрольная сумма (часть значения SHA-256); номер версии протокола и ожидаемая длина данных ESNI. DNS-запись ESNI – это только информация о ключе (или ключах – их может быть несколько) и о шифре (шифрах), это публичная информация, она не содержит скрываемого имени сервера.

TLS-сервер, поддерживающий ESNI, должен уметь распознавать расширение ESNI в составе сообщения ClientHello и обрабатывать его, проверяя корректность. Кроме того, сервер должен отправить ответную, серверную запись ESNI. В этой записи содержится копия значения, полученного в зашифрованном виде от клиента. ESNI-ответ сервера так же передаётся в зашифрованном виде.

Логика работы следующая: браузер получает из DNS открытый ключ DH (протокола Диффи-Хеллмана), генерирует сеансовый симметричный ключ, зашифровывает имя сервера и некоторое уникальное значение, которые, вместе со своим открытым ключом DH, передаёт серверу в расширении ESNI ClientHello. (Я описывал этот процесс в отдельной записке.)

Часть DNS

Сейчас в качестве DNS-записи используется запись типа TXT. Возможно, в дальнейшем для ESNI выделят новый тип записи.

Структура записей, размещаемых в DNS, следующая (в нотации языка Go):

type KeyShare struct {
Group uint16
Key []byte
}
type ESNIKeys struct {
Version uint16
Checksum [4]byte
Keys []KeyShare
Ciphers []uint16
PaddedLength uint16
NotBefore uint64
NotAfter uint64
Extensions []byte
}

Данные кодируются в Base64 и публикуются в TXT-записи под именем, содержащим префикс _esni. Для tls13.1d.pw это _esni.tls13.1d.pw. Сейчас для tls13.1d.pw опубликована ESNI-запись с двумя ключами (X25519 и P-384).

Подробные описания полей:

Version

длина: два байта; значение: 0xFF01 (действующая версия, draft).

Checksum

длина: четыре байта; это контрольная сумма – первые четыре байта значения SHA-256 от всех данны структуры ESNI (для вычисления контрольной суммы поле Checksum во входных данных заполняется нулями).

Keys

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

Рассмотрим список ключей для ESNI-записи _esni.tls13.1d.pw (здесь используется два ключа – X25519, P-384):

длина списка: 0x0089;

тип первой криптосистемы (X25519): 0x001D;
длина записи ключа (32 байта): 0x0020;
значение ключа: […] /32 байта, в криптосистеме X25519 – ключи записываются 32-байтовыми последовательностями/;

тип второй криптосистемы (P-384): 0x0018;
длина записи ключа (97 байтов): 0x0061;
значение ключа (97 байтов): 0x04[…] /ключ является точкой на кривой, записываются координаты точки, первый байт обозначает тип представления (04 – несжатое), далее – две координаты по 48 байтов: 48 + 48 +1 = 97; 48 байтов – соответствуют разрядности криптосистемы).

Ciphers

это тоже список, длина зависит от количества поддерживаемых шифров. Для tls13.1d.pw используется один шифр:

длина списка: 0x0002 (два байта – идентификатор шифра);
идентификатор шифра (AES_128_GCM): 0x1301.

PaddedLength

два байта, поле содержит максимальную длину записи списка имён в ESNI, значение, которое поддерживает сервер tls13.1d.pw: 0x0080.

NotBefore, NotAfter

всем привычные таймстемпы по восемь байтов. Интервал валидности записи.

Extensions

расширения ESNI. Они пока не определены спецификацией. Поле является списком, соответственно, пустой список представляется двумя нулевыми байтами (список, имеющий длину нуль): 0x0000.

Часть TLS

Обработка ESNI TLS-сервером, который действует на tls13.1d.pw, проводится при получении ClientHello. Это сообщение содержит список расширений, для каждого расширения указывается тип (подробнее про то, как работает TLS можно почитать в техническом описании протокола). Тип расширения ESNI – 0xFFCE (это Draft-версия!). Внутренняя логика обработки: расширения передаются в виде списка TLS-структур, длина списка задаётся первыми байтами, далее идут типы расширений и соответствующие им поля данных, каждое поле начинается байтами, содержащими длину.

Например, вот тип, описывающий клиентское расширение ESNI (представление внутри сервера):

type TLS_ESNI struct{
Cipher uint16
Group uint16
KeyShare []byte
Digest []byte
Data []byte
}

Клиент передаёт в ESNI идентификатор шифра (Cipher), тип криптосистемы (Group) и свой ключ DH (KeyShare), а также контрольную сумму (Digest) и, собственно, полезные данные (Data) – nonce и зашифрованное имя сервера. Значение nonce (16 байтов) – это то самое значение, которое сервер должен передать в ответном ESNI-расширении.

При разборе полей KeyShare, Digest, Data – используется обычный для TLS подход: первые два байта интерпретируются как длина записи, далее, в зависимости от типа, аналогичным способом разбираются поля данных.

Конкретно в моём тестовом сервере обнаружение и обработка ESNI устроены так: на первом шаге читаются поступившие TLS-записи, делается попытка разобрать их по TLS-сообщениями и построить список таких сообщений, если попытка удалась, то в списке выполняется поиск ClientHello; если удалось найти ClientHello, то для него вызывается парсер, который разбирает сообщение, выделяя “голову” и список расширений (здесь речь про все расширения, не только про ESNI); на втором шаге – делается попытка в списке расширений ClientHello найти расширения, необходимые для сессии TLS 1.3 (SupportedVersions и др.), это позволяет распознать нужную версию протокола; если минимальный набор расширений найден, то делается попытка обнаружить расширение ESNI. И, соответственно, если расширение обнаружено, то оно передаётся парсеру ESNI, который разбирает его по полям. Далее, используя секретный ключ для ESNI, сервер, выполняя алгоритм DH, вычисляет симметричный ключ и пытается расшифровать данные (поле Data). Если расшифрование завершилось успешно, то заполняется структура с данными из ESNI и генерируется ответное серверное расширение. (Отмечу, что это логика работы, подходящая именно для тестового сервера.)

Если возникли какие-либо ошибки в обработке ESNI, то, в действующей версии сервера, TLS-соединение завершается (с ошибкой уровня TLS). Здесь для ESNI получилось исключение, потому что многие другие ошибки и несоответствия сервер специально игнорирует, чтобы показать на странице результатов сведения об этих ошибках; наиболее существенный пример – это значение Finished клиента: некорректное значение, вообще говоря, является фатальной ошибкой, но тестовый сервер его игнорирует, выводя, впрочем, пометку (воспроизвести эту ситуацию довольно сложно – потребуется специальная утилита на стороне клиента). В дальнейшем я планирую сделать такую же мягкую обработку ошибок и для ESNI. Как минимум, наличие дефектного расширения ESNI не должно считаться фатальным.

Успешно расшифрованное имя из ESNI используется сервером, собственно, оно просто позже выводится на страницу результатов. Расшифрованное значение nonce – сервер записывает в ответное ESNI-сообщение. На этом обработка ESNI заканчивается. Если же расширение ESNI не обнаружено, то сервер продолжает обрабатывать соединение по обычной схеме. Все сообщения можно посмотреть на веб-странице, которую выводит сервер.

Да, кстати, из недавно добавленных улучшений на tls13.1d.pw: сейчас там выводятся симметричный ключ и вектор инициализации ESNI.



Комментировать »

Речь про Интернет. Скрытые сервисы, это замаскированные сервисы, которые “внешне” неотличимы от каких-то “обычных” сервисов, но выполняют другую функцию. Своего рода стеганография на уровне установления соединения. Хороший пример – прокси-сервер, имитирующий обычный веб-узел, доступный по HTTPS. Для обнаружения скрытого сервиса нужно знать секретный ключ. Например, для веб-узла, который является прокси, логика такая: клиент, установив соединение, передаёт специальный запрос, где указывает особое значение (для его генерации нужен секретный ключ); сервер проверяет полученное значение и, если оно соответствует коду доступа, подтверждает, что здесь есть скрытый сервис. Дальше клиент уже обращается к скрытому сервису по другому протоколу. Если же передано неверное значение, то сервер просто возвращает обычный код ошибки HTTP.

Внешний сканер, которому не известен подходящий секрет, не может отличить обычный веб-узел от веб-узла со скрытым сервисом, так как даже если сканер пытается передать некоторую имитацию запроса к скрытому сервису, в ответ он получит лишь сообщение об ошибке – одинаковое и для узла со скрытым сервисом, и для обычного веб-узла. Прикрытием, в случае HTTPS, может служить любой сайт, например, какой-нибудь тематический сайт, сайт-справочник и так далее. HTTPS удобен потому, что это один из самых распространённых протоколов, кроме того, он использует TLS, а это позволяет прозрачно провести аутентификацию узла. Есть черновик RFC, в котором описан именно этот подход.

Сходную схему можно построить и на уровне TLS (но, опять же, применительно к веб-узлу). Например, так (ниже – копия моего текста, который я некоторое время назад публиковал в Facebook).

Используем TLS, поверх которого работает HTTPS на некотором сервере: то есть, это 443/tcp и всё должно выглядеть как веб-сервер для внешнего сканера. Для упрощения, считаем, что у нас TLS 1.3, вообще, это не так важно, но 1.3 подходит лучше.

В TLS, при установлении соединения, передаются специальные сообщения. В них есть разные поля, мы будем использовать те, которые предназначены для отправки (псевдо)случайных данных – клиентская пара: ClientRandom и SessionID, серверная – ServerRandom и SessionID (значение не изменяется). Оба поля, в общем случае, содержат 32 байта данных (там есть всякие ограничения, но мы будем считать, что их нет). Цель – получить наложенный протокол, который активируется внутри TLS-трафика и только при наличии некоторых ключей, а в прочих случаях – неотличим от HTTPS/TLS. Механизм следующий.

Конфигурация: сервер, реализующий веб-узел – показывает произвольный сайт, например, трансляции новостей; клиент, планирующий использовать скрытый сервис (прокси), подключается к серверу по 443/tcp через TLS.

Клиент знает определённые ключи: общий с сервером симметричный ключ Ck (пусть, 256 бит; это ключ только для конкретного клиента – на сервере ведётся реестр ключей по клиентам, см. ниже), публичный асимметричный ключ сервера Pk (может отличаться от ключа, используемого сервером в TLS; считаем, что это та или иная криптосистема на эллиптической кривой, поэтому ключ тоже 256-битный, в совсем уж технические детали я постараюсь не вдаваться). На первом этапе клиент должен передать серверу свой идентификатор (некоторое число), который позволит серверу найти на своей стороне соответствующий секретный симметричный ключ (копию Ck).

Клиент, устанавливая TLS-соединение, использует серверный ключ Pk (он должен быть известен заранее – это важно) для получения общего секрета протокола Диффи-Хеллмана (DH), вычисленную открытую часть DH клиент отправляет в поле ClientRandom, а на основе полученного секрета – генерирует сеансовый симметричный ключ (Tk) и зашифровывает свой идентификатор, который, в зашифрованном виде, записывает в поле SessionID.

Получив TLS-сообщение, сервер вычисляет секрет DH (из ClientRandom), на его основе получает симметричный ключ и расшифровывает идентификатор из SessionID. На данном этапе сервер просто запоминает полученные значения: так как возможны атаки с повтором, сервер продолжает обработку TLS-соединения, запомнив полученные ключи и идентификатор. Очевидно, что это могут быть и не ключи вовсе. В ответном TLS-сообщении – сервер отправляет свою часть DH (от другого ключа) в ServerRandom (SessionID сервер использовать не может, потому что, согласно спецификации TLS, должен передать в ответ то же значение, которое получил от клиента).

К этому моменту, клиент уже может перейти на защищённый обмен сообщениями на уровне TLS (согласно спецификации). Клиент аутентифицирует сервер средствами TLS. Если аутентификация прошла успешно, то клиент переходит к следующему шагу получения доступа к скрытому сервису (если нет, то клиент просто закрывает TLS-сессию). На основе ответа сервера, клиент вычисляет вторую итерацию DH (это DH для скрытого сервиса) – получает второй общий секрет DH_2, используя ServerRandom, и второй открытый параметр DH_s, который нужно передать серверу (см. ниже). Итак, клиент, на настоящий момент, аутентифицировал сервер и получил следующие (дополнительные) криптографические параметры: общий секрет DH_2, сеансовый симметричный ключ Tk, общий с сервером симметричный ключ Ck. На основе этих значений, используя ту или иную хеш-функцию (например, SHA-256), клиент генерирует секретный тег (256-битное значение). Клиент соединяет это значение с параметром DH_s и записывает в начало полезной нагрузки первого TLS-сообщения, это, условно говоря, magic number. Полезная нагрузка передаётся в зашифрованном виде, поэтому третья сторона тег (magic number) – не видит.

Сервер, получив TLS-сообщение, расшифровывает его штатным образом, интерпретирует начальные данные как тег, выделяет DH_s, вычисляет общий секрет DH_2, генерирует ключи (секретный ключ Ck, соответствующий клиенту, сервер определил ранее – теперь этот ключ пригодился), вычисляет значение хеш-функции и сравнивает результат с тегом. Если значения совпали, то сервер считает, что осуществляется доступ к скрытому сервису и начинает проксировать полезные данные TLS в сторону этого сервиса (там уже есть своя аутентификация и пр.) Если тег не совпал, то сервер вспоминает, что он является веб-сервером с HTTPS и отвечает обычной (подходящей) HTTP-ошибкой, так как подставной тег выглядит как неверный HTTP-запрос.

Посмотрим, что видит третья сторона, пассивно просматривающая трафик: третья сторона видит, что установлено TLS-соединение на 443/tcp (HTTPS) и узлы обмениваются информацией по TLS, внутрь заглянуть нельзя, так что проксируемый трафик не виден. Так как, при правильно выбранной криптосистеме, значения ClientRandom, ServerRandom и SessionID вычислительно неотличимы от случайных (но при этом реально являются параметрами DH и зашифрованным идентификатором), то никаких подозрений, сами по себе, вызвать не могут.

Что видит активная третья сторона, проводящая сканирование узлов? Такой сканер не знает клиентских ключей, поэтому, даже попытка имитировать доступ к скрытому сервису, путём записи произвольных значений в поля TLS-заголовков и в данные сессии – будет приводить к ошибке HTTP (либо к ошибке TLS). Понятно, что обычное HTTPS-сканирование показывает, что там какой-то веб-сайт. Таким образом, узел, реализующий скрытый сервис, неотличим от обычного веб-узла с HTTPS. Более того, если сканеру известна часть ключей, например, открытый ключ сервера Pk, то сканер всё равно не сможет сгенерировать корректный тег, так как должен для этого знать ещё и валидный клиентский ключ. До момента появления корректного тега сервер ведёт себя в точности как HTTPS-узел, поэтому, опять же, не отличается от обычного сайта.

Активный сканер, выступающий в качестве подменного узла-сервера, в надежде, что к нему подключатся клиенты, не проходит аутентификацию средствами TLS, поэтому клиент, в общем случае, никак себя не выдаёт и никакие ключи не показывает.

Конечно, если сканеру известны все ключи, то он обнаруживает скрытый сервис, это понятно. Однако на стороне сервера может быть, во-первых, реализована ротация клиентских ключей; во-вторых, можно ещё подумать над дополнением в виде “доказательства работы” – когда клиент, для успешного соединения, должен провести тот или иной перебор, затратив вычислительную мощность: дело в том, что подлинный клиент подключается к скрытому прокси редко, а потом некоторое время использует установленную сессию; а вот сканеру нужно будет тратить немалые ресурсы на проверку произвольных узлов.



Комментарии (1) »

(Реализация шифра “Кузнечик”.)

Реализовал шифр “Кузнечик” на ассемблере, входящем в комплект компилятора языка Go. Ассемблерный вариант довольно простой и работает в 128-битных регистрах архитектуры amd64, это даёт большой прирост производительности.

“Кузнечик” из ГОСТ Р 34.12-2015 – это современный российский блочный симметричный шифр. Несколько лет назад я реализовал его на языке Go. Вариант на языке высокого уровня – не слишком быстро работает, поэтому я переписал шифр на ассемблере, для архитектуры x64/amd64. Использовал ассемблер (точнее – псевдоассемблер), встроенный в Go.

Новый вариант называется GOSThopper и использует 128-битную арифметику, доступную на современных процессорах с архитектурой x64 (далее я буду называть её amd64, именно такое обозначение использует компилятор Go). Основная идея оптимизации такая: написать быструю реализацию обработки блока в “длинных” регистрах процессора – шифр как раз использует 128-битный блок, так что разрядность хорошо подходит. В системе команд процессоров amd64 (точнее, в некотором расширении системы команд, но это детали, так как сейчас данное расширение доступно практически везде) – есть нужный набор “атомарных” инструментов: быстрая загрузка данных из памяти, XOR, сдвиги и произвольный доступ к байтам.

Тут необходимо напомнить, как операции шифра “Кузнечик” оптимизируются чисто алгебраически. Структура шифра такова, что все его преобразования можно предвычислить для значений отдельных байтов и хранить в довольно больших (64K) таблицах (если говорить строго, то это матрицы коэффициентов, но здесь я буду называть их просто таблицами). Аналогичная оптимизация известна для всех AES-подобных шифров.

После того, как таблицы подготовлены и загружены в память – работа алгоритма, реализующего шифр, сводится к выбору элементов из таблицы и сложению этих элементов при помощи операции XOR. То, какой элемент таблицы использовать, определяется значениями текущего байта входного текста, ключа и номером раунда. Элементы в таблицах – 128-битные, совпадающие по длине с блоком. То есть, при должной сноровке, алгоритм становится очень быстрым. Особенно сильно можно ускориться, если использовать регистровую арифметику подходящей разрядности, что я и попытался сделать. Ссылки на полные исходные тексты даны в конце записки. Но, фактически, весь ассемблерный код сводится к фрагменту, представленному на скриншоте (синтаксис у ассемблера Go своеобразный).

Assembly code listing screen copy.

Краткие пояснения к алгоритму (с. 19-33): выполняем сложение блока (PXOR) с ключом текущего раунда, результат помещаем в регистр X0 (так обозначаются 128-битные регистры XMMn); извлекаем младший (под нулевым номером) байт длинного регистра (PEXTRB), умножаем его на 16 (SHLQ) и складываем (ADD) с базовым адресом таблицы (он ранее загружен в регистр DX); полученное смещение (оно находится в регистре AX) теперь указывает на нужный элемент таблицы предвычисленных значений, извлекаем этот элемент и суммируем с предыдущим (PXOR со значением в X2, первый элемент последовательности просто записывается в X2 – с. 25). Написано “в лоб”, нет дополнительных проверок валидности адресов и размеров массивов (предполагается, что эти параметры контролируются снаружи данной процедуры).

Ассемблерный код выполняет только преобразование блока, а вычисление таблиц, разворачивание ключей – всё это осталось в коде на Go.

Итак, новая реализация в несколько раз быстрее предыдущих. Простой тест на процессоре Intel i5-9600K показывает скорость около 180 мегабайт в секунду для зашифрования и около 140 Мбайт/сек для расшифрования (процедура расшифрования существенно отличается от зашифрования, использует дополнительную таблицу, а кроме того, я её совсем не оптимизировал, потому что для основных современных режимов использования блочных шифров процедура расшифрования не нужна). Так или иначе, 180 мегабайт – это неплохой результат. Предыдущая версия, только на Go, но с unsafe-конструкциями, показывает на том же процессоре лишь около 45 Мбайт/сек. На небольших массивах данных – скорость ассемблерной версии ещё заметно возрастает, поскольку процессор эффективно использует кэш-память.

Как я уже не раз упомянул, это ассемблер архитектуры amd64, поэтому на других платформах, например, на различных ARM, данный ассемблерный код использовать не получится. Так что пришлось дополнить модуль “заглушками”, а точнее – реализациями шифра на “чистом Go”. Компилятор Go позволяет прозрачно генерировать межплатформенный код, для этого используются специальные директивы, в данном случае: // +build !amd64. Другими словами, в файле с исходным кодом, предназначенным для всех платформ, кроме amd64, указывается директива “// +build !amd64”, а для amd64 – код выносится в файлы с постфиксом _amd64. (Конкретно: docipher_amd64.go – содержит объявления функций; docipher_amd64.s – код на ассемблере.) Соответственно, данный модуль успешно компилируется на разных платформах, я проверил на ARM. Однако скорость работы на платформах, отличных от amd64, будет ниже на порядки (в сто раз и даже более) – это связано с тем, что используется простая реализация шифра (файл docipher.go). Но amd64 – более чем распространённая архитектура, поэтому новый быстрый шифр может оказаться полезен. (Не исключено, кстати, что возможны весьма экзотические конфигурации, когда платформа amd64 не содержит каких-то “длинных” команд, но это нужно проверять отдельно.)

Нередко спрашивают: как простым способом использовать реализацию шифра для обработки некоторого потока данных? Понятно, что непосредственно шифр применять нельзя, нужна некая обёртка, называемая “режимом использования шифра“. Для упрощения реализации примеров “из жизни” я добавил в модуль пару простых функций, реализующих зашифрование и расшифрование в режиме счётчика (CM_Encrypt(), CM_Decrypt()). Это готовый инструмент для работы с потоком данных: то есть, в качестве аргументов функции передаются вектор инициализации, ключ и слайс (массив) данных; функция возвращает обработанный слайс той же длины. Важное замечание: конкретный вектор инициализации нельзя повторно использовать для зашифрования с одним и тем же ключом; учитывайте и тот факт, что начальное значение увеличивается внутри процедуры на единицу с каждым обработанным блоком (см. исходный код).

В реализации режима счётчика нет аутентификации (это важно!). Для аутентифицированного варианта можно использовать штатный режим GCM из Go-пакета crypto/cipher. В модуле есть нужный интерфейс, поэтому шифр элементарно подключается к GCM. Примеры есть в исходном коде, а краткое описание дано ниже.

Тут необходима ещё одна оговорка: “Кузнечик” не стандартизован для применения в режиме GCM. В российских криптографических ГОСТах пока что вообще нет аналогичного режима (AEAD), но, вероятно, он вскоре появится, и это, конечно, будет не GCM, а вариант разработанного российскими специалистами режима, который сейчас называется MGM (Multilinear Galois Mode).

Логика использования в режиме счётчика:

CM_CipherText := gosthopper.CM_Encrypt(0x1234567, Key, SourceText) 
[...]
CM_PlainText := gosthopper.CM_Decrypt(0x1234567, Key, CM_CipherText)

(Здесь 0x1234567 – это вектор инициализации, начальное значение счётчика, собственно говоря. Данное значение использовано для примера, оно не является секретным, но, повторюсь, нельзя повторно использовать одно значение с тем же ключом. Важно учитывать, что значение счётчика увеличивается с каждым блоком на единицу внутри процедуры, поэтому для нового вызова с тем же ключом – начальное значение тоже должно увеличиться, как минимум, на число_блоков + 1, иначе возникнет повтор. То есть, данные функции являются только демонстраторами общего принципа, а управление инциализацией режима счётчика представляет отдельную задачу.)

Логика в режиме GCM (import “crypto/cipher”; AD – дополнительные данные, которые передаются в открытом виде):

kCipher, err := gosthopper.NewCipher(Key) 
[...]
kuznecGCM, err := cipher.NewGCM(kCipher)
[...]
GCM_sealed := kuznecGCM.Seal(nil, GCM_nonce, PT, AD)
[...]
GCM_opened, err := kuznecGCM.Open(nil, GCM_nonce, GCM_sealed, AD)

Режим GCM в пакете crypto/cipher реализован полностью, но тоже требует инициализации (GCM_nonce). В целом, GCM является более совершенным режимом, чем простой режим счётчика (собственно, GCM – это улучшенная разновидность режима счётчика).

(Обычное напоминание: я, по возможности, всё проверил, но это всего лишь экспериментальная реализация, поэтому какого-то серьёзного исследования кода этого модуля не проводилось, так что там, во-первых, могут быть ошибки; во-вторых – точно нет целого ряда вспомогательных механизмов, обеспечивающих защиту ключей в памяти и т.д., и т.п.)

Исходный код:
основной файл – gosthopper.go;
реализация шифра на ассемблере – docipher_amd64.s;
объявления функций – docipher_amd64.go;
реализация только на Go для платформ, отличных от amd64 – docipher.go.

Всё вместе в одном архиве: gosthopper1.tar.gz.

Подробные примеры использования и тесты: test_gosthopper.go

Пакет называется gosthopper. Для того, чтобы его использовать, нужно тем или иным способом разместить (например, просто скопировать) файлы с исходными кодами в директорию пакетов вашей инсталляции Go. (См. переменную окружения GOPATH.) Файл test_gosthopper.go относится к пакету main, поэтому его лучше собирать в другой директории. Внутри файлов – много дополнительных пояснений (англ.).

Вопросы, пожелания – приветствуются в комментариях или по электронной почте.



Комментарии (1) »

Про технологию ESNI (и SNI) я не так давно написал несколько записок. Сейчас ESNI находится в процессе внедрения, интересно взглянуть на эффект, который данная технология будет иметь для систем инспекции трафика и блокирования доступа. Современные системы используют SNI (а также, в продвинутых вариантах, TLS-сертификаты) для обнаружения имён узлов, с которыми пытается установить соединение пользователь. ESNI скрывает эти имена из SNI (TLS-сертификаты скрыты в новой версии TLS 1.3), причём, текущая версия ESNI использует для этого ключи, опубликованные в DNS.

То есть, особенность ESNI в том, что в качестве дополнительного источника ключей, защищающих метаинформацию, используется независимая от TLS система – DNS. Это важный момент: для того, чтобы зашифровать “адрес обращения”, клиенту не нужно устанавливать дополнительные соединения – получить нужный ключ можно типовым запросом к системе доменных имён; вообще говоря, не обязательно при этом указывать имя того TLS-узла, с которым будет соединяться клиент.

Провайдер хостинга может использовать ключи, опубликованные под одним DNS-именем, для обеспечения доступа к “скрытым серверам” под совсем другими именами, это означает, что открытый запрос в DNS не будет раскрывать имя “целевого узла”. Например, Cloudflare сейчас использует одни и те же ключи для самых разных веб-узлов. Более того, “скрытый узел” может находиться за некоторым “фронтэндом”, имеющим другое, универсальное, имя – фактически, это Domain Fronting.

В идеале, для работы ESNI нужны DNSSEC (чтобы аутентифицировать источник ключей и защитить DNS-трафик от подмены) и DNS-over-TLS (чтобы защитить DNS-трафик от пассивного прослушивания). Но и в условиях незащищённой DNS, технология ESNI довольно эффективна (отмечу, что ESNI предусматривает и вариант, в котором ключи встраиваются в приложение, либо передаются каким-то ещё способом, без DNS).

В открытой DNS, системы анализа трафика, которые видят весь трафик клиента, могут сопоставить запрос в DNS для извлечения ключа ESNI и последующее TLS-соединение. DNS-ответ с ключами даже можно заблокировать, сделав использование ESNI невозможным (но только при условии, что ключи не были получены другим способом). Однако автоматическое корректное сопоставление имени из DNS-запроса и сессии TLS – представляют серьёзную дополнительную задачу, которая тем сложнее, чем больше объём трафика, анализируемого системой фильтрации. (Конечно, уже само наличие ESNI может являться признаком подозрительного соединения.)

То есть, ESNI, в случае массового внедрения, довольно заметно повлияет на ландшафт систем инспекции трафика. А кроме того, данная технология может подстегнуть рост распространённости DNSSEC и DNS-over-TLS. Впрочем, пока что ESNI не поддерживается распространёнными веб-серверами, да и соответствующий RFC не вышел из статуса черновика.

(Как работает ESNI – можно посмотреть на моём тестовом сервере TLS 1.3, там реализована поддержка.)



Комментарии (2) »

TLS 1.3 – это новая версия протокола, вот-вот должен появиться RFC (пока что актуален черновик – draft-28). По адресу https://tls13.1d.pw/ я разместил тестовый сервер, который позволяет попробовать TLS 1.3 на практике, при помощи браузера. Поддержка протокола пока есть далеко не везде. Для сервера я полностью написал стек TLS версии 1.3 на Go, то есть, реализовал всё, что “выше TCP”, так как в стандартной библиотеке поддержки 1.3 нет. (Ну, строго говоря, криптопримитивы использованы библиотечные, а именно – шифры и алгоритмы на эллиптических кривых для протокола Диффи-Хеллмана (DH) и электронной подписи ECDSA.) Это именно тестовый сервер, поэтому он поддерживает не все возможности TLS 1.3, но базовые – поддерживает. В частности, я реализовал две draft-версии: 28 и 23. Draft-28 – должен стать RFC, а 23-й поддерживается распространёнными клиентами. (Update: c 09.2018 поддерживается и RFC-версия – 0x0304.) Сервер умеет шифры AES (с GCM) и ChaCha20 (c Poly1305). DH для сеансовых ключей и подпись – только эллиптические (возможно, RSA и “мультипликативный” DH я добавлю позже; update: c 26/01/2019 – есть поддержка “мультипликативного” DH). Кроме TLS – есть кусочек, поддерживающий HTTP-запрос GET, он позволяет использовать обычный браузер и выводит в текстовом виде подробную информацию о TLS-соединении. Понятно, что для получения этой информации TLS-соединение нужно установить. Версий TLS ниже 1.3 – тестовый сервер не поддерживает (совсем не поддерживает: всё же, это специальный сервер, я просто не стал их реализовывать, так как от 1.3 они отличаются весьма существенно).

Два самых распространённых браузера – Chrome и Firefox – уже умеют TLS 1.3 в своих самых свежих версиях. Я проверил Chrome 68 (версия draft-23 TLS) под Debian и Android 8, FireFox Quantum 62.0b14 (это бета) под Debian, а также Firefox 61 под Android 8: все эти браузеры соединяются с тестовым сервером, а FF 62 даже поддерживает draft-28 (самый свежий вариант). То есть, вы можете попробовать подключиться к тестовому серверу, если у вас актуальная версия браузера. Кроме того, 1.3 умеет утилита s_client из пакета OpenSSL версии 1.1.1-pre8, но это тоже бета-версия, которую нужно самостоятельно собирать. Все прочие типичные инструменты (wget, curl и т.д.) – скорее всего TLS 1.3 пока что не умеют (но планируют быстро добавить поддержку).

Если браузер сумел договориться с сервером, то вы увидите простую текстовую страницу (англ.) с параметрами TLS, в частности, там отображаются сеансовые ключи. Сервер использует полноценный TLS-сертификат от Comodo (с ECDSA), поэтому предупреждений о безопасности барузер показывать не должен. Если соединиться не удалось, то, скорее всего, браузер выведет ту или иную ошибку SSL/TLS. Возможны варианты, когда соединение просто сбрасывается на уровне TCP (например потому, что мой сервер не присылает фиктивное сообщение ChangeCipherSpec, и такие соединения разрывает DPI, но это технические детали, которые, впрочем, очень интересно отследить). Попробуйте: https://tls13.1d.pw.

(Сервер специально использует статический ключ DH и “не слишком случайное” значение поля Random.)

Update: кстати, если вы хорошо знакомы с TLS и интересуетесь всякими занимательными “гиковскими” штуками, то рекомендую внимательно взглянуть на открытый ключ ECDSA сервера tls13.1d.pw – этот ключ входит в состав TLS-сертификата (смотреть нужно в шестнадцатеричной записи).

Update 13/08/2018: добавил передачу сообщения ChangeCipherSpec сервером; в случае TLS 1.3 – это фиктивное сообщение, которое нужно только для того, чтобы “замаскировать” TLS-соединение под предыдущие версии, обеспечивая прохождение через промежуточные узлы с DPI и прочей фильтрацией трафика.

Update: c 28/12/2018 добавил поддержку ESNI.

English note: there is a test implementation of TLS 1.3 server (RFC 8846, draft-28,-17) with HTTPS support. Test server also supports ESNI.



Комментировать »

SNI – это Server Name Indication, поле, которое передаёт клиент на начальном этапе установления TLS-соединения. В данном поле указано имя сервера (на уровне приложений), с которым клиент планирует установить соединение. Например, при обращении к dxdt.ru по HTTPS, ваш браузер указывает в SNI строку dxdt.ru. Указание SNI нужно для того, чтобы на стороне сервера по имени различать “виртуальные хосты” (узлы), которые разделяют общий IP-адрес. Например, если у сервера есть несколько наборов TLS-сертификатов и серверных ключей для разных имён, то на основании SNI он может определить, какие сертификаты передавать в данном TLS-соединении. Сейчас, во всех версиях TLS, включая новейшую 1.3, поле SNI передаётся в открытом виде. Это означает, что прослушивающая канал связи третья сторона может определить имя узла, с которым устанавливается соединение, несмотря на то что TLS использует шифрование для защиты от прослушивания.

Вопрос зашифрования поля SNI обсуждается очень давно, но подходящего метода пока не появилось. Для этого есть много причин. Например, стороны должны прежде договориться о ключе, а так как SNI передаётся в самом начале соединения, да ещё и обозначает сервер, с которым нужно установить соединение, согласование ключа наталкивается на вполне серьёзные трудности. Так, попытка решить задачу в лоб приводит к тому, что добавляется ещё одна итерация (запрос-ответ) в протокол установления соединения: клиент и TLS-сервер сперва должны договориться о ключе для SNI, переслав пакеты в оба конца, а только потом клиент сможет сообщить, с каким же, собственно, хостом он желает соединяться. И это не единственная проблема, свою лепту вносят дополнительные вычислительные затраты, вопросы хранения ключей, и так далее.

Тем не менее, решения предлагаются. Скорее всего, значение SNI всё же спрячут. Свежий черновик (draft) RFC предлагает использовать для шифрования SNI записи DNS. Точнее, в DNS предполагается публикация открытого серверного ключа, с помощью которого клиент может зашифровать значение SNI. Такая схема позволяет клиенту сгенерировать нужный ключ заранее, сделав запрос в DNS (сопутствующие параметры публикуются там же, в одной TXT-записи), а сервер сможет расшифровать SNI непосредственно на первой итерации установления соединения (сервер знает секретный ключ). Решение весьма логичное, не требует обмена дополнительными сообщениями, кроме запроса-ответа DNS, который может быть выполнен асинхронно. Предполагается, что публикуемый в DNS ключ относится не к одному конкретному ресурсу, а к сервису, обеспечивающему размещение множества ресурсов.

Фактически, представленная в черновике схема стандартизует Domain Fronting: массовые провайдеры хостинга смогут опубликовать свои ключи для шифрования SNI в DNS, клиенты станут использовать эти ключи для доступа ко всем ресурсам, размещённым у провайдера. Удостоверение подлинности ключей – может быть выполнено в рамках DNSSEC. Ну и следует отметить, что ключи для шифрования SNI могут быть переданы не только через DNS.



Комментарии (3) »