Ограничиваем сертификат Минцифры

Posted on Вт 18 августа 2026 in misc

Источник

Сейчас многие российские сайты, в первую очередь сайты банков, перешли (не по своей воле, но тем не менее) на использование УЦ, которые контролируются Правительством Российской Федерации. Корневые сертификаты этих УЦ (в просторечье - «сертификаты Минцифры»), как правило, по умолчанию не включаются в списки доверенных стандартными иностранными ОС и браузерами. Что будет, если мы их туда включим?

В теории, в этом случае Правительство РФ сможет устроить MITM-атаку: сделать какой-нибудь поддельный сайт, который выглядит как целевой иностранный, заверить его своим сертификатом, перенаправить все наши запросы на этот сайт, и мы ничего не заметим. С моей точки зрения, оно ни за что не станет таким заниматься. Разве что вы какой-нибудь террорист и против вас проводят целевую атаку, но в этом случае - уж извините. Но тем не менее, раз мы работаем в окружении нулевого доверия (то есть при заходе на сайт мы доверяем только себе и этому сайту, и никакие третьи стороны нам при этом не нужны), то с этим надо что-то делать.

Кстати, описанные ниже способы можно применять не только к сертификатам Минцифры, но и к любым другим. Что может быть актуально например для людей, против которых могут провести MITM-атаку иностранные товарищи майоры.

Отдельный профиль браузера

Chromium-подобные и Firefox-подобные браузеры позволяют создавать отдельный профиль, в который можно устанавливать свои сертификаты. Создаем такой профиль, устанавливаем туда сертификат Минцифры, и в дальнейшем используем его для посещения Госуслуг, сайтов банков и т.п. Это самый простой способ, однако к сожалению он подходит только для десктопных браузеров. На смартфонах, например, вряд ли применим.

Собственная цепочка доверия

Ради этого способа, собственно, и затевалась данная статья. Оригинальная идея подкинута в этом нейропосте @ZenitharChampion, за что ему спасибо.

Мы можем

  1. создать собственный доверенный корневой сертификат
  2. при помощи расширений X.509 указать, для каких сайтов он может применяться, и для каких не может
  3. подписать этим сертификатом открытый ключ Минцифры

В таком случае гипотетическая MITM-атака будет успешно остановлена: TLS-стек не найдет имя листового MITM-сертификата в списке доверенных в нашей части цепочки и откажется устанавливать соединение. При этом мы пользуемся стандартными механизмами X.509, не полагаясь на специфичные возможности браузера.

Данный прием называется кросс-сертификация, является стандартным и широко применяется в корпоративной среде.

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

Построение цепочки кросс-сертификации

Запросы без цепочки

Для начала, проверим как все работает из коробки.

$ curl https://vtb.ru
curl: (60) SSL certificate OpenSSL verify result: unable to get local issuer certificate (20)
$ curl https://sberbank.ru
curl: (60) SSL certificate OpenSSL verify result: self-signed certificate in certificate chain (19)

vtb.ru и sberbank.ru пользуются сертификатом Минцифры. Добавляем этот сертификат в список доверенных:

$ curl --cacert ./ca/russian_trusted_root_ca_pem.crt https://vtb.ru
$ # успех
$ curl --cacert ./ca/russian_trusted_root_ca_pem.crt https://sberbank.ru
$ # успех

Задача данного примера - выпустить такой сертификат, чтобы curl успешно подключался только к vtb.ru, но не к sberbank.ru.

Выпуск сертификатов

Не то чтобы использовать Easy-RSA тут обязательно, но раз он позволяет упростить скрипты, то почему нет. Этот проект развивается разработчиками OpenVPN. Доступен во всех дистрибутивах, представляет собой bash-скрипт, который дергает openssl с нужными опциями. Рекомендую. Выпускаем корневой сертификат:

$ export EASYRSA_PKI="./pki"
$ easyrsa init-pki
$ easyrsa build-ca nopass
$ ls pki/ca.crt
pki/ca.crt

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

Далее следует основная магия. Сертификаты с помощью openssl можно выпускать, как используя Certificate Request (*.csr), так и какой-то уже существующий сертификат в качестве шаблона, даже не зная его секретный ключ. При этом соответствующий новому сертификату открытый ключ будет совпадать с оригинальным, также как и прочие значащие поля (Subject и т.п.). А значит, если мы используем сертификат Минцифры в качестве шаблона, то TLS-стек успешно проверит подпись и сможет построить цепочку доверия. Для начала нужно подготовить файл с описанием расширений X.509. В качестве примера воспользуемся тем, что идет в комплекте с easy-rsa:

$ cat /usr/share/easy-rsa/x509-types/ca
basicConstraints = CA:TRUE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
keyUsage = cRLSign, keyCertSign

Копируем этот файл куда-нибудь и вносим изменения:

basicConstraints = CA:TRUE, pathlen:1
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
keyUsage = cRLSign, keyCertSign
nameConstraints = critical, permitted;DNS:vtb.ru

pathlen:1 задает максимальную длину цепочки до листового сертификата, а ключевым является nameConstraints - именно это расширение задает список допустимых имен. critical не дает построить цепочку доверия в случае, если в используемом движке TLS нет поддержки этого расширения. easy-rsa к сожалению работает только с CSR, поэтому генерируем подставной сертификат вручную:

$ openssl x509 -in ./ca/russian_trusted_root_ca_pem.crt\
  -CA pki/ca.crt\
  -CAkey pki/private/ca.key\
  -extfile ca.cnf\
  -out pki/issued/cross.crt

Проверка

Готовим тестовое хранилище сертификатов:

$ cp ./pki/ca.crt ./pki/issued/cross.crt ./mycerts/
$ openssl rehash ./mycerts

Тестируем:

$ curl --capath ./mycerts https://vtb.ru
$ # входит и выходит
$ curl --capath ./mycerts https://sberbank.ru
curl: (60) SSL certificate OpenSSL verify result: permitted subtree violation (47)

Обратите внимание на ошибку, с которой завершилась проверка. Она возникла именно из-за того, что sberbank.ru нет в списке в nameConstraints в сгенерированном подставном сертификате.

Установка в браузер

Открываем chrome://certificate-manager/localcerts/usercerts В «Trusted Certificates» добавляем ca.crt, в «Intermediate Certificates» - cross.crt. Проверяем, работает. Из-за ограниченной природы этих сертификатов их можно добавить прямо в основной профиль браузера.

Установка в Android

Копируем сертификаты на смартфон, находим настройку «установить сертификаты с карты памяти», выбираем «сертификаты центра сертификации». Добавляем ca.crt и cross.crt, проверяем - работает.

Установка в Linux

Различается в зависимости от дистрибутива, например в Arch Linux сертификатами заведует пакет p11-kit и входящая в него утилита trust.

Благодарности