C доверенными TLS-сертификатами для IP-адресов связан интересный аспект, касающийся проверки права управления адресом. Так как через DNS проверять право управления IP-адресом нельзя, то проверка проводится путём отправки запроса и получения ответа непосредственно и только по IP-адресу, для которого запрашивается сертификат. Конечно, нельзя забывать, что в IP-сетях всегда фактический обмен данными происходит по IP-адресам. Вот только, в зависимости от схемы проверки, разными могут быть и задействованные адреса, и физические узлы, которые адресам соответствуют. Но в схеме с подтверждением IP-адреса – адрес один, по определению. При этом свойство сети таково, что по заданному IP-адресу может ответить практически любой промежуточный узел – то есть, вовсе не тот узел, которому, как ожидается, предназначен пакет с запросом. В Интернете промежуточные узлы есть почти всегда, обычно – их много.

Это означает, что пройти проверку и получить доверенный сертификат для IP-адреса может любой промежуточный узел. Пример. Пусть проверка права управления происходит по HTTP. Проверяющий сервис направляет HTTP-запрос по IP-адресу, который указан в запросе на выпуск сертификата. Но этот HTTP-запрос обрабатывается на каком-то транзитном, промежуточном узле и этот же узел отправляет нужный ответ. Откуда узел узнал нужный ответ? Ну, например, сертификат для подмены IP-адреса был запрошен этим же узлом (возможны варианты: не обязательно заказывать сертификат с того же узла, который будет делать подмену).

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

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

Естественно, нетрудно заметить, что при HTTP-проверке промежуточный узел точно так же может подтвердить право управления и для DNS-имени, перехватив HTTP. Запросы всё равно ведь отправляются по IP-сети. Так что, если промежуточный узел умеет перехватывать HTTP-трафик, адресованный IP-узлу под проверяемым DNS-именем, то схема подмены не будет отличаться. Более того, сертификат по такой схеме будет получен для доменного имени, а это означает, что перехватывать трафик в дальнейшем, используя подменный сертификат, можно уже с другими IP-адресами. Перехват же с “IP-адресным” сертификатом – потребует продолжения подмены/перехвата части IP-маршута. Другое дело, что так как для поиска IP-адреса в случае HTTP-проверки для DNS-имени УЦ должен использовать DNS, то есть слабая надежда на то, что можно воспользоваться дополнительными мерами защиты: например, CAA-записью.

А вот проверка через DNS, то есть, с размещением кода подтверждения в DNS, – отличается. Здесь перехватывать и подменять уже нужно DNS-ответы, а они, скорее всего, проходят через другие промежуточные узлы. В идеале, для доставки кодов подтверждения через DNS используется несколько совсем разных маршрутов. Более того, если используется DNSSEC, то такая подмена DNS вообще не сработает.

Поэтому “проверка по IP”, в каком-то смысле, играет по собственным правилам, иногда оказываясь довольно слабой.



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

В Let’s Encrypt собираются внедрить (англ.) новый метод проверки права управления доменом (DCV), который, при помощи публикации специальной TXT-записи, привязывает DNS-зону к ACME-аккаунту, а не к коду валидации, и при этом такая привязка становится “постоянной” – видимо, с точностью до того, как сервер будет запрашивать TXT-записи и фиксировать факт удаления соответствующего кода. Метод называется DNS-PERSIST-01.

Довольно занятное начинание. Например, в описании от Let’s Encrypt (по ссылке выше) имеется пространная “мотивировочная часть”, где несколько раз утверждается, что, мол, “во многих конфигурациях реквизиты доступа к DNS API распределяются через “пайплайны” выпуска и обновления [сертификатов], увеличивая количество мест, в которых атакующий может их [реквизиты] скомпрометировать”. Так, конечно, делать нельзя – нельзя раздавать реквизиты доступа к DNS “пайплайнами” по многим местам – ACME этого не требует. Но забавный момент тут в другом: предлагаемая схема, предположим, уходит от “раздачи реквизитов” от DNS, но зато привязывает всю защиту и безопасность к ключу от ACME-аккаунта!

То есть, ACME-аккаунт в этой схеме строго привязывается к DNS-имени, поэтому, если атакующая сторона получила доступ к ACME-аккаунту, то доступ к DNS уже вообще не нужен. Многие ли DevOps и системные администраторы вообще знают, что в ACME-клиенте есть секретный ключ от аккаунта, которым подписываются запросы, и о том, где этот ключ хранится? Не раздаётся ли этот ключ через “пайплайны” по резервным копиям? Вопросы, впрочем, риторические. Но должно же быть очевидно, что замена одной потенциальной “точки взлома” (доступы к DNS) на единственную другую (ACME-ключ клиента) – не даёт каких-то новых преимуществ с точки зрения безопасности. Но нет – приводят в пример. (Все эти моменты, кстати, описаны в черновике RFC для DNS-PERSIST-01. Но это – и черновик, и вряд ли многие прочитают.)

ACME-аккаунт – контролируется УЦ; можно предположить, что это не важно – УЦ и так выпускает какие хочет сертификаты; однако на практике, если используется одноразовый код подтверждения через DNS, есть небольшая надежда, что модуль проверки права управления доменом – как-то отвязан от модуля авторизации ACME-запросов; в случае же нового варианта – достаточно контроля над обработкой ACME-запросов.

И если, на практике, на стороне, которая заказывает TLS-сертификаты, управление DNS ещё как-то понятно администраторам, то ACME-клиент – совсем мутная вода.

Естественно, для того, чтобы публиковать коды ACME-подтверждения в DNS, не требуется раздавать реквизиты от этой DNS по куче мест. То есть, понятно, что такое регулярно происходит на практике, тут у Let’s Encrypt всё верно написано: это, как раз, один из побочных эффектов всей этой “автоматической истории” с ACME – раскидывание CNAME и тому подобные нехорошие варианты. Но управление DNS проще реализовать правильно и надёжно, чем управление ACME-клиентом: например, машина, которая является источником обновлений DNS-зоны, может сама ходить за кодами на точку раздачи, подтверждая потом публикацию ACME-клиенту, и так далее – вариантов много. В теории, наверное, и для ACME-ключа можно придумать серьёзную защиту. Проблема тут совсем в другом: многие знают, что DNS нужно защищать, а то будут проблемы; о том, что защищать нужно ACME-аккаунт – ну, может, кто-то и подумал, но даже если и подумал, то, скорее всего, решил: “а зачем? всё равно же DNS-доступ нужен для выпуска сертификата”. И не забывайте, что доступ к DNS, действительно, всё так же позволяет выпустить TLS-сертификат, только теперь ещё и доступа только к ACME-аккаунту становится достаточно.



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

Корень глобальной DNS сейчас подписан, в рамках DNSSEC, криптосистемой RSA. В ICANN – организации, определяющей требования к работе глобальной DNS, – некоторое время назад запустили процесс перехода корневой зоны с RSA на ECDSA (P-256/SHA-256, это алгоритм номер 13 в DNSSEC). В текущей версии предложения ICANN планируется переход в течение четырёх лет. То есть, переход должен завершиться в 2030 году (традиционная дата, да).

Сейчас среди зон ниже первого уровня мало что подписано, мало где есть DNSSEC. Например, в .RU – это какие-то доли процента. Тем не менее, DNSSEC касается всех валидирующих резолверов, и если DNSSEC сломается, то сломается всё дерево ниже подписанной точки, в которой DNSSEC отвалилась. Например, так как подписана сама зона .RU, то когда в .RU сломались подписи DNSSEC, отвалились абсолютно все домены второго (и ниже) уровня внутри .RU, вне зависимости от того, есть в каком-то из этих доменов DNSSEC или нет. Ещё раз подчеркну, это касается только валидирующих резолверов, однако сейчас самые массовые публичные резолверы (Google Public DNS, Cloudflare и др.) – валидирующие. Да и вообще – в 20-х годах 21 века нужно использовать только валидирующий резолвер. Соответственно, если отвалится DNSSEC в корневой зоне – DNS сломается, фактически, для всего Интернета.

DNSSEC в корне DNS полностью внедрили в 2010 году, естественно, на RSA. Так как при вводе DNSSEC в эксплуатацию никто не озаботился тем, чтобы не только согласовать процесс ротации корневого ключа, но и хотя бы предусмотреть такой процесс, замена корневого ключа состоялась только в 2018 году. Впрочем, вряд ли это как-то повлияло на практическую стойкость: практические возможности по взлому RSA-ключей из подписей – сильно расходятся с теоретическими предположениями. А тем более эффект сомнителен, если учитывать реальность использования DNS и DNSSEC и схему подписывания самой корневой зоны. Для подписи зоны используются дополнительные ключи, которые, в свою очередь, удостоверяются корневым. Процесс генерирования новых ключей подписи зоны и удостоверения этих ключей изначально предполагал серьёзные шаги по обеспечению безопасности криптографических данных: защищённые помещения, доверенные люди, физические сейфы, физические ключи, аппаратные модули (HSM) и так далее.

Однако в 2020 году вообще просто взяли и нагенерировали и подписали доверенные ключи для корневой зоны заранее, на будущее, сделав участие “криптоофицеров” дистанционным (да! это через эти же “подписываемые интернеты”), чтобы лишний раз не собираться – после этого уже стала окончательно понятна вся театральность действий по “защите” корня DNS, включающая посещение хранилищ и не менее церемониальную работу с аппаратными модулям при помощи отвёртки и молотка – и это не преувеличение. Ритуалы и уровень их исполнения – выходят на первый план, да. Прямо из ICANN и корня DNS. Театр, как социальное явление, очень важен. Главное, понимать, где именно театральное действо переходит в цирковое.

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

Самое занятное, что в рамках перехода на ECDSA имеющиеся ключи RSA собираются сократить. Да. Собираются уменьшить их разрядность с 2048 бит до 1536 бит. Как бы, по нынешним временам, 1536 бит не считаются стойким вариантом. (Заметьте, впрочем, что для атаки на гипотетическом-фантастическом квантовом компьютере – 1536 битов RSA всё равно сильнее, чем 256 бит ECDSA, потому что теоретические алгоритмы квантового взлома прямо оперируют разрядностью, и для записи 256 бит может потребоваться меньше кубитов.) Естественно, снижение разрядности RSA обусловлено стремлением удерживать максимальный размер DNS-ответа ниже границы, доступной в UDP-транспорте: ниже 1232 байтов. Это позволяет избежать перехода на TCP со стороны резолверов. (DNS требует перехода на TCP в тех случаях, когда ответы не проходят по размеру в UDP.)

