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

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

Содержание
  1. Почему проверка подписей ломается на практике
  2. Ошибка 1. Проверка без привязки к ожидаемому издателю
  3. Ошибка 2. Доверие к ключу, полученному из недоверенного источника
  4. Ошибка 3. Проверка подписи, но не содержимого
  5. Ошибка 4. Проверка «один раз где-то» вместо проверки в точке использования
  6. Ошибка 5. Игнорирование срока действия, отзыва и обновления доверия
  7. Ошибка 6. Проверка подписи вместо проверки происхождения
  8. Ошибка 7. Неверная проверка в скриптах и CI
  9. Ошибка 8. Смешение моделей доверия разных экосистем
  10. Ошибка 9. Отсутствие политики для неподписанных зависимостей
  11. Как построить проверку, которая действительно работает
  12. Частые вопросы
  13. Достаточно ли проверки подписи, чтобы считать зависимость безопасной?
  14. Что делать, если зависимость вообще не подписана?
  15. Проверять подпись в CI или в момент развертывания?
  16. Как понять, что наша текущая проверка бесполезна?
  17. С чего начать

Почему проверка подписей ломается на практике

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

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

Ошибка 1. Проверка без привязки к ожидаемому издателю

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

Правильная проверка всегда сравнивает идентичность подписавшего с ожидаемой. Минимальный набор проверок:

  • субъект сертификата или идентификатор подписи (например, email издателя, OIDC-идентификатор рабочего процесса, имя организации) совпадает с ожидаемым значением;
  • сертификат выдан доверенным удостоверяющим центром или якорь доверия явно задан в конфигурации;
  • для прозрачных журналов (transparency logs) подтверждено включение подписи в журнал, а не просто наличие ссылки на него.

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

Ошибка 2. Доверие к ключу, полученному из недоверенного источника

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

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

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

Ошибка 3. Проверка подписи, но не содержимого

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

Типичные сценарии этой ошибки:

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

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

Ошибка 4. Проверка «один раз где-то» вместо проверки в точке использования

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

Надёжный подход — применять политику в точке потребления:

  1. определить, где именно артефакт переходит в «исполняемое» состояние: admission-контроллер кластера, шаг установки зависимостей, загрузка образа на хост;
  2. настроить проверку именно там, а не только в CI;
  3. запретить обход: например, блокировать pull образов без валидной подписи на уровне реестра или кластера, а не полагаться на дисциплину разработчиков.

Проверка в CI полезна, но она защищает процесс сборки, а не продакшен. Если образ можно запустить в кластере напрямую, минуя конвейер, нужна принудительная проверка на входе в кластер.

Ошибка 5. Игнорирование срока действия, отзыва и обновления доверия

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

Что проверить в своей политике:

  • учитывается ли срок действия сертификата на момент подписания (для краткоживущих сертификатов, например выпускаемых Sigstore, важна проверка времени через источник точного времени, а не локальные часы сборочной машины);
  • проверяется ли статус отзыва (CRL/OCSP) там, где он применим;
  • есть ли процедура ротации якорей доверия и кто за неё отвечает;
  • что происходит при недоступности сервиса проверки: fail-open (пропустить) или fail-closed (заблокировать). Для критичной инфраструктуры по умолчанию безопаснее fail-closed с явным списком исключений.

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

Ошибка 6. Проверка подписи вместо проверки происхождения

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

Поэтому подпись — один из элементов проверки происхождения (provenance), а не замена ему. Дополняющие меры:

  • сверка подписи с ожидаемым источником кода: для OIDC-подписей — проверка репозитория и workflow, из которых выпущен артефакт;
  • сравнение digest артефакта с независимо опубликованным значением (например, в release notes проекта или в вашем собственном зафиксированном списке);
  • мониторинг уведомлений о компрометациях и отзыве ключей проектов, от которых вы зависите;
  • SLSA-подобные уровни гарантий сборки, если экосистема их предоставляет.

