С 2022 года часть российских сайтов работает на сертификатах Russian Trusted Root CA, который выпускает Минцифры. Браузеры этому корню не доверяют, поэтому банки и госсервисы предлагают установить его в систему. Я написал программу, которая открывает такие сайты без установки корня, и хочу разобрать, как она устроена и где у неё слабые места.

Сразу о себе: я автор проекта, он открытый, бесплатный и называется no-mintsifra. Реализацию я писал вместе с Claude, архитектуру и проверку делал сам. Если найдёте ошибки в рассуждениях или коде, буду рад.

В чём проблема

Корневой сертификат не привязан к сайтам. Установив Russian Trusted Root CA, вы разрешаете ему подтверждать подлинность любого домена: почты, мессенджера, облака. Технически ничто не мешает выпустить под ним сертификат для mail.google.com, и ваш браузер его примет.

Это не теория. В 2019 году Казахстан требовал от граждан установить национальный корневой сертификат, и его использовали для перехвата трафика к соцсетям. Chrome, Firefox и Safari в ответ внесли этот ключ в чёрный список.

При этом сайтов, которые реально работают только на сертификатах Минцифры, немного. Хочется доверять Минцифре ровно там, где сайт сам её выбрал, и нигде больше.

Какие были варианты

Вариант

Плюсы

Минусы

Установить корень Минцифры

Просто, работает везде

Доверие ко всему интернету

Отдельный браузер для банков

Изоляция

Два браузера, легко перепутать

Кросс-сертификат с ограничением по доменам

Без прокси

Ключ Минцифры в системе, браузеры могут заблокировать ключ

Локальный прокси со своим ограниченным CA

Минцифры нет в хранилище, проверка цепочки под контролем

Прокси видит расшифрованный трафик к этим сайтам

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

Я выбрал последний вариант.

Как это работает

  1. Windows получает прокси-скрипт (PAC). Он отправляет на 127.0.0.1 только домены из списка, всё остальное идёт как раньше: напрямую или через прокси VPN-клиента.

  2. Прокси сам подключается к сайту и проверяет цепочку: она должна вести к корню Минцифры, встроенному в программу, и сертификат должен быть выдан именно этому имени. Корень живёт только в памяти процесса.

  3. Браузеру прокси отдаёт сертификат, подписанный локальным CA. Этот CA создаётся на вашем компьютере при установке.

  4. Дальше байты передаются без изменений, программа их не разбирает и не сохраняет.

Локальный CA, который ничего не может

Главная защита в самом сертификате CA. Он содержит критическое расширение Name Constraints со списком разрешённых доменов, запрет на выпуск промежуточных CA и запрет на IP-адреса:

tmpl := &x509.Certificate{
    IsCA:                        true,
    MaxPathLenZero:              true,
    KeyUsage:                    x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
    PermittedDNSDomainsCritical: true,
    PermittedDNSDomains:         domains,
    ExcludedIPRanges:            allIPv4AndIPv6,
}

Windows и браузеры проверяют эти ограничения сами. Даже если вредонос украдёт ключ, подписать им google.com не выйдет: сертификат будет отвергнут. Ключ при этом зашифрован DPAPI под учётную запись пользователя.

Откуда берётся список доменов

Список собирает бот в GitHub Actions раз в сутки. Он проверяет каждый адрес: цепочка должна вести к корню Минцифры, имя должно совпадать. Готовый список подписывается ключом ed25519; программа не примет неподписанный или более старый список.

Правила охвата строгие:

  • домен целиком попадает в охват, только если сертификат Минцифры стоит на нём самом или на www;

  • общие домены (gov.ru, mail.ru, yandex.ru, хостинги) никогда не попадают целиком, только отдельными адресами;

  • Google, Apple, Microsoft, Telegram, WhatsApp, Signal, Proton, GitHub и подобные исключены в коде программы. Даже украденный ключ подписи списка их не добавит.

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

Мелочи, без которых не работало

Банки фильтруют ботов по TLS-отпечатку. Если User-Agent говорит «Chrome», а ClientHello выглядит как Go, вход блокируется. Прокси через uTLS повторяет ClientHello самого браузера, а если не получается, использует стандартный отпечаток Chrome или Firefox.

Вторая проблема — VPN. Многие клиенты (v2rayN, Clash, Hiddify) ставят системный прокси с исключением *.ru. PAC проверяет домены из списка раньше исключений, поэтому банки идут через no-mintsifra напрямую, а всё остальное по-прежнему через VPN.

Честно о слабых местах

  • Трафик к сайтам из списка проходит через прокси в расшифрованном виде. Это неизбежно для любой такой схемы.

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

  • Только Windows и только браузеры. Программы со своим хранилищем сертификатов (Java, 1С, банк-клиенты) не охвачены.

  • Антивирусы могут реагировать: «локальный прокси + свой корневой сертификат» устроены так же, как HTTPS-проверка в самих антивирусах.

Как проверить, что бинарник собран из этого кода

Релизы собирает GitHub Actions, к архивам приложена аттестация происхождения:

gh attestation verify .\no-mintsifra-v1.0.0-beta-windows-amd64.zip -R Nettrove/no-mintsifra

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

Что дальше

Проект в бете. Больше всего мне нужны:

  • сайты на сертификатах Минцифры, которые не открываются: достаточно создать issue с адресом;

  • критика модели угроз и кода прокси;

  • люди с опытом на macOS и Android, если кто-то захочет портировать.

Репозиторий: https://github.com/Nettrove/no-mintsifra