Согласно плану, уменьшение длины RSA-ключа в корне DNS намечено на 2027 год. А в первой четверти 2028 года запланирована публикация ECDSA-подписей. При этом, сами ECDSA-ключи в зоне публиковать сразу не собираются. Тоже занятное решение, обусловленное, скорее всего, тем, что DNSSEC требует подписывать зону всеми обозначенными криптосистемами, но не всеми ключами. Поэтому ключи опубликуют позже, во второй четверти 2027 года, когда ECDSA-подписи уже побывают в зоне. RSA-ключи отзовут в 2030. Если по плану. Потому что документ предварительный и пока что выставлен для публичного обсуждения.



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

В DNS есть несколько штатных способов создать циклы, которые сделают обычный рекурсивный опрос невозможным. Самый привычный способ – использование субординатных NS. То есть, пусть зона example.com делегирована на два авторитативных NS – ns1.example.com, ns2.example.com. Оба имени находятся в исходной зоне example.com, откуда и название “субординатные”. Чтобы найти A-запись в зоне example.com – резолверу нужно опросить хотя бы один из авторитативных NS-ов, а чтобы опросить NS, нужно знать его IP-адрес (A- или AAAA-запись), а чтобы узнать IP-адрес – нужно опросить авторитативный NS зоны example.com, ну и так далее.

Если бы не glue-записи с IP-адресами, то разрешить такой цикл было бы невозможно. Glue-записи – это специальный способ предоставить резолверу IP-адреса узлов, которые, из-за циклов в адресации, невозможно найти обычным рекурсивным опросом. Эти записи передаются авторитативными серверами в специальной секции DNS-ответа и, в случае примера выше, сразу содержат IP-адреса для имён ns1.example.com, ns2.example.com. Без glue-записей практическая DNS работать не будет. И не только по причине наличия только что описанного элементарного цикла: есть случаи и посложнее.

Один из таких случаев – “кросс-делегирование”. Предположим, что зона example.net делегирована на ns1.example.com, ns2.example.com. Обратите внимание: из зоны .net – делегируется на имена в .com. При этом зона example.com – делегирована на ns1.example.net, ns2.example.net.

Теперь предположим, что резолвер ищет A-запись для example.com. Как резолвер мог бы действовать:

(0) для обнаружения нужной A-записи резолверу необходимо найти авторитативные серверы зоны example.com;

(1) на авторитативных серверах делегирующей зоны .com резолвер узнаёт, что NS для example.com – ns1.example.net, ns2.example.net;

(2) теперь резолвер должен определить A-записи для этих имён серверов (ns1.example.net, ns2.example.net);

(3) но для этого нужно найти авторитативные серверы зоны example.net;

(4) на серверах делегирующей зоны .net резолвер узнаёт, что имена этих серверов – ns1.example.com, ns2.example.com;

(5) теперь, чтобы узнать A-записи ns1.example.com, ns2.example.com, резолверу нужно определить авторитативные серверы зоны example.com, но это – шаг (1); получился цикл.

Естественно, настраивать так зоны, мягко говоря, не очень хорошо, даже несмотря на то, что данный цикл разрешается с использованием glue-записей. В случае использования glue-записей, их должна предоставлять каждая делегирующая зона, но в составе этих записей уже будут имена из другой зоны. То есть, в случае простого примера с субординатными NS для example.com – glue-записи содержат адреса серверов внутри example.com (ns1, ns2); в случае “кросс-делегирования” – адреса уже будут для имён в example.net.

Это далеко не все варианты создания циклов. Есть и ещё более сложное “петлевое” делегирование, и постоянно неверно используемая запись CNAME. Что касается CNAME, то это самая проблемная запись в DNS. CNAME предназначена для перезапуска процесса рекурсивного опроса. Вдумайтесь: в DNS изначально заложен сигнал, который, при штатном использовании, заставляет резолвер “начать заново”. Можно ли предложить лучшую основу для DoS? Вряд ли. Потому что если резолвер, отправив запрос про test.ru, получил CNAME с указанием на example.com, то он должен “забыть” исходное test.ru и начать опрос DNS для example.com. И так далее. CNAME можно указать для любого имени. Стоит ли говорить, что нетрудно построить цикл из нескольких CNAME? Нет, не стоит – это очевидно, а такой цикл может быть сколь угодно сложным.

Естественно, DNS-резолверы вынуждены отслеживать все эти особенности, чтобы не зависнуть.



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

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

Администраторы настраивают DNS “как получилось”, с ошибками и дефектами – указаны IP-адреса вместо хостнеймов при делегировании, указаны лишние NS, серверы имён не отвечают на запросы SOA и т.д., и т.п. Однако система как-то работает. Только вот попытка использовать эти настройки в более или менее строгом варианте, – например, для проведения проверки права управления доменом (DCV), – наталкивается на эти ошибки и проверка не срабатывает. Потому что есть случаи, когда игнорировать дефекты настройки DNS нельзя. Обнаружение этих “дефектов”, например, может свидетельствовать об идущей атаке.

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

Проблема эта, конечно, не только DNS касается.



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

(Это дополненная и скорректированная версия статьи, которую я некоторое время назад публиковал на “Хабре”.)

Данные в запросах и ответах классической DNS никак не защищены, передаются в открытом виде. DNS-over-TLS (DoT, RFC 7858) предоставляет один из инструментов защиты информации, а именно: защиту DNS-запросов и DNS-ответов от прослушивания на промежуточных узлах.

DoT использует TLS для зашифрования DNS-транзакций, передаваемых между узлами, но не защищает сами DNS-данные. Под “DNS-данными” тут подразумевается состав ответов и запросов DNS: имена и записи. То есть, если ваш локальный компьютер использует DoT для работы с DNS-сервером, то передаваемые DNS-данные не видны “на транзите” (как объясняется ниже, сам факт обмена DNS-данными обычно всё равно виден) и нельзя простым способом узнать, для какого узла и какие DNS-запросы направлялись. Однако, если DNS-сервер, к которому запросы отправлялись с защитой DoT, возвращает неверные, искажённые DNS-данные, – например, подставляет собственный IP-адрес для, условно, example.com, – то DoT ничего с этим поделать не может – тут уже нужно использовать DNSSEC.

На всякий случай, замечу: DoT – это технология, которая никак не связана с DNS-over-HTTPS (DoH). Архитектура DoT довольно логичная: TLS здесь используется в качестве инструмента создания защищённого “сокета” между DNS-клиентом и DNS-сервером. Через “сокет” передаются те же DNS-запросы/DNS-ответы, как они передавались бы на уровне обычного UDP или TCP. То есть “сокет” с TLS тут нужно считать туннелем, работа которого прозрачна для уровня DNS. Естественно, чтобы использовать DoT в таком прозрачном режиме, нужна поддержка TLS и на клиенте, и на сервере. Однако, поскольку спецификация отводит TLS обособленный уровень (в отличие от DoH), ничто не мешает поднять TLS-соединение между узлами какими-то другим способом, установив тем самым туннель, а запросы/ответы от неподдерживающих TLS клиентов/серверов перенаправлять без изменений через этот туннель.

Вспомним самые базовые свойства DNS как сервиса поиска данных. Эти свойства важны для понимания места DoT в уже построенной инфраструктуре. DNS – сложная система, работающая по сложным и, что называется, “развесистым” протоколам (простой эта система только кажется; в чём, надеюсь, можно убедиться даже по результатам чтения данной небольшой статьи). Внедрение TLS в DNS заметно усложняет архитектуру, но конкретно DoT позволяет внести изменения почти оптимальным образом.

Типовой сценарий работы DNS это так называемый рекурсивный опрос, в котором специальный DNS-сервер (резолвер) производит обход других DNS-серверов (которые называются “авторитативными”), следуя по веткам дерева делегирования с целью поиска данных, соответствующих некоторому ключу. Хрестоматийный пример: в качестве ключа выступает имя хоста (example.com), а целевыми данными является IP-адрес (значение адресной A-записи), то есть, “хотим найти IP-адрес для имени сервера”. DNS правильно рассматривать именно как специальную распределённую базу данных и сервис поиска в этой базе данных. Собственно, сама аббревиатура DNS может расшифровываться двумя способами: Domain Name System и Domain Name Service. DoT – относится к транспорту для сервиса поиска и позволяет защитить трафик каждого DNS-запроса на каждом отрезке. (А криптографическая система на уровне базы данных DNS – это как раз DNSSEC, но DNSSEC не зашифровывает данные.)

Рекурсивный DNS-резолвер, если подходящий ответ отсутствует в кеше, обращается к разным авторитативным серверам Интернета по некоторому довольно сложному алгоритму и, при штатной работе системы, либо получает от сервера нужный ответ о целевом имени, либо получает так называемый делегирующий ответ, который содержит имена других авторитативных серверов и позволяет резолверу продолжить поиск, обращаясь уже к каким-то из этих серверов (откуда и появляется “рекурсия” в названии процесса). При этом рекурсивный резолвер обычно находится за пределами “локальной машины”. В качестве рекурсивного резолвера может выступать сервер провайдера интернет-доступа или публичный сервис типа Google Public DNS 8.8.8.8. На локальной машине DNS-запросы обрабатывает более простая программа – stub-резолвер, который только перенаправляет запросы рекурсивному резолверу (иногда его ещё называют “рекурсор”) и принимает от него ответы. DoT может использоваться на любом участке работы DNS: не только на “последней миле”, то есть на отрезке от локального stub-резолвера к рекурсивному резолверу, но и между авторитативными серверами. Кстати, эта “последняя миля” в современных браузерах как раз часто защищена при помощи DNS-over-HTTPS.

Итак, общая логика работы DoT следующая:

1) произвольный DNS-клиент, если он поддерживает TLS, устанавливает TLS-соединение, подключившись по TCP к DNS-серверу (для UDP есть отдельный вариант – DNS-over-DTLS, здесь можно считать, что он работает так же, как и DoT);
2) DNS-клиент может провести аутентификацию сервера различными способами (см. ниже);
3) если TLS-соединение установлено, то DNS-клиент переходит к отправке DNS-запроса обычным способом, но уже через TLS-соединение;
4) ответ DNS-сервера доставляется в рамках того же TLS-соединения; при этом формат DNS-ответа и прочие свойства – не изменяются, если сравнивать с работой DNS непосредственно по UDP или TCP, без TLS-туннеля (см. ниже);
5) TLS-соединение закрывается, если клиент не планирует использовать его далее.

