← Все статьи

Воспроизводимые сборки: доказательство того, что запущенное ПО — это ПО, которое вы проверили

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

Март 2019 года, Берн. Swiss Post собирается сделать то, что звучит как золотой стандарт прозрачности выборов. Система интернет-голосования — разработанная испанским поставщиком Scytl и предназначенная для использования на обязывающих голосованиях федерального уровня в Швейцарии — вот-вот получит опубликованный в сети полный исходный код для ознакомления всем желающим.

Это то, о чём годами просили защитники демократии. Откройте чёрный ящик. Дайте экспертам посмотреть. Доверие через проверку.

Через несколько недель трое независимых криптографов — Sarah Jamie Lewis, Olivier Pereira и Vanessa Teague — начали изучение. И они обнаружили не совсем успокаивающие вещи.


Люк, который никто не видел, пока кто-то не посмотрел

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

Это называлось универсальной проверяемостью. Идея была в следующем: вам не нужно доверять серверу. Вы сами проверяете доказательство.

Исследователи обнаружили, что доказательство построено на так называемой схеме люк-обязательства. Проще говоря: доказательство перетасовки могло быть «верифицировано» как корректное, даже если голоса были тайно изменены — при условии, что вы знаете значение люка. А это значение было у операторов системы.

Другими словами, должностное лицо, знающее это значение, могло сгенерировать доказательство перетасовки, которое прошло бы проверку, переставив при этом каждый бюллетень. Утверждение об «универсальной проверяемости» было неопровержимо для внешнего наблюдателя.

Исследователи назвали свою работу «Ceci n'est pas une preuve» — Это не доказательство.

Swiss Post и Scytl подтвердили этот результат. Швейцарские органы власти приостановили работу системы.

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

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


Прочитать рецепт — не то же самое, что попробовать блюдо

Представьте ресторан, который публикует свои рецепты. Каждый ингредиент, каждый приём, каждый шаг — всё онлайн, всё время.

Теперь представьте, что кухня закрыта. Вы можете прочитать, что должно происходить. Но вы не можете наблюдать, что происходит на самом деле.

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

Вот почему. ПО, которое вы читаете в репозитории, — это не то ПО, которое запущено на машине. Между «исходным кодом» и «запущенной программой» есть цепь шагов:

  1. Компилятор берёт исходный код и переводит его в машиночитаемый двоичный код.
  2. Этот двоичный файл упаковывается, устанавливается на оборудование голосования или серверы и криптографически подписывается — или нет.
  3. В момент выборов машина загружается и запускает то, что на ней находится.

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

Люк Swiss Post был в исходном коде. Но другой люк может быть где угодно в этой цепи — и никогда не появиться в коде вообще.


Проблема воспроизводимых сборок простым языком

В разработке ПО существует дисциплина, называемая воспроизводимыми сборками. Её цель проста: при одинаковом исходном коде, одинаковом компиляторе и одинаковых инструкциях сборки любой, кто запустит процесс сборки, должен получить идентичный двоичный файл — бит в бит, байт в байт — каждый раз.

Это важно, потому что это даёт вам способ проверки. Если избирательная комиссия опубликует хеш двоичного файла — уникальный цифровой отпечаток ПО на машине — и если вы можете независимо воспроизвести точно такой же двоичный файл из опубликованного исходного кода, у вас будет криптографическая гарантия, что код на машине — это код, который вы читали.

Если двоичный файл не совпадает, вы знаете, что что-то изменилось между исходным кодом и машиной. Вы не знаете, что именно изменилось. Но вы знаете, что нужно об этом спросить.

Без воспроизводимых сборок пробел между «проверенным исходным кодом» и «запущенным ПО» остаётся невидимым. У вас нет инструмента для его проверки. Вы возвращаетесь к доверию органу власти, который выполнял сборку.

Проект Reproducible Builds — кросс-проектная инициатива сообщества разработчиков — систематически задокументировал, насколько сложно это достичь и сколько способов существует, чтобы цепь сборки произвела разные выходные данные из идентичного исходного кода. Компиляторы встраивают отметки времени. Компоновщики вставляют переменные окружения. Порядок файлов варьируется. Каждое из этих обстоятельств — механизм, благодаря которому две «идентичные» сборки могут произвести разные двоичные файлы без малейшего намерения совершить мошенничество.


