Ресурсы: техническое описание TLS, LaTeX - в картинки (img), криптографическая библиотека Arduino, шифр "Кузнечик" на ассемблере AMD64/AVX и ARM64
Открытый исходный код на языке высокого уровня нужно воспринимать как удобный комментарий к внутреннему устройству того или иного программного пакета. Речь здесь о пакетах, которые распространяются среди конечных пользователей. То есть не о сервисах, работающих где-то в Интернете.
В общем случае, если исходный код написан хорошо, то, действительно, лучший источник сведений о подробностях работы программы найти весьма трудно (естественно, желательно знакомство с соответствующим языком программирования). Однако с этим вот “общим случаем” связано немало неверных, излишних “обобщений”. Например, верно ли, что “открытый исходный код” позволяет легко находить уязвимости в ПО, а если исходный код скрыт, но предоставляется только исполняемый файл, то уязвимости найти гораздо сложнее? Поэтому, дескать, “закрытое ПО” более безопасно. Проблема тут в том, что такой вопрос подразумевает некорректное сравнение. Дело даже не в том, что некорректно оценивать “безопасность” программного пакета по “степени сложности” чтения исходников (ведь “бинарный исполняемый” код – такой же “исходник”; об этом – ниже). Просто, нет такой метрики, которая позволила бы универсальным способом сравнить “сложность” нахождения уязвимостей в ПО, когда такие уязвимости были найдены разными методами.
Поиск и практическое использование (“эксплуатация”) уязвимостей – процесс всё ещё творческий, некоторые считают, что более творческий, чем, собственно, написание кода. С этим, конечно, можно и нужно поспорить: например, и там, и там – есть сейчас развитые “автоматизации”, то есть “бездумный” кодинг, автоматическая генерация кода, и, скажем, “фаззинг” на стороне обнаружения (и нельзя забывать про современные анализаторы кода, но это тоже другая тема). Однако полностью исключить творческую составляющую не получится, как не получится корректно сравнить степени “сложности” в метрике, состоящей из параметров доступности и формата исходного кода. Нередко, после того как некоторая уязвимость была выявлена, а описание опубликовано, она тут же начинает почти всем казаться очевидной (относится не только к уязвимостям и не только к ПО, конечно). Преимущество “открытых исходников” тут в том, что можно, как говорится, ткнуть пальцем в код, показав, где проблема.
Действительно, наличие открытых исходников помогает находить типовые уязвимости, помогает быстрее исследовать подозрительные направления – это равно та документирующая роль, которая упомянута выше. Но это ни разу не гарантирует, что та же уязвимость не была бы обнаружена быстрее, если бы для анализа был представлен только исполняемый “бинарник”. Во-первых, средства анализа скомпилированного, исполняемого кода сейчас тоже мощные; во-вторых, “исходный код на языке высокого уровня”, с точки зрения анализа, не равно понятию “исполняемый код”. Да, и в компиляторах бывают “особенности”, и ошибка, приводящая к уязвимости, может возникать только на конкретной платформе, да и не все типы потенциальных уязвимостей легко увидеть в исходных кодах.
Другой момент, о котором постоянно забывают: а если в качестве открытых исходников опубликован код на ассемблере – это сильно помогает в поиске уязвимостей или уже меньше? Ассемблер, даже как явление, сейчас известен меньшему кругу специалистов, это да. Но можно же обфусцировать исходный код на С, да ещё так, что понять его будет посложнее, чем соответствующий кусок в машинных кодах x86.
Вообще, трудность в понимании записи произвольного, заранее не известного, алгоритма настолько фундаментальная, что, похоже, именно она и является причиной формулирования знаменитой проблемы P≟NP. Из этого, впрочем, следует вывод, что и в обратную сторону, – то есть, для превентивного выявления закладок/уязвимостей, – публикация исходников работает не так эффективно, как принято думать: дефекты не просто всегда есть, но они и могут долго оставаться незамеченными, к сожалению. Однако открытые исходники – там, где их возможно открыть – конечно, лучше, и публикация исходников, сама по себе, не делает программу или программную систему, которая распространяется по конечным пользователям, “более уязвимой”.
Комментировать »
Шуточное сравнение, но занимательное, поскольку показывает “почти экспоненциальные” (см. ниже) различия. А именно: одноплатный Raspberry Pi 4 (Model B) и “большой” процессор. Raspberry Pi бывает удобно использовать для некоторых вычислений, особенно четвёртую версию, поскольку она заметно более мощная, но тут я решил в качестве второго устройства взять систему на базе i9-10900K. В качестве алгоритмической основы применён пакет CADO-NFS – это реализация метода NFS (в русскоязычной терминологии, обычно, метод “решета числового поля”) факторизации чисел. Система на процессоре Intel работает под Debian 11, а Raspberry Pi – под Raspberry Pi OS (64 bit), которая тоже основана на Debian 11. CADO-NFS я собрал из исходных кодов, с типовыми настройками (за исключением параметра, задающего разрядность счётчиков, но это детали). У Raspberry Pi 4 – Broadcom BCM2711 и четыре ядра Cortex-A72, без разгона, а у i9-10900K – 10 ядер (Comet Lake, куда более мощных, понятно) на 20 потоков, без разгона.
Что получилось (RPi – Raspberry Pi 4, указано время, затраченное на факторизацию):
1) для полупростого числа разрядностью 200 бит: RPi – ~88 сек.; i9 – ~27 сек. RPi/i9 == ~3.26;
2) для полупростого числа разрядностью 319 бит: RPi – ~4879 сек.; i9 – ~262 сек. RPi/i9 == ~18.62;
3) для полупростого числа разрядностью 336 бит: RPi – ~9861 сек.; i9 – ~578 сек. RPi/i9 == ~17.06;
4) для полупростого числа разрядностью 353 бита: RPi – ~17812 сек.; i9 – ~751 сек. RPi/i9 == ~23.72.
Итак, с ожидаемо огромным преимуществом выиграла система с i9, но для чисел малой разрядности – разница не так уж велика, что, конечно, не менее ожидаемо. (Конечно, показатели зависят и от самого числа, но для шуточного сравнения это не так уж важно.) При этом, из-за меньшего объёма памяти, RPi в принципе сошла бы с дистанции значительно раньше, если бы соревнование продолжилось (в системе с i9 установлено 128 Gb ОЗУ, а в RPi – только четыре гигабайта).
Интересен и другой момент: система с i9 использует чипсет Z590, водяное охлаждение, занимает место на полке и суммарно потребляет при интенсивных вычислениях около 360 Вт (точность измерения мощности, впрочем, не очень высокая, но результат похож на правду); RPi – это одноплатный компьютер, который под нагрузкой потребляет менее 6 Вт. То есть, i9 требуется в 60 раз больше электрической мощности. Вот так.
Комментировать »
Одна из не очевидных особенностей DNS, как сервиса, в том, что этот сервис позволяет преодолевать самые разнообразные системы сетевых ограничений. Типичная ситуация: на некотором сервере закрыты не только все внешние входящие, но и исходящие сетевые соединения “во внешний Интернет”; это может быть сделано как силами сетевого экрана (файрвола) в операционной системе, так и настройкой правил на том или ином маршрутизаторе, за которым подключен сервер. Однако DNS-сервис здесь доступен через выделенный локальный (для сетевого сегмента) резолвер без ограничений. Почему? Во-первых, без DNS не работает множество привычных системных утилит. Во-вторых, администраторы нередко воспринимают DNS именно как сервис, поэтому доступ к резолверу считается доступом к локальным ресурсам и не фильтруется.
Понятно, что так как исходящие соединения закрыты, то запущенная на сервере “троянская программа” не сможет простым способом передать какие-то данные внешнему узлу. Но можно передать данные через DNS. Типовой и универсальный способ – запрос A-записи для имени в доменной зоне, которая делегирована на авторитативные серверы, контролируемые “принимающей стороной”. Предположим, что нужно передать последовательность из нескольких байтов (с произвольными значениями), а “инструментальная” доменная зона – test.ru. Закодируем последовательность тем или иным способом в подмножество допустимых ASCII-символов и запросим в DNS имя, где получившаяся строка будет дописана на третьем (и ниже) уровне: JBQWY3DPFVUGC3DMN4QSCIIK.TEST.RU. Данное имя, через резолвер (или несколько резолверов) попадёт в составе запроса на авторитативный сервер зоны test.ru, где его можно обработать и извлечь данные. (При необходимости, можно разделить запись полезной нагрузки точками – это не повлияет на доставку.)
DNS-запросы, содержащие подобную “абракадабру”, далеко не всегда вызывают подозрения, потому что могут генерироваться не только какими-нибудь элементами ботнета, но и вполне штатными программами, которые таким образом пытаются, например, обнаружить подмену и перехват DNS. Однако, для повышения уровня скрытности, запросы могут кодироваться каким-нибудь другим, вполне “безобидным”, способом: table.is.unavail.fedora.test.ru. Но каждый шаг в сторону снижения уровня “подозрительности” будет снижать и пропускную способность канала передачи.
(В обратную сторону подобная схема тоже может работать, но с меньшей надёжностью, поскольку DNS-ответы могут быть подменены, в том числе, ответы с А-записями.)
Комментировать »
Переход сети ARPANET на TCP/IP состоялся 01/01/1983 – то есть, в 2023 году Интернету исполнилось 40 лет. Почему отсчёт связан именно с этой датой? Потому что Интернет, как глобальная сеть, это именно ARPANET c TCP/IP и связанные технологии, к которым, прежде всего, отсносятся механизмы IP-маршрутизации между компьютерными сетями. Интернет составляют автономные системы. Логические инструменты, позволяющие определить автономную систему (в терминах Интернета и маршрутизации), это следущие понятия:
- межсетевая граница контроля над связностью узлов;
- пограничный маршрутизатор;
- алгоритм маршрутизации и схема межсетевой адресации узлов.
Межсетевая граница – позволяет разделить сетевые узлы внутри и сетевые узлы снаружи, с точки зрения структуры связности; ключевой аспект: локальная администрация сети контролирует и самостоятельно определяет связность локальных узлов (то есть, “какой компьютер куда и как подключен”), а на “коммутацию” внешних узлов повлиять не может и об их реальной связности ничего априори не знает.
Пограничный маршрутизатор – узел, который позволяет логически взаимодействовать с внешними сетями и, по сути, формирует основу для преобразования внутренних политик доставки пакетов во внешние; то есть, пограничный маршрутизатор как раз позволяет получить информацию (с ограничениями) о том, как связаться с внешними узлами, находящимися в других автономных системах.
Алгоритм маршрутизации и межсетевой адресации узлов – это универсальный и эффективный способ адресовать сети и узлы так, чтобы можно было строить практические маршруты доставки пакетов через межсетевые границы при помощи пограничных узлов-маршрутизаторов, абстрагируясь от свойств физических каналов и конкретных протоколов электросвязи. Это и есть IP (префиксы и адреса узлов) и необходимый, в современной сети, BGP (Border Gateway Protocol).
Второй важнейший составляющий элемент Интернета – DNS. Но здесь он оставлен за скобками.
Идея о том, что можно обмениваться обобщёнными “пакетами данных”, самоочевидная. Тем более, если уровень технического развития такой, что уже есть вычислительные машины. Первые строгие механизмы систематической доставки “пакетов данных” очень древние: почтовая связь по дорогам силами гонцов и оптические телеграфы (прообраз электросвязи). Телеграфная связь между “вычислителями” существовала ещё в те дни, когда полупроводниковых электронных вычислительных машин не было, а сложные расчёты проводили с использованием механических калькуляторов организованные коллективы “лаборантов”, работавших параллельно по общей схеме в одном помещении. Но, конечно, это ещё не являлось “Интернетом”.
Комментировать »
В прошлой заметке я написал, что уже давно не приходится использовать C для того, чтобы “что-то написать”, а только для того, чтобы вносить исправления в “старый” (или чужой) код. Записка опубликована вчера, а уже сегодня я сообразил, что вот же – пишу код для некоторых хитрых часов, работающих на Arduino. Естественно, этот код я пишу на C/C++ (от “плюсов” – там очень немного), так как именно такой вариант предлагает типовая среда разработки. Внёс пояснение в прошлую записку. Кстати, для AVR/Arduino я тоже иногда использую ассемблер – см. криптографическую библиотеку.
Комментировать »
Поделюсь текущим состоянием набора “инструментальных” языков программирования, которые использую сейчас. Речь тут именно об использовании на практике. И основной сейчас – Golang, но встречается и Python (см. пояснения ниже), заметное место занимает JavaScript (как ни странно), а вот Perl – напротив, уже не заметен (но всё ещё встречается). А что касается языков уровнем ниже, то ассемблер тоже всё ещё иногда требуется (пример: реализация шифров).
Некоторые детали: Go оказался настолько удобным по конструкциям и сопутствующим инструментам, что заменил (для меня) даже Perl как язык для сиюминутной обработки наборов данных; то есть, Go играет и роль “скриптового языка”, но не только, поскольку на Go написал немало “долгоиграющих” модулей и утилит, в том числе, касающихся приложений криптографии (один из примеров – тестовый сервер TLS 1.3).
Python, фактически, входной язык SAGE (это система компьютерной алгебры, которую я постоянно использую), поэтому сопутствующие вычисления тоже приносят за собой данный язык, но этим объём применения Python, в моём случае, практически полностью исчерпывается. А вот место JavaScript обусловлено клиентскими веб-приложениями, исполняемыми в браузере. Кроме перечисленного, регулярно встречаюсь с Bash (как воплощением shell-скриптов) и Lua. Что касается исчезновения Perl: в своё время я достаточно много кода написал на C, но вот уже лет семь необходимости писать на этом языке не возникало (edit 05/01/23: естественно, кроме Arduino/AVR и пр. – здесь я использую C/C++), за исключением мелких правок кода (это именно в плане что-то заметное написать; читать и немного править написанное на C – приходится постоянно); с Perl-ом, похоже, складывается такая же история.
(Edit 05/01/23: почему-то забыл упомянуть PHP.)
Комментарии (1) »
Немного поправил внутренний код и CSS темы оформления WordPress, которая используется на dxdt.ru сейчас. Визуальных изменений очень мало, но если показывается неправильно (что-нибудь уехало и т.д.) в браузере, то нужно нажать Ctrl+F5 или Ctrl+Shift+R (относится только к десктопным браузерам, понятно).
Кстати, нередко для обновления страницы предлагают использовать Ctrl+R (F5). Но, в случае изменений на строне разработки, это не лучший вариант, по крайней мере, для Firefox и Chrome: Ctrl+R действительно вызывает повторную загрузку страницы, но разрешает использование собственного кеша браузера, так что какие-то элементы могут читаться из кеша. А вот Ctrl+F5 или Ctrl+Shift+R – это загрузка страницы со сбросом состояния кеша.
(А вёрстку, конечно, нужно существенно переделать, с учётом изменений, которые произошли в веб-разработке за минувшие годы.)
Комментировать »
В продолжение заметки про TLS-сертификаты и роль Certificate Transparency. Конечно, наличие меток логов Certificate Transparency может служить в качестве дополнительного признака при валидации сертификата браузером. Однако нужно учитывать тот факт, что сама по себе корректная SCT-метка в сертификате вовсе не означает, что данный сертификат был добавлен в лог. Дело в том, что для получения корректной метки достаточно знать сам исходный сертификат (чтобы вычислить значение хеш-функции) и знать секретный ключ лога (чтобы вычислить значение подписи). Собственно, сами SCT-метки появились для того, чтобы отделить вычислительно сложный процесс добавления сертификата в лог от процесса создания метки, предназначенной для включения в сертификат. Согласно планам применения, наличие метки означает, что оператор лога видел сертификат (и должен включить его в лог, иначе оператор нарушает некоторые правила). Если же посмотреть на этот процесс строго, то наличие метки в сертификате означает, что сертификат видела сторона, знающая секретный ключ лога. Поэтому, если клиентская программа, – например, браузер, – не проверяет наличие сертификата непосредственно в логе, а полагается лишь на корректную SCT-метку, полученную в сертификате, то и проверяет такая программа только дополнительную подпись на сертификате, не более того. То есть, для выпуска валидного сертификата с валидной меткой, стороне, которая выпускает такой сертификат, потребуется доступ к двум ключам – удостоверяющего центра и оператора лога (и не обязательно, что эти две роли играют совершенно разные организации). Не более того.
Естественно, проверять наличие сертификатов в логах можно отдельно и другими программами. Главное – получить сам сертификат. В теории, если обнаружен сертификат с меткой, который, тем не менее, отсутствует в соответствующем логе, данный лог должен быть исключен из списка доверенных браузера. И это очень напоминает историю с удостоверяющими центрами TLS.
Комментировать »
Иногда открытый ключ нужно буквально вводить руками, например, через “воздушный зазор”. Открытый ключ в ECDSA (и, кстати, в ГОСТ-подписи, но не только там) – это точка на кривой, заданной в параметрах. Речь тут про кривые, используемые в криптосистемах на практике, например, P-256. Координаты точки – это два числа X, Y (сторого говоря, два элемента соответствующего конечного поля, но это технические детали). Если разрядность 256 бит, то может показаться, что для передачи ключа придётся руками вводить 2*32 == 64 байта, что в hextext составит аж 128 знаков. Однако обычно можно ограничиться одной координатой X, а Y – вычислить из уравнения кривой. Такой способ кодирования называется сжатым представлением ключа. Единственная хитрость в том, что, так как в уравнение кривой Y входит в квадрате, координат, соответствующих данному X, пара. Это обратные по сложению элементы (всем привычные +/-). Поэтому нужно передать знак элемента, но для передачи знака достаточно одного дополнительного бита. Поэтому сжатое представление в два раза короче, но “разрядность ключа” при этом никак не страдает (страдают вычислительные затраты на принимающей ключ стороне, но этим часто можно пренебречь; иногда, впрочем, приключаются проблемы с обработкой заведомо неверных значений). Кстати, по этим же причинам ключи криптосистем с ed25519 короче в записи – они изначально “в сжатой форме”.
Комментировать »
Некоторое время назад я написал утилиту, кодирующую произвольные байтовые значения в строки англосаксонских рун. Используется кодирование, эквивалентное Base32, но с алфавитом из рун. Алфавит, понятно, может быть любым, но в случае рун для отображения в привычных компьютерных системах потребуется поддержка Unicode.
Таблицы Unicode – весьма богатое нововведение. Unicode позволяет записывать в тексте числа древними шумерскими цифрами, вот так: 𒁹 𒌍𒐋 𒌋𒌋𒐈. (Если на вашем устройстве эти цифры не отображаются, то, вероятно, устройство не содержит подходящих шрифтов; такое всё ещё возможно; более того, мне пришлось столкнуться с существенными трудностями при размещении этой записки в WordPress – стандартный редактор данной CMS отказывался принимать соответствующие символы, пришлось применять некоторые хитрости.)
В принципе, шумерская (вавилонская) система записи – позиционная, по основанию 60, но имеет свои особенности, создающие неоднозначности при интерпретации: там используется плавающая “шестидесятеричная точка”, которая не обозначается; кроме того, в классическом варианте, нет цифры для нуля. 𒁹 𒌍𒐋 𒌋𒌋𒐈, в зависимости от контекста, можно интерпретировать не только очевидным способом, как 5783 (1*60^2 + 36*60 + 23), но и, например, как 96.38333(3) (1*60 + 36 + 23/60). Клинописные цифры, соответствующие числам от 1 до 59, записывались засечками; так, 𒐈 – это 3, а 𒌋𒌋 – это 2*10, то есть, 20 (𒌋 обозначает 10). Unicode, – по крайней мере, в теории, – позволяет все эти засечки напечатать и вывести на экран компьютера в виде текста, благодаря наличию разнообразных кодовых таблиц, среди которых есть и таблица с шумерскими древними цифрами.
Именно засечки, штрихи и прочие дополнительные “чёрточки” (умляуты и тому подобные знаки) Unicode создают исключительные проблемы при преобразовании символов. Рассмотрим в качестве примера кириллическую букву “И” с “краткой”, то есть, “Й” (“кратка” – это чёрточка над “И”). Правила Unicode позволяют обозначить данную букву как одним кодом (Й), так и комбинацией из двух – из сочетания кода буквы “И” (без “кратки”) и отдельного кода, обозначающего “кратку”, который предусмотрен в Unicode. То есть, одна буква расщепляется на два представления! Фольклорное фонетическое восприятие букв сталкивается с универсальным “чисто топологическим” и с треском прогрывает последнему. Результат, вообще говоря, может доставить неожиданных проблем разработчику программного кода, выполняющего преобразования кодировок. Поэтому в тех случаях, когда нужно запись Unicode приводить к другим кодировкам, которые не сохраняют разделение по принципам начертания, используются те или иные соглашения о нормализации, предназначение которых состоит в том, чтобы согласованным способом привести наборы кодов к единому символу. Это один из самых нетривиальных моментов в практике Unicode.
Комментировать »
В ноябре 2012 года, десять лет назад, регистраторы RU-CENTER и R01 включили поддержку DNSSEC для имён в доменных зонах .su, .com, .net. В этом перечне важен домен .su – открытое внедрение DNSSEC в российских реестрах началось именно с него. А включение поддержки означало, что все клиенты даных регистраторов смогут настроить DNSSEC для своих зон. (К сожалению, за прошедшие десять лет слишком многое изменилось, а в результате перемен – утрачена масса полезных исторических веб-страниц, поэтому не могу привести ссылку на текст исходной новости; впрочем, копию пока что можно найти в web.archive.ru.)
Вообще, если посмотреть на сайт под доменом nox.su, то там сказано, что “криптографическая информация, соответствующая домену nox.su, внесена в файл зоны .su 23.11.2011” – то есть, ровно 11 лет назад и годом ранее, чем доступ к DNSSEC появился у упомянутых регистраторов. Это объясняется тем, что с nox.su мне помогли специалисты ТЦИ и RU-CENTER (большая им благодарность!): соответствующую DS-запись в реестр внесли, так сказать, в обход общедоступного интерфейса.
За эти годы DNSSEC не обрела особой популярности. Не только в российских доменах, но и во всей глобальной DNS. Сейчас в зоне .ru лишь около 6 тыс. имён с DS-записью (статистика по другим TLD – картину улучшает не сильно). В моде другие направления и другие этапы доставки данных, а в качестве едва ли не единственного метода защиты информации, на прикладном уровне, используется TLS.
Например, если говорить про DNS, то это DoH (DNS-over-HTTPS) и DoT (DNS-over-TLS). Естественно, эти технологии не позволяют, на уровне клиента, проверить подлинность данных именно DNS-ответа, однако делают возможной аутентификацию узла-источника, который эти данные принёс (DNS-резолвер), защищают канал передачи от прослушивания и подмены. Обычно, всё это доступно только на “последней миле” (то есть, от резолвера до клиента). Использование DoH/DoT не отменяет, конечно, возможности проверки DNSSEC, как и не решает основной задачи DNSSEC – подтверждения подлинности адресной информации.
Комментировать »
Новый