Условный пример: проект X подписывает релизы ключом, который хранится в том же CI, что и сборка. Компрометация CI означает компрометацию подписи. В такой схеме подпись защищает от подмены «по пути», но не от атак на источник — и это нормально, если вы это понимаете и не приписываете подписи больше гарантий, чем она даёт.

Ошибка 7. Неверная проверка в скриптах и CI

Даже правильная по смыслу проверка часто реализована так, что не срабатывает. Типичные дефекты кода проверки:

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

Минимальные требования к скрипту проверки: явный выход с ненулевым кодом при любой неопределённости, запрет продолжения при ошибке, отсутствие флагов вида «пропустить проверку сертификата», фиксация версии утилиты проверки (поведение инструментов подписания меняется, и «та же команда» в новой версии может проверять другое).

Ошибка 8. Смешение моделей доверия разных экосистем

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

Модель Что подтверждает Ключевая точка проверки Частая ошибка
GPG-подпись релиза Целостность файла и владение ключом Отпечаток ключа из независимого источника Импорт ключа с той же страницы, что и файл
PKI-сертификат издателя Идентичность, подтверждённая УЦ Цепочка до корня, срок, отзыв Проверка только факта валидности без сверки субъекта
Краткоживущие сертификаты + журнал прозрачности Факт подписания в конкретный момент идентификатором из OIDC Issuer и идентификатор подписавшего, включение в журнал Проверка без указания ожидаемого идентификатора
Подпись реестра пакетов Соответствие содержимого публикации Настройки клиента и зеркал Использование зеркала, не передающего метаданные подписей

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

Ошибка 9. Отсутствие политики для неподписанных зависимостей

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

Разумная политика разделяет зависимости по уровням риска:

  • критичные для безопасности и целостности компоненты (криптобиблиотеки, агенты, образы базовых слоёв) — только с проверяемой подписью и зафиксированным digest;
  • основные зависимости — подпись желательна, при её отсутствии требуется явное одобрение с указанием причины и срока;
  • остальные — как минимум фиксация хешей (lock-файлы с целостностью), чтобы поймать подмену после первой загрузки.

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

Как построить проверку, которая действительно работает

  1. Составьте список артефактов, для которых подпись обязательна: образы, идущие в продакшен, бинарные сборки, критичные зависимости.
  2. Для каждого определите ожидаемого подписанта: идентификатор, issuer, отпечаток ключа или корень доверия — и источник, из которого этот якорь доверия получен.
  3. Выберите точку принудительной проверки: admission-политика кластера, настройка менеджера пакетов, шаг перед запуском. Проверка должна блокировать, а не только логировать.
  4. Зафиксируйте digest проверенных артефактов и используйте его при развертывании вместо тегов и «latest».
  5. Определите поведение при сбое проверки (fail-closed по умолчанию), порядок исключений с сроками и владельцами.
  6. Запланируйте ротацию якорей доверия и регулярную сверку: раз в квартал убедитесь, что политика всё ещё применяется и не содержит «временных» отключений.
  7. Протестируйте негативный сценарий: подмените артефакт в тестовом окружении и убедитесь, что проверка его блокирует. Проверка, которая ни разу не сработала на отказ, может не работать вовсе.

Частые вопросы

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

Нет. Подпись подтверждает авторство и целостность, но не отсутствие уязвимостей и не добросовестность издателя. Её нужно сочетать с анализом уязвимостей, фиксацией версий и мониторингом инцидентов проекта.

Что делать, если зависимость вообще не подписана?

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

Проверять подпись в CI или в момент развертывания?

Лучше в обоих местах, но обязательной является проверка в точке использования. CI-проверка защищает процесс сборки, а принудительная политика на входе в кластер или на хосте защищает работающую систему от артефактов, попавших в обход конвейера.

Как понять, что наша текущая проверка бесполезна?

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

С чего начать

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

Материал носит информационный характер и описывает общие принципы. Конкретные инструменты, параметры и политика доверия зависят от вашей инфраструктуры и требований безопасности; перед применением сверяйте актуальную документацию используемых механизмов подписания и при необходимости привлекайте специалиста по безопасности.

PEFile.ru