Для DoT выделен номер порта 853 (классический номер порта DNS – 53). Поэтому клиенты могут сразу пробовать использовать DoT, подключаясь по TCP на номере порта 853. Так как DoT – универсальная спецификация, то такое подключение можно устанавливать и к авторитативным серверам. Но сейчас поддержка DoT на авторитативных серверах является большой редкостью. Тем не менее, DoT поддерживают, например, серверы DNS-зон facebook.com и wikimedia.org.

Практика

Воспользуемся утилитой dig из пакета BIND и посмотрим, как DoT работает вживую на авторитативных серверах. В качестве источника примеров используем зону wikimedia.org, авторитативные серверы имён которой поддерживают DoT.

Утилита dig – это один из типовых инструментов из области DNS. В более или менее современной версии dig умеет в DoT сразу из коробки: нужно просто указывать опцию +tls при вызове. Я здесь использую версию 9.18.33-1 под Raspberry Pi OS (Debian 12).

Сначала определим, на какие серверы делегирована зона wikimedia.org (пока что без DoT), это делается запросом NS-записей (-t NS):

$ dig -t NS +short wikimedia.org
ns1.wikimedia.org.
ns2.wikimedia.org.
ns0.wikimedia.org.

(+short здесь – это краткий формат вывода.)

Наша цель – отправить запрос AAAA-записи (IPv6-адрес) через DoT и получить ответ, посмотрев, с помощью tshark, что происходит в трафике. Кроме того, мы извлечём серверный TLS-сертификат с авторитативного сервера и глянем, что там, в сертификате, написано. Будем использовать авторитативный сервер ns1.wikimedia.org. Однако сначала определим его IPv4-адрес, чтобы DNS-запросы отправлять непосредственно серверу.

$ dig -t A +short ns1.wikimedia.org.
208.80.153.231

Запросили A-запись – получили IPv4-адрес в ответ. Теперь включаем DoT (и тут вообще будет опций побольше, но все они объяснены ниже):

$  dig -t AAAA @208.80.153.231 +tls +nocookie +norec wikimedia.org

; <<>> DiG 9.18.33-1~deb12u2-Debian <<>> -t AAAA @208.80.153.231 +tls +nocookie +norec wikimedia.org
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44438
;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; TCP KEEPALIVE: 157.0 secs
; PAD: (388 bytes)
;; QUESTION SECTION:
;wikimedia.org.			IN	AAAA

;; ANSWER SECTION:
wikimedia.org.		300	IN	AAAA	2a02:ec80:300:ed1a::1

;; Query time: 300 msec
;; SERVER: 208.80.153.231#853(208.80.153.231) (TLS)
;; WHEN: Sun Mar 16 18:18:01 MSK 2025
;; MSG SIZE  rcvd: 468

(Опции: +tls - используем DoT, +nocookie - без куки-меток, чтобы не перегружать вывод (DNS-куки не относятся к DoT и тут не рассматриваются: про них планирую отдельную записку опубликовать), +norec - не устанавливаем флаг рекурсии.)

Здесь уже в нижнем информационном блоке видно, что запрос и ответ передавались с использованием TLS, через TCP-соединение на номере порта 853. То есть, это DoT.
Дополнительно подтвердить, что трафик ходил через TLS, можно при помощи tshark, посмотрев в дамп, записанный tcpdump (это два основных инструмента).

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

# tcpdump -i eth0 -w pcap-dns.pcap

Парсинг дампа при помощи tshark:

$ tshark -r pcap-dns.pcap -o tls.keylog_file:keylog.log -O tls,dns -S "-----PACKET-----" -x

Здесь: -r - входной PCAP-файл; -o tls.keylog_file:keylog.log - это импорт сессионных ключей TLS, они потребуются для раскрытия DNS-трафика (про способ экспорта кратко рассказано ниже); -O tls,dns - перечень протоколов для парсера; -S "..." - разделитель "пакетов" в распечатке дампа, можно использовать любую удобную строку; -x - выводить hex-дамп тоже.

В распечатке дампа находим примерно следующее:

Internet Protocol Version 4, Src: 192.168.1.13, Dst: 208.80.153.231
Transmission Control Protocol, Src Port: 36461, Dst Port: 853, Seq: 1, Ack: 1, Len: 303
Transport Layer Security
    TLSv1 Record Layer: Handshake Protocol: Client Hello
        Content Type: Handshake (22)
        Version: TLS 1.0 (0x0301)
        Length: 298
        Handshake Protocol: Client Hello
            Handshake Type: Client Hello (1)
            Length: 294
            Version: TLS 1.2 (0x0303)

Это часть сообщения ClientHello, направленного утилитой dig авторитативному DNS-серверу. Соответствующее серверное сообщение ServerHello, которое можно найти в дампе, вместе с возможностью расшифровывать трафик, означает, что узлы успешно установили соединение TLS 1.3. Внутри TLS-соединения передан DNS-запрос и получен DNS-ответ.

Чтобы посмотреть прикладной трафик внутри TLS, нужно экспортировать сессионные ключи - то есть, симметричные секреты, которые стороны использовали для зашифрования трафика. Конкретный способ экспорта зависит от параметров сборки dig и системного окружения, но обычно достаточно установить переменную окружения SSLKEYLOGFILE, чтобы dig, при использовании TLS, выводила ключи в файл или в STDERR (откуда их можно точно так же скопировать). Механика экспорта TLS-ключей не относится к теме данной статьи, поэтому детали остаются за скобками. А вот выдачу tshark для расшифрованного DNS-трафика - посмотрим. Запрос:

Transport Layer Security
    TLSv1.3 Record Layer: Application Data Protocol: Domain Name System
        Opaque Type: Application Data (23)
        Version: TLS 1.2 (0x0303)
        Length: 61
        [Content Type: Application Data (23)]
        Encrypted Application Data: [...]
        [Application Data Protocol: Domain Name System]
Domain Name System (query)
    Length: 42
    Transaction ID: 0xad96
    Flags: 0x0020 Standard query
        0... .... .... .... = Response: Message is a query
        .000 0... .... .... = Opcode: Standard query (0)
        .... ..0. .... .... = Truncated: Message is not truncated
        .... ...0 .... .... = Recursion desired: Don't do query recursively
        .... .... .0.. .... = Z: reserved (0)
        .... .... ..1. .... = AD bit: Set
        .... .... ...0 .... = Non-authenticated data: Unacceptable
    Questions: 1
    Answer RRs: 0
    Authority RRs: 0
    Additional RRs: 1
    Queries
        wikimedia.org: type AAAA, class IN
            Name: wikimedia.org
            [Name Length: 13]
            [Label Count: 2]
            Type: AAAA (IPv6 Address) (28)
            Class: IN (0x0001)
[...]

Здесь и далее часть данных (опции EDNS и пр.) удалена, чтобы не слишком растягивать распечатку. В блоке Flags можно видеть параметры запроса, как его сформировала утилита dig. А в блоке Queries (сам запрос) - имя wikimedia.org. и состав запроса IN AAAA. Обратите внимание на поле [Application Data Protocol: Domain Name System]. Вообще, то, что TLS-сессия используется для DoT, видно не только по номеру порта, но и по идентификатору протокола уровня приложений, который dig передаёт в составе начального TLS-сообщения ClientHello. Выглядит это вот так:

Extension: application_layer_protocol_negotiation (len=6)
  Type: application_layer_protocol_negotiation (16)
   Length: 6
    ALPN Extension Length: 4
    ALPN Protocol
     ALPN string length: 3
     ALPN Next Protocol: dot

ALPN - это Application Layer Protocol Negotiation, расширение ClientHello, которое позволяет сразу сообщить серверу, какой именно прикладной протокол будет использован поверх этого соединения. Для DoT зарезервирован идентификатор 0x646F74 (то есть, строка "dot" в ASCII). Расширение передаётся в открытом виде, поэтому системе, инспектирующей трафик, нетрудно классифицировать TLS-сессию как сессию DNS: номер порта и идентификатор протокола - этого вполне достаточно. Понятно, что ни TLS, ни DoT и не ставят своей целью сокрытие факта соединения.

Просматривая выдачу tshark далее, находим DNS-ответ:

Transport Layer Security
    TLSv1.3 Record Layer: Application Data Protocol: Domain Name System
        Opaque Type: Application Data (23)
        Version: TLS 1.2 (0x0303)
        Length: 487
        [Content Type: Application Data (23)]
        Encrypted Application Data: [...]
        [Application Data Protocol: Domain Name System]
Domain Name System (response)
    Length: 468
    Transaction ID: 0xad96
    Flags: 0x8400 Standard query response, No error
        1... .... .... .... = Response: Message is a response
        .000 0... .... .... = Opcode: Standard query (0)
        .... .1.. .... .... = Authoritative: Server is an authority for domain
        .... ..0. .... .... = Truncated: Message is not truncated
        .... ...0 .... .... = Recursion desired: Don't do query recursively
        .... .... 0... .... = Recursion available: Server can't do recursive queries
        .... .... .0.. .... = Z: reserved (0)
        .... .... ..0. .... = Answer authenticated: Answer/authority portion was not authenticated by the server
        .... .... ...0 .... = Non-authenticated data: Unacceptable
        .... .... .... 0000 = Reply code: No error (0)
    Questions: 1
    Answer RRs: 1
    Authority RRs: 0
    Additional RRs: 1
    Queries
        wikimedia.org: type AAAA, class IN
            Name: wikimedia.org
            [Name Length: 13]
            [Label Count: 2]
            Type: AAAA (IPv6 Address) (28)
            Class: IN (0x0001)
    Answers
        wikimedia.org: type AAAA, class IN, addr 2a02:ec80:300:ed1a::1
            Name: wikimedia.org
            Type: AAAA (IPv6 Address) (28)
            Class: IN (0x0001)
            Time to live: 300 (5 minutes)
            Data length: 16
            AAAA Address: 2a02:ec80:300:ed1a::1
    Additional records
        : type OPT
            Name: 
            Type: OPT (41)
[...]
            Option: EDNS TCP Keepalive
                Option Code: EDNS TCP Keepalive (11)
                Option Length: 2
                Option Data: 0622
                Timeout: 1570
            Option: PADDING
                Option Code: PADDING (12)
                Option Length: 388
                Option Data: 00…
                Padding: 00…

Это исходник DNS-ответа, который был отображён dig (см. распечатку выше). Обратите внимание на секцию "опций", вот её копия из выдачи dig:

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; TCP KEEPALIVE: 157.0 secs
; PAD: (388 bytes)