Аттестация: вторая половина проблемы

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

Вот здесь на сцену выходит аттестация.

Аттестация — это процесс криптографической подписи утверждения: «Это устройство запускает двоичное ПО X, собранное в момент времени T, с хешем H». Подпись должна исходить из чего-то, защищённого от подделок — в идеале из аппаратуры, которую ПО не может переопределить после загрузки, например Trusted Platform Module (TPM) или аппаратный модуль безопасности. Подписанное утверждение может затем быть проверено каждым, кто имеет открытый ключ.

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

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

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

«Мы запускаем сертифицированное ПО с открытым исходным кодом» — это утверждение. Воспроизводимые сборки и аттестация — это то, что делает его проверяемым.


Что швейцарский случай говорит нам о более глубокой проблеме

Вернёмся в Швейцарию. Исследователи обнаружили люк потому, что прочитали исходный код. Swiss Post подтвердил недостаток и приостановил систему. До сих пор: система работает как задумано.

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

Это не гипотетическая ситуация. В 2003 году информатик Ken Thompson в своей лекции при получении премии Тьюринга описал, как компилятор может быть изменён, чтобы автоматически вставлять бэкдор в программу — а затем дополнительно изменён, чтобы вставлять бэкдор в самого себя, так что даже компиляция компилятора из чистого исходного кода произведёт скомпрометированный двоичный файл. Исходный код выглядит чистым. Компилятор выглядит чистым. Результат является троянизированным.

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

Швейцарский случай доказал, что открытый исходный код лучше, чем закрытый. Он также доказал, что открытый исходный код сам по себе недостаточен.


Пробел, который отслеживает наш трекер пробелов

В TrustVoting мы отслеживаем конкретный пробел: отсутствие в большинстве развёрнутых систем голосования публично проверенных воспроизводимых сборок и аттестации устройств. Вы можете увидеть, насколько часто этот пробел появляется и где, по всему миру.

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

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

Несколько вещей, которые помогли бы закрыть его:

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

Ни одно из этого не экзотично. Это стандартная практика при развёртывании высокозащищённого ПО. Это в значительной степени отсутствует в требованиях к сертификации систем голосования.


Что вы всё ещё не можете проверить сегодня — и что это исправит

Вот неудобное положение, в котором мы оказались после Швейцарии 2019.

Мы знаем, что открытый исходный код — лучше. Люк был найден ровно потому, что код был читаемым. Закрытая система отправила бы недостаток необнаруженным.

Мы знаем, что криптографическая проверяемость — лучше, чем только бумажные следы. Вся философия дизайна швейцарской системы — универсальная проверяемость, математически проверяемые доказательства — это попытка позволить избирателям подтвердить, не доверяя органам власти, что их голос был учтён.

И мы знаем, что оба недостаточны, если они не включают мост между исходным кодом и запущенным двоичным файлом.

Прямо сейчас, для практически каждой системы голосования, развёрнутой в мире, честный ответ на вопрос «как мне узнать, что ПО на этой машине — это ПО, которое было проверено?» звучит так: вы не можете. Вы доверяете органам власти, которые его развернули.

Это доверие может быть обоснованным. Но доверие — это не проверка. Федеральный конституционный суд Германии понимал это, когда в 2009 году постановил, что электронное голосование легитимно только тогда, когда обычные граждане — не только эксперты — могут независимо проверить каждый существенный шаг от бюллетеня к результату. Рассуждения суда применяются столь же остро к развёртыванию ПО, как и к подсчёту голосов: если подтверждение того, что запущен правильный код, требует доверия людям, которые его запускают, то существенный шаг проверки отсутствует.

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

Пока эта цепь не замкнута, каждое утверждение «мы используем открытое, сертифицированное ПО» — это приглашение доверять. И система выборов, которая просит вас доверять ей, вместо того чтобы показать вам доказательство, не завершила решение проблемы.

Посмотрите, как пробел воспроизводимых сборок появляется во всех отслеживаемых нами системах голосования

Прочитайте двухминутную версию этого аргумента

Исследуйте глобальную карту пробелов в проверке технологии выборов


Источники