Здесь мы, например, без труда видим, что параметр TCP Keepalive имеет значение 157 секунд, как и в распечатке tshark, а дополнение (padding) совпадает по длине (388 октетов). Как нетрудно догадаться по названию, TCP Keepalive содержит указание на интервал времени, в течение которого сервер (в данном случае) готов поддерживать контекст TCP-соединения, чтобы клиент мог реализовать конвейеризацию запросов. К DoT это относится весьма косвенно, но зато служит прекрасным примером реальной сложности современных DNS-протоколов (про Keepalive в DNS тоже можно написать отдельную статью, как ни странно).

А вот дополнение (padding) к DoT относится в большей степени, чем Keepalive, хоть и так же находится за пределами конкретно DoT. Идея использования дополнения здесь в том, чтобы можно было изменять длину блоков данных, содержащих DNS-транзакции, не влияя на сами транзакции. Если длину блоков выравнивать, то это позволяет сделать семантически разные блоки неразличимыми по длине. Что, кстати, особенно важно, если блоки зашифрованы. Проще говоря, наблюдая трафик, состоящий из разных по длине блоков данных, где длина определяется форматом, можно выстроить корреляцию с конкретными DNS-запросами и ответами по порядку их следования. Ведь параметры известны: длина доменного имени, длина "флагов" и заголовков, количество DNS-записей в ответах и т.д. Это позволит сделать предположения о составе наблюдаемого трафика, даже без раскрытия самого его содержания. Если же вы наблюдаете только ровный конвейер блоков одинаковой длины, в стиле "запрос-ответ, всё в одинаковых коробках", то извлечь дополнительную информацию гораздо сложнее. Естественно, можно при помощи дополнения варьировать длину и так, чтобы, напротив, блоки не были одинаковыми по количеству байтов. Заметьте, что аналогичный механизм выравнивания длины записей с помощью дополнения есть и в TLS 1.3.

TLS-сессия использует серверный сертификат. Для авторитативного сервера ns1.wikimedia.org. TLS-сертификат можно получить из дампа трафика (но, так как там TLS 1.3, придётся расшифровать отдельно), а можно воспользоваться утилитой s_client из OpenSSL: TLS в DoT точно такой же, как и в других случаях, так что s_client сработает прекрасно, нужно лишь подключиться по номеру порта 853, вот так:

$ openssl s_client -connect 208.80.153.231:853

Cертификаты в TLS нужны для аутентификации узлов, то есть для установления подлинности этих узлов. Однако в DoT с сертификатами связаны свои особенности.

Во-первых, спецификация не указывает методов аутентификации, которые обязательно должны использоваться. Основное предназначение TLS-сертификата в том, чтобы привязать открытый ключ сервера к сетевому илмени или адресу. Однако сам TLS-сертификат далеко не всегда полезен для DNS: например, можно ли требовать, чтобы имя авторитативного сервера соответствовало имени в сертификате (как это делается для веба)? В DNS сложно строго сопоставить имена серверов: конкретный контекст DNS-запроса никакого имени сервера не предусматривает - запрос отправляется по IP-адресу. Да, можно наследовать имя из предыдущих запросов и ответов, но получится не самый строгий результат. Поэтому имя из сертификата можно использовать, а можно и игнорировать. Зато TLS-сертификаты для IP-адресов подошли бы неплохо.

Технически, ничто не мешает выпустить сертфикат для IP-адреса. Если говорить про имеющиеся "хорошо известные УЦ", то сертификаты для IP-адресов пока что выпускают неохотно, так как есть трудности с надёжной проверкой права управления адресом, а также с сопровождением. Впрочем, Let's Encrypt обещают довольно скоро сделать автоматические короткоживущие TLS-сертификаты на IP-адреса для всех, используя механизм подтверждения управления адресом чисто по TLS. Это должно позволить в прозрачном режиме привязать ACME-проверку к DoT, не поднимая ненужного, в данном случае, веб-сервера.

Во-вторых, в DoT можно применять дополнительные методы аутентификации, которые не связаны с привычной по вебу инфраструктурой УЦ: например, проверять отпечаток серверного ключа подписи, а не сверять подписи в сертификатах.

В-третьих, DoT использует "приспособительный" подход и к аутентификации, и к TLS в целом. Это то, что в англоязычной традиции называется Opportunistic Security - "если удалось, то обязательно будем аутентифицировать и зашифровывать, а если нет - тогда ладно".

Может показаться, что если серверный сертификат не проверять, то от TLS для DNS нет никакого толку: промежуточный узел может перехватить соединение и подставить свой, произвольный сертификат, подходящий по формату. Однако в случае DoT схема, как минимум, всегда защищает от пассивного прослушивания трафика. Пассивное прослушивание реализовать гораздо проще, чем активный перехват, а защита от утечек через "пассивные" каналы и является основной для DoT. Тем более, если речь идёт о работе с авторитативными DNS-серверами. Защита же от активной атаки может быть реализована на "последней миле", где клиенту, обычно, заранее известны некоторые данные о сервере: имя, ключ и так далее.

Кстати, посмотрим на имена и на интервал валидности сертификата, который вернул сервер ns1.wikimedia.org.:

        Serial Number:
            05:f7:71:aa:08:8e:32:88:0a:71:9c:2d:f3:98:17:e1:d6:aa
        Signature Algorithm: ecdsa-with-SHA384
        Issuer: C = US, O = Let's Encrypt, CN = E5
        Validity
            Not Before: Mar 16 05:02:56 2025 GMT
            Not After : Jun 14 05:02:55 2025 GMT
        Subject: CN = ns0.wikimedia.org

[...]
            X509v3 Subject Alternative Name: 
                DNS:ns0.wikimedia.org, DNS:ns1.wikimedia.org, DNS:ns2.wikimedia.org

Это сертификат, выпущенный Let's Encrypt. Subject пропускаем, смотрим в Subject Alternative Name, где указаны все три имени для трёх авторитативных серверов зоны wikimedia.org., которые мы нашли в самом начале этого экскурса в DoT силами dig.

DoT уже поддерживается основными программными пакетами авторитативных серверов и рекурсивных резолверов. Например, BIND и Unbound. Так что протокол можно внедрять не только на "последней миле", но и на авторитативных серверах, защищая трафик резолверов к этим серверам (и обратно).

Подведём итог. DoT, используя TLS, защищает данные от пассивного прослушивания третьей стороной. Если клиент применяет тот или иной метод аутентификации сервера, то DoT может защитить от активного перехвата и подмены данных. Но всё это работает только на том отрезке, где применяется DoT, а защита TLS не распространяется на сам состав DNS-данных. Чтобы защитить содержательную часть DNS, необходимо использовать DNSSEC, а эту технологию тоже можно изучать при помощи dig на практических DNS-зонах.



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

Провайдер Timeweb, как я заметил, включил поддержку DNS over TLS на двух (как минимум) из своих массовых авторитативных серверов имён (NS): ns2.timeweb.ru., ns4.timeweb.org. Это означает, что резолверы могут использовать TLS при подключении к этим серверам, что автоматом делает DNS over TLS (DoT) довольно распространённой в Рунете технологией, если считать по зонам, так как упомянутые NS использует больше двух с половиной сотен тысяч DNS-зон в .RU.

DoT полезно внедрять на авторитативных серверах. Это защищает трафик резолвера при рекурсивном опросе. Подробно про DoT я недавно писал на “Хабре”.

Скриншот отчёта сервиса проверки настроек DNS:
Screenshot



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

Можно ли “смешать” TLS-сертификаты для IP-адресов и для хостнеймов? Например, если говорить о TLS для HTTPS, то тут используются и адреса, и имена: поиск сайта, обычно, происходит по имени хоста (по доменному имени), но само соединение устанавливается по IP-адресу. Соответственно, TLS-клиент, – пусть это будет браузер, – на момент отправки первого TLS-сообщения серверу знает доменное имя и IP-адрес, который сам браузер поставил в соответствие этому имени (в процессе обнаружения адреса использовалась DNS, это понятно). Обычно, чтобы признать сертификат валидным, браузер ожидает, что в TLS-сертификате указано имя, соответствующее ожидаемому имени хоста – это может быть одно из нескольких имён в сертификате, может быть результат “раскрытия” wildcard-имени (“со звёздочкой”).

Технически, в TLS-сертификате, вместе с именами хостов, можно указать и IP-адреса, форматом допускается. За примерами не нужно далеко ходить – сертификат на веб-сервере dns.google содержит и DNS-имена, и IP-адреса:

X509v3 Subject Alternative Name: 
 DNS:dns.google, DNS:dns.google.com, DNS:*.dns.google.com,
 DNS:8888.google, DNS:dns64.dns.google,
 IP Address:8.8.8.8, IP Address:8.8.4.4,
 IP Address:2001:4860:4860:0:0:0:0:8888,
 IP Address:2001:4860:4860:0:0:0:0:8844,
 IP Address:2001:4860:4860:0:0:0:0:6464,
 IP Address:2001:4860:4860:0:0:0:0:64

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

Однако в теории тот же браузер может потребовать, чтобы в сертификате и имя хоста совпадало, и IP-адрес, по которому установлено TCP-подключение. Двойная проверка.

Чем такая схема, если бы её реализовать, грозит? С одной стороны, схема неожиданным образом защищает от использования секретного ключа другим сервером, если таковой сервер использует другой IP-адрес. Подключение к подставному серверу можно реализовать подменой DNS, так что имя – совпадёт. Но не IP-адрес. Может ли атакующий, вооружённый секретным серверным ключом, соответствующим ключу в сертификате, подменить и IP-узел? То есть, сделать так, чтобы перехватывающий, подменный узел стал доступен для атакуемого клиента по тому же IP-адресу, который указан в сертификате? Как ни странно, не факт – владение секретным ключом от сертификата никак не помогает в решении сетевой задачи подмены IP-узлов. При этом, если в сертификате сверяется только имя хоста, то атака сработает уже и при подмене DNS. С другой стороны, тот, кто может подменить IP-узел и DNS, может попытаться выпустить сертификат для этих реквизитов. Однако такая подмена уже потребует атаки на системы УЦ, а не на обычного клиента веб-узла.

На одном IP-адресе может размещаться большое количество веб-узлов с разными именами. Но это не является препятствием для того, чтобы вписать для всех этих узлов один и тот же IP-адрес в сертификат. Сертификат может быть выпущен только для IP-адреса. Например, такие сертификаты обещает даже Let’s Encrypt. Но если обращение к узлу происходит в контексте, где нет DNS-имён, то можно использовать сертификат только с IP-адресом, и если бы был возможен контекст, в котором есть только DNS-имя, то сгодился бы сертификат только с именем, как сейчас. Так что тут проблемы нет. Тем более, если сертификаты начинают выпускаться всего на несколько дней.

Проблемы возникнут при попытке замены IP-адресов в DNS – нужно будет согласовывать такую замену с выпуском новых сертификатов. IP-адреса могут выбираться из большого пула и, конечно, строго привязывать их при помощи TLS-сертификата и к DNS-имени, и к серверу на уровне приложения не очень-то удобно. А соответствие адресов именам в DNS, вообще-то, подтверждает DNSSEC, тоже при помощи электронной подписи. Но DNSSEC – редкая технология.



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

Небольшой, – зато самодостаточный, – практикум по “хакерскому исследованию” DNSSEC.

В DNSSEC “цепочка доверия” выстраивается при помощи связки делегирующих записей, ключей и, собственно, цифровой подписи. Делегирующая запись – это DS (Delegation Signer), которая содержит отпечаток ключа из зоны уровнем ниже. Ключи размещаются в записях DNSKEY, а подписи – в RRSIG. Иногда спрашивают, можно ли простым методом посмотреть на детали устройства DNSSEC. Конечно, совсем вручную – будет медленно, но можно воспользоваться “низкоуровневым” режимом утилиты dig, чтобы извлечь байты нужных записей, а “сходимость” записей непосредственно проверить при помощи OpenSSL. Всё в консоли, то есть, можно сказать, что в ручном режиме. Это упражнение и разобрано ниже. Оно помогает понять практическую сторону DNSSEC.

Утилита dig – это инструмент для непосредственной работы с DNS из пакета BIND, входит в стандартный набор для данной технологической области. При помощи dig можно отправлять DNS-запросы в адрес различных интернет-узлов, получать вывод в доступной форме, а при необходимости – получать байтовые дампы, чем и воспользуемся. Общий смысл запросов должен быть очевиден, а заковыристые опции и загадочные термины ниже объясняются по мере их появления. Утилита dig не единственный вариант, но здесь используем именно её. Кроме dig и openssl dgst задействованы типовые для современных линуксов (и unix-подобных систем вообще) консольные средства: xxd, cat и др. Я здесь использую dig версии 9.16.50 под Debian 11, но это не является требованием.

План достаточно простой: получить базовые записи, относящиеся к DNSSEC, из DNS; сравнить ключи и отпечатки, что называется, вручную; убедиться, что всё сходится, сверив, как минимум, одну цифровую подпись “вручную”, то есть, при помощи инструментария openssl.

Утилита dig, при самом простом вызове (dig example.com), запрашивает A-запись у локального (системного) резолвера. По умолчанию dig печатает большое количество сведений и о запросе, и об ответе, и об ответившем сервере. Чтобы не перегружать текст, ниже используются различные дополнительные опции (+short, +noall и др.), позволяющие управлять составом вывода dig.

В качестве источника примеров здесь используется DNS-зона example.com, которая не только предназначена для того, чтобы служить зоной-примером, но ещё и удачно подписана DNSSEC (что в современной глобальной DNS – редкость).

Итак, для начала нужно узнать, какие серверы имён отвечают за зону com.

$ dig -t NS com. +noall +authority @a.root-servers.net
com.	172800	IN	NS	l.gtld-servers.net.
com.	172800	IN	NS	j.gtld-servers.net.
com.	172800	IN	NS	h.gtld-servers.net.
com.	172800	IN	NS	d.gtld-servers.net.
com.	172800	IN	NS	b.gtld-servers.net.
com.	172800	IN	NS	f.gtld-servers.net.
com.	172800	IN	NS	k.gtld-servers.net.
com.	172800	IN	NS	m.gtld-servers.net.
com.	172800	IN	NS	i.gtld-servers.net.
com.	172800	IN	NS	g.gtld-servers.net.
com.	172800	IN	NS	a.gtld-servers.net.
com.	172800	IN	NS	c.gtld-servers.net.
com.	172800	IN	NS	e.gtld-servers.net.

За этим несложным запросом и строчками ответа скрывается большое количество логических элементов DNS. Пока что непосредственно до DNSSEC мы не добрались, но описываемые подготовительные шаги прямо относятся и к устройству DNSSEC.
Расшифровка запроса dig:
-t NS – означает, что запрашивать нужно NS-записи (то есть, записи, содержащие перечень NS-серверов);
com. – это имя зоны, для которой производится поиск: это ключ для поиска, поскольку DNS является распределённой базой данных типа “ключ-значение”;
+noall +authority – это параметры, управляющие отображением результата; в dig вывод разбит на блоки различного назначения, а первый слева параметр (+noall) запрещает вывод всех этих блоков; второй параметр – включает вывод блока “авторитативной части” (Authority – см. ниже); другими словами – чтобы не множить опции, отключая отдельные блоки, сначала отключены все, а потом включен единственный блок, который мы тут хотим видеть – Authority.
@a.root-servers.net. – это DNS-имя одного из корневых серверов (про то, как это имя превращается в IP-адрес – см. ниже); сведения о зоне com. мы спрашиваем сначала у корневых серверов, обслуживающих вершину глобальной DNS.

В DNS каждая доменная зона обслуживается специальными серверами – серверами имён, которые уполномочены отвечать на запросы об именах в данной зоне. Такие серверы называют “авторитативными серверами” (или “авторитетными”). Авторитативными – серверы назначают в результате выполнения процесса делегирования: список прописывается в зоне уровнем выше. Вся глобальная DNS по именам выстроена в иерархию, ведущую к единому (пока что) корню. Эта иерархия имеет ключевое значение и для DNSSEC. “Ключевое” – в прямом смысле. В мире DNS корневой домен настолько важен, что он обозначается пустым множеством, чтобы математически подчеркнуть фундаментальность. Корневой домен отделяется в DNS-именах крайней справа точкой, вот так: habr.com. – это называется FQDN (fully-qualified domain name) и, скорее всего, знакомо многим инженерам DevOps. Поэтому в запросе выше “com.” снабжается правой точкой, как и имена авторитативных серверов для зоны com.

В DNSSEC цепочка доверия выстраивается от корневого ключа, соответствующего корню глобальной DNS. Звенья цепочки – соответствуют делегированию. Корневой ключ должен передаваться на сторону резолвера, проверяющего DNSSEC-записи, не через DNS.

Почему использована опция +authority? Потому, что корневые серверы не являются авторитативными для зоны com. Соответственно, корневой сервер, который получил запрос, отвечает “делегирующими записями”, а именно – перечнем серверов имён, которые могут быть авторитативными. Это и есть основа так называемого рекурсивного опроса. DNS-сообщения разбиты на несколько блоков (секций). Записи, соответствующие “делегирующему ответу”, передаются в специальном блоке (Authority). Если бы корневой сервер был авторитативным для com., то он бы ответил перечнем NS в блоке обычного ответа (Answer). Таким образом мы проверили работу делегирования: сервер вышестоящей зоны отвечает списком NS у которых нужно спрашивать записи для зоны по мнению данного конкретного DNS-сервера. И пусть мы спрашивали именно NS-записи – полученный ответ всё равно здесь не является “окончательным” и авторитативным, несмотря на название блока Authority.

Обратите внимание, что мы спрашивали NS потому, что, во-первых, для изучения DNSSEC нам потребуются NS-записи; во-вторых, мы уже заранее знали, как устроено делегирование зоны com. Но спросить можно было и A-запись, и TXT-запись, и другую запись, и сразу для example.com. – корневой сервер всё равно ответил бы делегирующим списком серверов имён (NS list), потому что не является авторитативным, но является “делегирующим” для com. Например, спросим TXT для example.com.:

$ dig -t TXT example.com. +noall +authority @a.root-servers.net
com.	172800	IN	NS	l.gtld-servers.net.
com.	172800	IN	NS	j.gtld-servers.net.
com.	172800	IN	NS	h.gtld-servers.net.
com.	172800	IN	NS	d.gtld-servers.net.
com.	172800	IN	NS	b.gtld-servers.net.
com.	172800	IN	NS	f.gtld-servers.net.
com.	172800	IN	NS	k.gtld-servers.net.
com.	172800	IN	NS	m.gtld-servers.net.
com.	172800	IN	NS	i.gtld-servers.net.
com.	172800	IN	NS	g.gtld-servers.net.
com.	172800	IN	NS	a.gtld-servers.net.
com.	172800	IN	NS	c.gtld-servers.net.
com.	172800	IN	NS	e.gtld-servers.net.

А блок Answer – пустой:

$ dig -t TXT example.com. +noall +answer @a.root-servers.net
$

Для того, чтобы подключиться к корневому серверу a.root-servers.net утилита dig должна была определить его IP-адрес. Так как всякий рекурсивный опрос DNS начинается с корневого домена, а IP-адреса корневых серверов невозможно извлечь при помощи только DNS, то серверы, отвечающие за корневую зону, должны быть заранее известны стороне, выполняющей рекурсивный опрос. Поэтому IP-адреса корневых серверов распространяются без DNS, например, в составе дистрибутива резолвера (исторический файл root hints, например) или дистрибутива операционной системы. Впрочем, этот момент к DNSSEC напрямую не относится – для DNSSEC важен криптографический аналог: корневой ключ DNS.

Тем не менее, так как корень обычно уже настроен, для определения IP-адреса корневого сервера, который мы указали по имени a.root-servers.net., dig может использовать локальный рекурсивный резолвер:

$ dig -t A a.root-servers.net. +short
198.41.0.4

Соответственно, запросы к корневому серверу можно было отправлять по IPv4-адресу 198.41.0.4 (это только один из IP-адресов корневых серверов; конкретные IP-адреса указывают на различные узлы, являющиеся составляющими частями одного логического корневого сервера из тринадцати; в зависимости от сетевой конфигурации в точке подключения к Интернету, отвечать на запросы, отправляемые по одному и тому же IP-адресу корневого севера, могут разные физические узлы).

Итак, мы получили делегирующий ответ со списком NS для зоны com. В DNSSEC делегирующие ответы не подписываются, а связь между зонами, позволяющая строить цепочки доверия, устанавливается по ключам. В корневой зоне для com. присутствует DS-запись, содержащая отпечаток, – значение хеш-функции, – открытого ключа, размещённого в зоне com. Подписывается именно DS-запись (их может быть несколько). Заметьте, что если DS-запись отсутствует, то тем или иным способом обязательно удостоверяется (подписывается) факт отсутствия записи. Для этого служат записи NSEC/NSEC3,которые, впрочем мы здесь не рассматриваем.

Запрашиваем DS-запись:

$ dig -t DS com. +short @a.root-servers.net
19718 13 2 8ACBB0CD28F41250A80A491389424D341522D946B0DA0C0291F2D3D7 71D7805A

Здесь использованы следующие параметры:
-t DS – DS-запись;
+short – краткий вывод (только DS-запись);
@a.root-servers.net – всё у того же корневого сервера: DS-запись для com должна быть опубликована в корневой зоне.

В ответ получили состав DS-записи. Это числа. Что они обозначают? Смотрим слева направо:

19718 13 2 8ACBB0CD28F41250A80A491389424D341522D946B0DA0C0291F2D3D7 71D7805A

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

Второе число (десятичная запись, 13) обозначает криптосистему ключа; здесь это ECDSA на кривой P-256, что весьма удобно для наших целей, поскольку запись ключей имеет разумную длину в 64 байта.

Третье число (два) это идентификатор хеш-функции, использованной для получения отпечатка: SHA-256.

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

Для DS-записи в корневой зоне есть подпись. Подписи DNSSEC публикуются в RRSIG-записях. Проверим (значение RRSIG разбито на строки):

$ dig -t DS com. +dnssec +short @a.root-servers.net
19718 13 2 8ACBB0CD28F41250A80A491389424D341522D946B0DA0C0291F2D3D7 71D7805A
DS 8 1 86400 20250315050000 20250302040000 26470 . hYFItZHTilkQr5S2UgyiqboUu7tQ2z
QyqGJU++jAGdDMdFLNCAMZ575q sbR+A/B35ARDCeJuzC54mBn9LXrGNO8EQHoGpBX4l8n6s0y4m6bHQha2
6pOBVtB5waWns/glKgVbxStEUrlGf110Q+ShWSodgEMVkHKeOJghmC/6 jRiHyVLsiUQjYqXVUln9o9sr1l
hGvWcwrzuxuEeaQh1PRuLpqUO9KL80 pwnOK1MDFKDakmnT8sKhaegMUXKqQN6vKDJefEpFU1fqGk1hP4GV
Uy/i UbvbTpLRoJj4HBBmw8phvd1qFRbtQ2pqdCRMpVSNzdESw0fFncMutkiX NwXT8Q==

Здесь добавлена опция +dnssec, которая устанавливает в DNS-запросе флаг, требующий от авторитативного сервера отправки DNSSEC-записей, соответствующих запросу. Мы получили RRSIG. Это RRSIG от ключа подписи корневой зоны (идентификатор 26470). Корневая зона использует RSA-ключи, поэтому такой большой ответ. Чтобы не возиться с байтами устаревшей RSA – мы не станем проверять именно эту RRSIG-запись, а проверим RSSIG с ECDSA когда перейдём непосредственно к example.com. ниже. Логика работы с RRSIG от уровня зоны никак не зависит, и от используемой криптосистемы подписи тоже не зависит. Поля RRSIG тоже рассматриваются детально ниже.

Здесь же проверим, что сходится сама DS-запись, то есть, убедимся, разобрав всё по байтам, что отпечаток соответствует ключу в зоне com.

Для начала такой проверки нужно получить соответствующий ключ. Ключи публикуются в DNSKEY-записях. Запросим ключи у авторитативного сервера зоны com. (то есть, уже не у корневого сервера):

$ dig -t DNSKEY +noall +answer @a.gtld-servers.net. -q com.
com.	86400	IN	DNSKEY	257 3 13 tx8EZRAd2+K/DJRV0S+hbBzaRPS/G6JVNBitHzqpsGlz8hu
E61Ms9ANe 6NSDLKJtiTBqfTJWDAywEp1FCsEINQ==
com.	86400	IN	DNSKEY	256 3 13 pkypg02poKlakMmicLGweP8+fDKFWyeUuYXlkJ+qhZHwrHZ
q+xRSeEm+ /68t287aYn0tn/IRw7PbHl08cnH66A==

Здесь использована новая опция +answer – она означает, что мы хотим вывести основной блок ответа (Answer).

В ответе мы получили два ключа. Посмотрим на параметры записей, слева направо:
com. – имя зоны;
86400 – TTL, время кеширования записи;
IN – класс записи (не рассматриваем, здесь все будут IN);
DNSKEY – запись DNSKEY, которая содержит ключ.

Далее идёт значение флагов, соответствующих ключу (257 и 256). Это весьма важное для DNSSEC поле. В данном случае 257 означает, что установлен бит SEP (Secure Entry Point – точка доверенного входа). Именно ключ с флагом SEP нам и нужен, потому что на него должна показывать DS-запись. Естественно, флаг SEP не является строгим указанием на то, что ключ с этим флагом действительно является ключом в цепочке доверия: нужно проверить соответствие подписей и отпечатков.

Ключи в зоне с DNSSEC делят на KSK и ZSK. Первый тип, KSK (Key Signing Key – ключ подписи ключа), служит для подписывания записей с ключами DNSKEY и является SEP-ключом. Ключ ZSK (Zone Signing Key) служит для подписывания прочих DNS-записей в зоне. На практике, это достаточно условное деление: в зоне может быть только один ключ (KSK, в таком случае), которым подписано всё остальное.

Формально, разделение ключей на KSK и ZSK объясняется необходимостью снизить количество подписей, выполняемых каждым ключом до его замены: например, ZSK обновляется реже, чем другие записи в зоне, которые им подписываются, при этом сам ZSK может быть заменён без необходимости замены KSK, которая требует и замены DS-записи в зоне уровнем выше. ZSK может быть меньшей разрядности, чем KSK, что иногда может повысить производительность, но уменьшит допустимое время использования ключа. И так далее. Всё это верные, но скорее теоретические рассуждения, относящиеся к “сферической в вакууме”, идеальной системе, потому что на практике DNSSEC в корне всё ещё использует RSA, а сама система была запущена даже без проектирования процесса ротации корневых ключей. Конечно, в зоне может быть несколько ключей KSK, несколько ZSK. Ключи должны быть предварительно опубликованы в процессе ротации, также предусмотрен бит статуса, отмечающий отозванные ключи.

Вернёмся к записи DNSKEY (убран пробел и удалены общие поля):

257 3 13 tx8EZRAd2+K/DJRV0S+hbBzaRPS/G6JVNBitHzqpsGlz8huE61Ms9ANe6NSDLKJtiTBqfTJWDAywEp1FCsEINQ==

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

Параметр 13 – обозначает криптосистему: ECDSA-P256 (обратите внимание, что уже сходится с параметрами DS-записи выше).

Следом идёт значение ключа, записанное здесь в Base64. Запись в Base64 генерирует dig.

Значение хеш-функции для DS-записи вычисляется от данных DNSKEY-записи. Для сравнения результатов преобразований проделаем несколько шагов.

Шаг 1. Получение байтового представления DNSKEY-записи

Значение хеш-функции для DS-записи вычисляется от конкатенации имени, к которому относится ключ, с DNSKEY-записью. Всё в байтовом представлении. Поэтому нам нужны байты DNSKEY-записи, а не текстовое отображение, которое получено утлитой dig выше. К счастью, всё та же утилита dig может вывести и исходные байты: для этого нужно указать параметр +unknownformat (или просто +unknown), здесь вывод байтовых значений разбит на несколько строк:

$ dig -t DNSKEY com. +noall +answer @a.gtld-servers.net +unknownformat
com.	86400	CLASS1	TYPE48	\# 68 0101030DB71F0465101DDBE2BF0C9455D12FA16C1CDA44F4BF1BA255
3418AD1F3AA9B06973F21B84EB532CF4035EE8D4832CA26D89306A7D
32560C0CB0129D450AC10835
com.	86400	CLASS1	TYPE48	\# 68 0100030DA64CA9834DA9A0A95A90C9A270B1B078FF3E7C32855B2794
B985E5909FAA8591F0AC766AFB14527849BEFFAF2DDBCEDA627D2D9F
F211C3B3DB1E5D3C7271FAE8

Нам нужен ключ из первого набора (257 == 0x101), а точнее – байты ресурсной записи с этим ключом, поскольку до значения ключа, если смотреть слева направо, указаны ещё флаги и идентификаторы.

Шаг 2. Добавление значения имени

Мы используем HEX-текстовую запись байтов, чтобы вычислить значение хеш-функции утилитой sha256sum. Но для этого к байтам нужно слева присоединить имя зоны. Именем у нас является com. – обязательно указывается в FQDN-формате. Для кодирования DNS-имён в DNS применяется отдельная схема: имена разбиваются на лейблы (это, грубо говоря, то, что идёт “между точками”), каждый лейбл предваряется байтом, содержащим длину записи, после которой идут байты с ASCII-кодами. То есть, никаких точек нет, но зато есть корневой домен, обозначаемый нулевым байтом. У нас для com. получается три байта со значениями букв: 0x63, 0x6F, 0x6D и 0x00 для корневого домена; перед байтами записи имени должен быть байт со значением 0x03 – это длина. Полученную строку присоединяем слева к записи DNSKEY (результат опять разбит на строки):

03636F6D000101030DB71F0465101DDBE2BF0C9455D12FA16C1CDA44F4BF1BA2
553418AD1F3AA9B06973F21B84EB532CF4035EE8D4832CA26D89306A7D32560C
0CB0129D450AC10835

Хорошо, что это “эллиптический” ключ на кривой P-256, а не RSA в 4096 бит.

Шаг 3. Вычисление значения хеш-функции

Теперь строку можно преобразовать в байты, передать в sha256sum и получить результат. Сделаем это так:

$ echo "03636F6D000101030DB71F0465101DDBE2BF0C9455D12FA16C1CDA44F4BF1BA2\
553418AD1F3AA9B06973F21B84EB532CF4035EE8D4832CA26D89306A7D32560C\
0CB0129D450AC10835" | xxd -r -p | sha256sum
8acbb0cd28f41250a80a491389424d341522d946b0da0c0291f2d3d771d7805a  -

Здесь xxd -r -p преобразует HEX-текст в байты. -r – означает “обратное” преобразование, то есть, из “дампа” в байты, а -p – задаёт тип входной строки: поток HEX-текста.

Полученный результат совпал со значением в DS-записи из корневой зоны (см. выше):

8acbb0cd28f41250a80a491389424d341522d946b0da0c0291f2d3d771d7805a
8ACBB0CD28F41250A80A491389424D341522D946B0DA0C0291F2D3D771D7805A

Дальше – RRSIG

Что ж, раз ключ совпал, можно переходить к следующему упражнению: мы планировали проверить RRSIG для каких-то записей, но уже в зоне example.com. Обратите внимание, что только что обработанный ключ не подходит для example.com, поскольку это был ключ из com. Мы выше определили, на какие авторитативные серверы делегирована зона com. Спросим у них имена NS для example.com:

$ dig -t NS example.com. +noall +authority @a.gtld-servers.net
example.com.	172800	IN	NS	a.iana-servers.net.
example.com.	172800	IN	NS	b.iana-servers.net.

Зона делегирована на a и b в .iana-servers.net. Запросим у сервера b A-записи для example.com (значение RRSIG разбито на несколько строк):

$ dig -t A example.com. +noall +answer +dnssec @b.iana-servers.net.
example.com.	300	IN	A	23.192.228.80
example.com.	300	IN	A	23.192.228.84
example.com.	300	IN	A	23.215.0.136
example.com.	300	IN	A	23.215.0.138
example.com.	300	IN	A	96.7.128.175
example.com.	300	IN	A	96.7.128.198
example.com.	300	IN	RRSIG	A 13 2 300 20250322112746 20250301062039 13517 \
			example.com. c9mvE8rEy8qnrjHkTXI2vszUdxJeex0RmmG1G2DTRABeYENPZvl3JNgz \
			bWylvUIDptgCaQhVoOFq/mWCR7mXqA==

Рассмотрим запись RRSIG, начиная от названия записи:

A 13 2 300 20250322112746 20250301062039 13517 example.com. c9m...

A – означает, что подпись RRSIG относится к A-записям;
13 – тип криптосистемы подписи (ECDSA-P-256);
2 – количество лейблов в имени; это поле необходимо для того, чтобы можно было отличить “сконструированные” имена, для wildcard-записей (то есть, вида *.example.com), от “прямых” имён; например, в example.com – два лейбла (корневой домен не считается);
300 – исходное значение TTL для подписывания: исходное значение нужно потому, что конкретные значения TTL для A-записей могли уменьшиться, так как ответ мог прийти от кеширующего резолвера; для того, чтобы подпись проверить, нужно знать то значение TTL, котрое было указано в DNS-зоне;
20250322112746 – время окончания действия подписи (2025-03-22 11:27:46);
20250301062039 – время начала действия подписи;
13517 – идентификатор ключа.

Далее идёт значение подписи в Base64. Мы будем проверять подпись при помощи утилиты dgst из OpenSSL. Чтобы проверить подпись, нужно выполнить следующее: определить байтовый состав подписываемого сообщения; найти значение подписи и преобразовать в подходящий для openssl dgst формат; извлечь из DNS ключ; привести ключ в формат, подходящий для использования с openssl dgst; проверить, что подпись сходится, при помощи openssl dgst.

Итак, RRSIG охватывает все A-записи. Естественно, подписывается не текстовый вариант, который выведен ниже, а объединение байтового представления самих записей, как они фигурируют в DNS, вместе с RRSIG (но кроме самого значения подписи). Заметьте, что в состав A-записи входит не только адрес, но и сопутствующие поля. (Всё это вместе называется RDATA.) Получим байтовое представление при помощи +unknownformat (запись RRSIG разбита на строки):

$ dig -t A example.com. +noall +answer +dnssec +unknownformat @b.iana-servers.net.
example.com.	300	CLASS1	TYPE1	\# 4 17C0E450
example.com.	300	CLASS1	TYPE1	\# 4 17C0E454
example.com.	300	CLASS1	TYPE1	\# 4 17D70088
example.com.	300	CLASS1	TYPE1	\# 4 17D7008A
example.com.	300	CLASS1	TYPE1	\# 4 600780AF
example.com.	300	CLASS1	TYPE1	\# 4 600780C6
example.com.	300	CLASS1	TYPE46	\# 95 00010D020000012C67DE9EB267C2A737
34CD076578616D706C6503636F6D0073
D9AF13CAC4CBCAA7AE31E44D7236BECC
D477125E7B1D119A61B51B60D344005E
60434F66F97724D8336D6CA5BD4203A6
D802690855A0E16AFE658247B997A8

У нас шесть A-записей. Базовое значение A-записи – это просто IPv4-адрес, записанный в четырёх октетах (байтах): 0x17C0E450 == 23.192.228.80. Чтобы преобразовать одну A-запись в байтовый формат, нужно добавить байты, обозначающие имя, тип, класс записи, а также исходное значение TTL (из RRSIG). В байтах это выглядит так:

076578616D706C65 // "example" - лейбл из DNS-имени example.com.
03636F6D         // "com" - следующий лейбл в example.com.
00               // "." - "корневой домен", обозначающий окончание записи имени.
0001             // DNS-класс записи (здесь это единица, IN).
0001             // Тип записи - единица обозначает A-запись.
0000012C         // Исходное значение TTL: 300 секунд.

Просто приписываем эти байты в качестве префикса слева к каждому значению A-записи, а результат – сортируем по возрастанию, как числа:

076578616D706C6503636F6D00000100010000012C000417C0E450 // Первая A-запись.
076578616D706C6503636F6D00000100010000012C000417C0E454 // Вторая [...]
076578616D706C6503636F6D00000100010000012C000417D70088 // [...]
076578616D706C6503636F6D00000100010000012C000417D7008A
076578616D706C6503636F6D00000100010000012C0004600780AF
076578616D706C6503636F6D00000100010000012C0004600780C6

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

0001              // A-запись: это RRSIG для A-записей.
0D                // 13 - индекс криптосистемы.
02                // 2 - количество лейблов.
0000012C          // исходное значение TTL.
67DE9EB2          // таймстемп окончания действия.
67C2A737          // таймстемп начала действия.
34CD              // идентификатор ключа.
076578616D706C65  // example
03636F6D          // com
00                // .

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

00010D020000012C67DE9EB267C2A73734CD076578616D706C6503636F6D0007
6578616D706C6503636F6D00000100010000012C000417C0E450076578616D70
6C6503636F6D00000100010000012C000417C0E454076578616D706C6503636F
6D00000100010000012C000417D70088076578616D706C6503636F6D00000100
010000012C000417D7008A076578616D706C6503636F6D00000100010000012C
0004600780AF076578616D706C6503636F6D00000100010000012C0004600780
C6

Переходим к извлечению и преобразованию подписи. Все байты, которые идут следом за полями с параметрами RRSIG – байты подписи. В ECDSA подпись предcтавляет собой два целых значения r и s. Так как мы используем ECDSA на кривой P-256 значения будут 256-битными, то есть займут 32 байта (строго 32, поскольку спецификация предписывает в DNSSEC указывать байты полной разрядности). Чтобы подпись успешно прочитал пакет OpenSSL эти значения нужно записать в формате ASN.1/DER, что не так уж сложно сделать руками: нужно задать тип SEQUENCE, внутри которого находятся два элемента с типом INTEGER. ASN.1 здесь – это кодирование с указанием длины блока данных перед самими байтами блока. То есть, тип SEQUENCE – 0x30, длина данных будет (0x20+1+1)*2 == 0x44 (ниже будет понятно почему). Значит, первые два байта: 0x30, 0x44. Внутри SEQUENCE – INTEGER. Тип INTEGER – 0x02, длина данных будет 0x20 (это 32 байта, которые занимает и r, и s). Префикс INTEGER получается 0x02, 0x20. И он должен быть указан перед каждым 32-байтовым элементом: и r, и s. Отсюда и общая длина 0x44. (Тут есть некоторые оговорки, которые конкретно в этих данных не проявились: в ASN.1 тип INTEGER знаковый, знак записывается в старший бит старшего байта, это необходимо учитывать, дописывая нулевой байт слева, если старший бит равен единице; кроме того, длина блоков данных может иметь разный формат записи, а определяется этот формат старшим битом первого байта, следующего за байтом, указывающим тип.)

Дописав байты, получаем такое представление для значения подписи:

3044 // ASN.1/SEQUENCE
0220 // ASN.1/INTEGER
73D9AF13CAC4CBCAA7AE31E44D7236BECCD477125E7B1D119A61B51B60D34400
0220 // ASN.1/INTEGER
5E60434F66F97724D8336D6CA5BD4203A6D802690855A0E16AFE658247B997A8

Подпись в нужном формате одним блоком:

3044022073D9AF13CAC4CBCAA7AE31E44D7236BECCD477125E7B1D119A61B51B
60D3440002205E60434F66F97724D8336D6CA5BD4203A6D802690855A0E16AFE
658247B997A8

Переходим к извлечению ключа (значения опять разбиты на строки, а пробелы – удалены).

$ dig -t DNSKEY example.com +noall +answer +unknownformat @b.iana-servers.net.
example.com.		3600	CLASS1	TYPE48	\# 68 0100030D92F8951FD3F87B6DD
6E04B5FF1DA38E293DA1853ECEAB520C1BAD224FA120E04B494E9F28342AC8C55F28C55C7044FA8
0EC06F610C26FA6E4FF38FBB126443FF
example.com.		3600	CLASS1	TYPE48	\# 68 0101030D9172A4BD6537BC661
F4C91A5DEA05DE2A8625A9E5A46CED8B64089C43D9DFADECA5EAC1A870C3922026DC494F6C8522D
96081ACF27D7A891153A6309DEA4F4B5

Мы проверяем подпись внутри зоны, поэтому будем использовать ключ без флага SEP. Это ключ, соответствующий записи на 0x0100 (то есть, 256). Открытый ключ ECDSA – это точка на кривой. Здесь используется несжатый формат, так что будут указаны две координаты: X и Y. Каждая по 32 байта, поскольку это P-256. Если отделить поля параметров (0x0100030D), то найти байты ключа нетрудно:

92F8951FD3F87B6DD6E04B5FF1DA38E293DA1853ECEAB520C1BAD224FA120E04
B494E9F28342AC8C55F28C55C7044FA80EC06F610C26FA6E4FF38FBB126443FF

Здесь верхняя строка – X-координата, а нижняя – Y-координата. Осталось преобразовать ключ к формату, который можно обработать openssl dgst. OpenSSL принимает ключи в ASN.1, можно завернуть в PEM. Небольшую трудность, относительно кодирования подписи, представляет указание OID (ObjectID) – то есть, идентификаторов, по которым OpenSSL сможет опредлить, что в файле именно открытый ключ на кривой P-256. Тем не менее, всё кодирование тут точно так же, как и для подписи, сводится к ручному построению байтов префикса (это настоящий хакерский подход, между прочим) и приписыванию этого префикса к байтам ключа. Делается это следующим образом.

Префикс:

3059             // ASN.1/SEQUENCE
3013             // ASN.1/SEQUENCE
0607             // ASN.1/OID
2A8648CE3D0201   // 1.2.840.10045.2.1 ecPublicKey
0608             // ASN.1/OID
2A8648CE3D030107 // 1.2.840.10045.3.1.7 P-256
0342             // ASN.1/BIT STRING
0004...

Ключ кодируется типом ASN.1 BIT STRING, при этом X и Y объединяются, и приписывается идентификатор несжатого представления (0x04). Итоговое значение в байтах:

3059301306072A8648CE3D020106082A8648CE3D0301070342000492F895
1FD3F87B6DD6E04B5FF1DA38E293DA1853ECEAB520C1BAD224FA120E04B4
94E9F28342AC8C55F28C55C7044FA80EC06F610C26FA6E4FF38FBB126443
FF

Ключ в PEM (далее – zsk-1.pem):

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEkviVH9P4e23W4Etf8do44pPaGFPs6rUgwbrSJPoS
DgS0lOnyg0KsjFXyjFXHBE+oDsBvYQwm+m5P84+7EmRD/w==
-----END PUBLIC KEY-----

Данные, которые записаны HEX-текстом, нетрудно преобразовать в исходные бинарные файлы при помощи утилиты xxd. Например, если HEX-запись подписываемого сообщения сохранена в файле tbs.hex, то получить бинарный файл можно так:

cat tbs.hex | xxd -r -p > tbs.bin

Аналогично получаем файл подписи – sig.bin

Итак, у нас есть нужный для проверки набор данных, все элементы которого получены из DNS в ручном режиме. А именно: tbs.bin – подписанные данные, A-записи и RRSIG; zsk-1.pem – ключ проверки подписи; sig.bin – подпись. Осталось убедиться, что всё сходится:

$ openssl dgst -sha256 -verify zsk-1.pem -signature sig.bin tbs.bin
Verified OK

Проверка завершилась успешно – что и требовалось доказать.



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

CAA-запись в DNS позволяет управлять тем, какие УЦ могут выпускать сертификаты для имён в соответствующей доменной зоне. Проще говоря, в DNS публикуется имя УЦ, которому разрешено выпускать сертификаты. Чьё имя не указано – тем выпускать нельзя. (Есть тонкости с Wildcard-именами и уведомлениями о попытках заказа сертификата.) Интересно, что отсутствие CAA-записи в зоне разрешает выпуск всем. А если в CAA для имён УЦ указать пустой список (“;”), то выпускать нельзя никому.

То есть, тут мы неожиданно, на практике, сталкиваемся с весьма важной особенностью из области теоретической математики вообще и математической логики в частности. Речь про логики и формальные системы разного уровня. Пустой ящик – имеет совсем другой смысл, чем отсутствие ящика. Отсутствие ящика – это отсутствие CAA-записи, которое отсутствие вовсе и не эквивалентно пустому значению CAA-записи. Ящик есть, но он пуст – это наличие CAA-записи, значение которой – пустое множество. В первом случае, пустое множество – это “количество” CAA-записей в зоне. Во втором случае – количество УЦ, имена которых указаны в CAA-записи. Пустое множество при этом одно и то же, но оно укладывается в разные, рекурсивные ящики. Так можно построить натуральные числа.

Конечно, в спецификации этот подход использован не с целью построения натуральных чисел, а всего лишь с целью сделать использование CAA мягким. Иначе без доступа к DNS вообще не получилось бы сертификаты заказывать у УЦ, которые следуют CAA-записям.



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

Один из самых распространённых сценариев использования DNS, как сервиса, это поиск IP-адреса по имени хоста. Сервис DNS, в целом, нужно рассматривать как механизм, обеспечивающий поиск в глобальной распределённой базе данных записей по ключу. Можно считать, что в современном Интернете, с точки зрения обычного использования сервисов типа веба, каждый сценарий начинается с обращения к DNS, и задействует DNS на последующих шагах. Поэтому перехват и подмена DNS-трафика является одним из самых эффективных методов целевых атак. И, конечно, не только целевых.

На слуху сейчас пара методов защиты DNS, а также косвенно связываемый с DNS метод защиты доступа, реализующий аутентификацию по имени (TLS). Пара методов, относящихся непосредственно к DNS, это криптографическое расширение самой системы – DNSSEC, а кроме того защита трафика при помощи TLS – DNS-over-TLS (далее – DoT, сюда же относится “надстройка” в виде DNS-over-HTTPS, DoH). При этом часто можно услышать, что TLS, используемый для веба, – то есть, HTTP-over-TLS, – решает вопрос подмены DNS, так как позволяет аутентифицировать веб-узел по имени.

Действительно, TLS позволяет клиенту проверить подлинность сервера, к которому он подключается, при помощи серверного TLS-сертификата. Если DNS-ответ подменён и программа-клиент подключается к подменному узлу по TLS, то эта программа может проверить сертификат: предполагается, что подменный узел не имеет корректного ключа от доверенного сертификата, DNS-имя задано клиенту при обращении и должно сходиться с указанным в сертификате, поэтому подмена будет обнаружена. Последнее, конечно, подходит для большинства практических случаев, но в случае общем – просто не верно: подставной, но доверенный, сертификат для другого ключа мог быть выпущен либо при помощи всё той же подмены DNS, осуществлённой в направлении систем УЦ, либо при помощи перехвата трафика, идущего к легитимному узлу. Уже это наблюдение приводит к мысли, что TLS для веба никак не может отменить необходимость защиты DNS тем или иным способом – ведь это инструменты, работающие на разных шагах и на разных уровнях: если бы клиент обнаружил подмену DNS, то этот клиент не подключался бы к подменному узлу, так что подменный сертификат по описанной схеме не получилось бы применить (это, впрочем, не исключает применения “в разрыв”, то есть, на промежуточном узле).

Основное отличие TLS, как метода защиты в вебе, для HTTPS, от защиты DNS состоит в том, что для срабатывания TLS в вебе клиент всё же должен подключиться к атакующему, подменному узлу. А это совсем другая поверхность атаки. Например, атакующий узел может “проэксплуатировать” какие-то дефекты клиентской реализации TLS, что отменит требование о наличии легитимного сертификата: в особо продвинутых случаях ни один HTTP-запрос даже не будет отправлен, но клиент окажется взломан, получив код с атакующего узла. DNS, срабатывая раньше, позволяет предотвратить _само_ _подключение_ к подменному узлу по TLS. То есть, эффективная защита DNS предоставляет тут дополнительный рубеж безопасности. Поэтому предположение, что использование TLS с сертификатами в HTTP-over-TLS достаточно для защиты клиента, и поэтому не нужно защищать DNS, так как TLS решит все проблемы выше уровня IP, – ошибочно. (Не будем, впрочем, забывать, что TLS-сертификаты можно получить для IP-адресов тоже, даже в Let’s Encrypt автоматом.)

Но с защитой DNS есть свои проблемы. Вернёмся к паре упомянутых выше методов такой защиты. Первый из них – DNSSEC. Эта технология крайне мало распространена в доменных зонах. Зато DNSSEC касается буквально всех пользователей валидирующих резолверов, так как используется в корневом домене. Всего лишь несколько десятичных цифр позволяют отломить зону верхнего уровня целиком, при этом аварию DNSSEC в большой зоне замечают все и сразу, очередной раз демонстрируя, что про DNS мало кто вообще в курсе, пока эта технология работает, но как только ломается – о DNS узнаёт едва ли не каждый. Второй метод защиты, – DoT/DoH, – распространён существенно больше, поскольку на клиенте встроен прямо в браузеры, но касается только тех сессий, в которых используется. Так вышло потому, что эти технологии тоже принципиально различаются: и по шагам использования, и по уровню применения (почти как HTTP-over-TLS).

DNSSEC методами цифровой подписи обеспечивает аутентификацию данных в DNS. Основная практическая особенность DNSSEC в том, что она позволяет полностью отделить доверие источнику данных от доверия составу данных: не важно, каким образом и от какого узла получены DNS-данные с подписями DNSSEC, главное – чтобы подписи сходились к доверенному ключу. Доверенным, обычно, выступает ключ подписи ключей глобальной корневой зоны.

Если TLS позволяет удостоверить только узлы в рамках сессии, но не на уровне DNS, то DNSSEC, архитектурно, работает в другой плоскости: информация в DNS касается не сессий и узлов, а правил определения узлов и создания сессий. Поэтому, если поступили данные с DNSSEC-подписями, то DNS-клиенту всё равно, какой узел эти данные прислал, если подписи сходятся. В TLS, несомненно, тоже есть аутентификация передаваемых данных, однако эта аутентификация возможна только по конкретным сессионным ключам, то есть, здесь всё на уровне “передачи данных”. В TLS возможно только узнать, что данные передаёт тот сервер, у которого есть секретный ключ от ключа в сертификате, но ничего нельзя узнать про сами передаваемые данные. Поэтому, если требуется проверить сами данные, TLS не помогает, а нужны дополнительные методы.

Вспомним теперь, что DoH используется только на “последней DNS-миле”, то есть, от клиента к рекурсивному резолверу. DoT может использоваться и на авторитативных серверах (например, поддерживается NS wikimedia.org и facebook.com), но, всё же, распространена эта технология тоже только на “последней DNS-миле”. Таким образом, DoT/DoH не позволяют проверить подлинность DNS-данных, как их видит резолвер, но позволяют аутентифицировать резолвер и зашифровать данные на “последней DNS-миле”. DoT/DoH скрывают трафик от пассивно прослушивающей канал стороны – обычные DNS-пакеты, напротив, ходят в открытом виде, так как DNSSEC никакого зашифрования не предусматривает. Но “последняя миля” тут очень важна: трафик DNS к резолверу, соответствующий запросу клиента, всё равно ходит в открытом виде (если, конечно, резолвер не использует только DoT в направлении авторитативных серверов, но так сейчас работать DNS не будет на практике). В DNS-запросы могут быть встроены маркеры, которые позволят различать источники этих запросов на промежуточных узлах “за резолвером”.

Интересно, что в DNS, как в систему, давно встроены базовые методы защиты от спуфинга ответов. Встроены они на уровне пакетов и UDP-обмена. Классический элемент пакета – это поле TransactionID, которое содержит 16-битный идентификатор: предполагается, что корректный ответ сервера будет содержать TransactionID, совпадающий с присланным в запросе, и это позволит клиенту (резолверу) отличить настоящий ответ от “подспуфленного”, который присылает узел, не видевший запроса. Второй элемент – номер порта источника, указываемый в UDP-пакете. Работать должно аналогично схеме с TransactionID, но с учётом особенностей транспорта: то есть, ответ на тот же номер порта, который указан в качестве источника (и тут необходимо “передать привет” NAT). Есть и более экзотическая схема, построенная на рандомизации регистра символов имени (DNS Case Randomisation). Эти методы работают где-то между DNSSEC и DNS-over-TLS: сторона, видевшая запрос, всё равно может подделать ответ.

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



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