Семнадцать месяцев между апрелём 2025-го и октябрём 2026-го безопасность восьми промежуточных удостоверяющих центров в доверенных хранилищах Mozilla, Chrome, Apple, Microsoft и половины интернета держалась на восьми битах. Не на украденном ключе, не на уязвимости в кривой и не на ошибке криптографии — а на том, что восемь чисел, которые должны были быть случайными 116-значными монстрами, оказались целыми числами меньше 256. Одно из них — 4.
eMudhra — индийский удостоверяющий центр, торгующийся на Бомбейской и Национальной биржах. Его публичный TLS-бренд называется emSign, их корни лежат в доверенных хранилищах всех крупных платформ, а сама компания отчитывается о более чем 60 миллионах выданных сертификатов. 30 апреля 2025 года eMudhra провела ключевую церемонию, на которой должны были родиться 24 промежуточных CA: три региональные серии (A для Индии, B для нидерландской eMudhra BV, C для американской eMudhra Inc), два типа ключей (RSA-4096 и эллиптика P-384) и четыре назначения (DV, OV, EV плюс code signing).
Сломались восемь из двадцати четырёх — все P-384 ключи серий B и C. Шесть из них имели право выпускать TLS-сертификаты для сайтов, два — подписывать код. Доверие к ним шло двумя путями: через корень emSign ECC Root CA - G3 и через кросс-подпись Sectigo с её древним корнем AAA Certificate Services — то есть им верили и те хранилища, которые знали только Sectigo.
17 октября 2026 года в 16:27 UTC исследователь под псевдонимом Wayne прогнал собственный инструмент проверки ключей по сертификатам из CCADB — общей базы данных удостоверяющих центров, которую ведут браузерные программы доверия. Восемь ключей упали. К 18:05 он подписал каждым из них одну и ту же фразу — «We have a problem here - Wayne» — и разослал доказательства вместе с сертификатами в Apple, Chrome, Microsoft, Mozilla и, надо отдать должное, саму eMudhra.
Дальше события шли со скоростью, за которую авторам любой формальной процедуры раскрытия уязвимостей хочется плакать от счастья. Chrome заблокировал шесть TLS-промежуточных через свой CRLSet в 19:45 — через сто минут после получения письма. В 21:28 eMudhra подтвердила получение. В 21:48 Mozilla, не зная метода, самостоятельно восстановила все восемь ключей. В 22:59 блокировка дошла до пользователей Firefox через OneCRL. На следующий день eMudhra отозвала все восемь промежуточных с формулировкой cACompromise. 20 октября всё стало публичным: Mozilla опубликовала баг, выкинула корень emSign ECC Root CA - G3 из хранилища целиком и выпустила Firefox 158.0.1; Go выкатил патч с восемью вжёанными хешами публичных ключей; Microsoft заблокировала сертификаты; Apple — 22-го.
Mozilla и Cloudflare отдельно проверили логи Certificate Transparency и атаку на свои сети: ни одного выпущенного сертификата, ни следов эксплуатации через их инфраструктуру. Для браузеров это хорошая новость — они требуют CT-доказательства, и поддельный сертификат не прошёл бы в них ни в один момент за все семнадцать месяцев. Плохая новость в том, что браузеры — не бо́льшая часть интернета.
Эллиптическая криптография на кривой P-384 выглядит грозно, но устроена почти поэтично. Приватный ключ — это просто число d. Публичный ключ — точка Q, полученная сложением фиксированной точки кривой G самой с собой d раз: Q = d·G. Прямая задача — умножить — мгновенна. Обратная — по известной точке Q найти число d — настолько трудна, что на ней держится весь безопасный веб. Случайный d в P-384 — это около 10¹¹⁵ вариантов, число, которого не существует в физическом мире ни в каком виде.
Приватные ключи eMudhra выглядели так: 47 байтов нулей и один значащий байт. В hex-записи километровая строка нулей и «04» на конце. Атака, которуюalled «count up», не требует даже знаний: считаешь 1·G, сравниваешь с Q, считаешь 2·G, сравниваешь. Максимум 255 умножений — и ты в гостях у чужого ключа. На ноутбуке это миллисекунды.
Публичный ключ при этом выглядит идеально нормально. Точка 4·G — такая же законная точка кривой, как и результат честного броска 384 кубиков. Все структурные проверки сертификата проходят, размер файла стандартен, кодировка безупречна, HSM рапортует об успешной операции. Единственный способ обнаружить слабый ключ — попробовать восстановить его. В этом и есть тихий ужас истории: сертификат не лжёт, он просто честно показывает точку кривой, которая ничего не говорит о том, сколько шагов до неё было.
Объяснение eMudhra, выложенное в баге Mozilla 2083449, заставляет нервно усмехнуться любого, кто хоть раз писал скрипты для продакшена. Ключи планировалось экспортировать в дата-центры Европы и США — вопрос data sovereignty, — и потому в церемонию добавили одобренное отклонение от стандартного сценария: тест процедуры key wrap/unwrap. Скрипт церемонии, помимо правильной пары, создал на HSM вторую пару — слабую. У обеих пар оказались похожие метки. Для запросов на сертификаты выбрали не ту. CSR ушли родительскому корню и Sectigo на кросс-подпись, и кривые ключи навсегда застыли в публичных сертификатах.
Отдельного абзаца заслуживает статус аппаратуры. Utimaco HSM с сертификацией FIPS 140-2 Level 3 проработали ровно как задумано — валидация устройства ничего не говорит о скрипте, который им управляет. HSM честно выполнит любую просьбу: сгенерирует ключ, обернёт, развернёт, подпишет. Если скрипт попросит импортировать ключ со значением, выбранным кем-то на стороне, — импортирует и скажет, что всё прошло успешно. Сертификация модуля — это не сертификация церемонии. Метка ключа — имя, напечатанное человеком; единственное, что реально идентифицирует ключ, — его публичная часть, и сравнивать надо её.
Mozilla добавила, что пыталась взломать оставшиеся P-384 ключи серии A (индийской, сгенерированной на модулях Entrust) «значительно упорнее» — и не смогла. RSA-ключи той же церемонии пошли другим кодовым путём и тоже целы. То есть сломана не компания, не платформа и не криптография — сломан один скрипт плюс человеческая процедура сверки по имени вместо сверки по значению.
Самая тяжёлая часть истории случилась за семнадцать месяцев до публичного раскрытия. В мае-июне 2025-го Sectigo и сторонние наблюдатели заметили, что eMudhra раздаёт CRL — списки отзыва, подписанные листы, которые клиенты скачивают для проверки актуальности сертификатов, — шесть ровно тех TLS-промежуточных, и подписи на этих списках не проходят верификацию. eMudhra закрыла баг с формулировкой «incorrect CA key configuration» и ручным процессом, переподписала списки, все разошлись.
Теперь, когда известна динамика, картинка оживает в неприятную сторону. Сначала eMudhra подписала CRL правильными ключами — и подписи не совпали с сертификатами, потому что в сертификатах лежали публичные части слабых ключей. Тогда CRL переподписали слабыми ключами — и всё сошлось. Центр, владеющий парой ключей, получил от собственной криптографии самое громкое сообщение, которое она способна отправить: «подпись твоим настоящим ключом не совпадает с тем, что написано в твоём же сертификате». Ответом было не расследование, а смена ключа на тот, при котором ошибка исчезает. Против иерархии, которую оба ключа защищали — публичного для всех и приватного для владельца, — сообщение криптографии не имело шансов.
Требовательный читатель спросит: а как же после этого доверять остальным проверкам eMudhra? Ответ браузерных программ доверия звучит примерно так: до октября 2026 против eMudhra накопилось около десяти инцидент-репортов — запрещённые их же политикой размеры ключей, плохие OCSP-сертификаты, validity на секунду сверх лимита, семь не раскрытых подчинённых CA. И в том же окне десять новых корней eMudhra шли в конвейер включения Mozilla, а уведомление о намерении их одобрить вышло 6 октября 2026 года, за одиннадцать дней до взрыва. Chrome сейчас «тщательно оценивает», оставаться ли корню eMudhra в хранилище. Mozilla формулировка «в данный момент не удаляем» полезна ровно тем, что в ней слышно «пока».
Браузеры отделались испугом, и у этих испугов есть техническая фамилия: CRLSet у Chrome, OneCRL и CRLite у Mozilla, быстрые обновления хранилищ, принудительный Certificate Transparency. У браузера одно приложение и канал push-обновлений — он чинится «переключателем» за часы.
Остальной мир устроен иначе, и вот здесь начинается то, что Скотт Хелме назвал «eight bits of trust». Подавляющее большинство не-браузерных TLS-клиентов с настройками по умолчанию делают три вещи: доверяют файлу с корнями, лежащему на диске, не проверяют отзыв и не проверяют CT. Обнови eMudhra вся компания Sectigo все свои списки отзыва календарём вперёд — этих клиентов это не коснётся.
Хелме проверил это лично: подписал подделкой сертификат на api.stripe.com — имя, дорогое настолько, что объяснение не требуется — и предложил его дефолтным curl, Python и Node на Linux и macOS. Все согласились без единого предупреждения. Windows API CryptoAPI согласился, пока включена проверка отзыва; даже с включённой только один из двух путей вернулся как revoked, потому что кросс-подпись Sectigo жила в хранилище Apple и Microsoft отдельно.
Категории риска выстраиваются вокруг того, кто автоматизирует интернет: системные скрипты, curl|sh-установщики, переползающие с сайта на сайт бинарники, CI-раннеры с облачными ключами, SDK платёжных систем, TLS-инспектирующие middleboxes на границе корпоративных сетей, соременные firmware в железе, обновления которого не было после установки. Хелме цитирует замороженные прошивки с валидностью до 2038 года; fenko.nz называет фактический предел — июль 2039, когда истекает валидность самих скомпрометированных промежуточных сертификатов. Вся эта экосистема доверяет взломанному ключу до тех пор, пока не обновится файл или не истечёт сертификат — что наступит раньше.
Любопытно и то, кто чинился дольше всех. Патчи вышли волной: Go 1.27.3 и 1.26.10 с хешами, вжёанными прямо в crypto/x509; NSS 3.131.1; Python certifi 2026.10.19 (и прозрачно признавшаяся, что сначала убила из хранилища лишнего, вернув незаинтересованным их корни через день); curl CA bundle 2026-10-20; Node.js 26.12.1, 24.22.1, 22.23.4; Bun 1.4.4; Deno 2.9.8; Electron три ветки; OpenJDK по всей линейке от 27 до 8; Python 3.15.1, 3.14.9, 3.13.17; pip, uv, rustup, Docker Desktop, ArgoCD, Conda, pixi — вплоть до Claude Code 2.1.303, что само по себе сигнал о том, сколько нужных вещей в современном стеке ходят по HTTPS со своим списком корней. Отдельная ловушка, о которой стоит сказать явно: бинарник на Go чинится только пересборкой патченым тулчейном — обновление Go на сборочном хосте не переписывает уже развёрнутое.
Этот раздел я пишу с чистой совестью, потому что всё описанное ниже проделано руками на моей машине в ночь написания статьи. Скачал proof.zip с disclosure-сайта — он содержит восемь сертификатов, восемь публичных ключей, восемь отсоединённых подписей Уэйна и само сообщение. Вытащил из каждого сертификата публичную точку openssl'ем. Написал умножение точки на кривой P-384 на чистом Python — сорок строк со школьной формулой касательной — и запустил перебор от 1 до 255 по всем восьми сертификатам.
Итог: 0.55 секунды на все восемь ключей, включая извлечение точек. Таблица совпала до единицы с независимо опубликованной таблицей Скотта Хелме и reproduction от fenko.nz:
| Промежуточный | Назначение | d |
|---|---|---|
| EM DV TLS CA - G3B | TLS | 156 |
| EM DV TLS CA - G3C | TLS | 230 |
| EM EV TLS CA - G3B | TLS | 115 |
| EM EV TLS CA - G3C | TLS | 56 |
| EM OV CS CA - G3B | Code signing | 66 |
| EM OV CS CA - G3C | Code signing | 237 |
| EM OV TLS CA - G3B | TLS | 155 |
| EM OV TLS CA - G3C | TLS | 4 |
Дальше — Reconstruction в обе стороны. Из восстановленного скаляра я собрал DER-структуру приватного ключа RFC5915, выписал PEM, скормил openssl'у — публичная часть каждого совпала с публичной частью оригинального сертификата, восемь из восьми. Потом прогнал верификацию подписи Уэйна: сообщение «We have a problem here - Wayne» (ровно 30 байт, без завершающего переноса строки) с отсоединённой подписью SHA-384 — Verified OK по всем восьми сертификатам.
Финальный аккорд — фулл-длина атаки. Я подписал своим восстановленным ключом (тем самым, d=4) свежевыпущенный сертификат с CN=api.stripe.com и SAN=DNS:api.stripe.com, собрал цепочку через кросс-подписанный Sectigo промежуточный, поднял локальный TLS-сервер и постучался в него curl'ом с трюком --resolve. Результат в логе:
* subject: CN=api.stripe.com
* issuer: C=US; O=eMudhra Inc; CN=EM OV TLS CA - G3C
* SSL certificate verify ok.
Поддельный сертификат на чужое доменное имя, выпущенный ключом, восстановленным из публичных данных за долю секунды, — принят моим curl молча, без варнингов, с «verify ok». Никакой взломанной криптографии в этой цепочке нет ни на одном шаге. Есть только маленькое число там, где должно быть большое.
Самое отрезвляющее открытие вечера вышло не из генерации ключей, а из проверки тест-хостов disclosure-сайта. Магия этой площадки в том, что она измеряется сама: за каждым хостом стоит сервер, считающий успешные подключения по user-agent, и его статистика — живая метрика того, сколько клиентов в мире всё ещё доверяет взломанному CA. Вот три результата с моего же стенда:
$ curl https://dv-g3b-emudhra.we-have-a-problem.com/
Your client successfully connected to the site. That's bad!
$ curl https://dv-g3b-aaa-emudhra.we-have-a-problem.com/
Your client successfully connected to the site. That's bad!
$ curl https://dv-g3b-noct-emudhra.we-have-a-problem.com/
Your client successfully connected to the site. That's bad!
Три из трёх. В том числе NoCT-хост — лист, который никогда не попадал в Certificate Transparency, значит мой клиент доверяет взломанному промежуточному, не проверяет CT и не проверяет отзыв. Это ровно тот класс клиентов, о которых в disclosure-сайте написано: «these are the clients that can be attacked without anyone noticing».
Причина элегантна и лежит на поверхности. Debian 13 (trixie), /etc/os-release: ca-certificates 20250419, кандидат на обновление в зеркале — тот же 20250419. Фикс по таблице disclosure-сайта идёт в пакете 20250419+deb13u1, и до моей машины он ещё не доехал. При этом в бандле из 150 корней сидят и AAA Certificate Services, и сам emSign ECC Root CA - G3 — оба «правильных» доверенных якоря, через которые только и можно достроить цепочку к взломанным ключам. Патч существует, релизы помечены 20 октября, мои зеркала просто ещё не раздали.
Мораль этого эпизода мне дороже всей математики: «непропатченный Linux» в этой истории — не анекдот из миру IoT и не ящик в серверной, брошенный в 2019-м. Это продакшен-машина с свежим Debian 13 и curl 8.14.1, применяющая последние обновления четыре дня спустя после раскрытия инцидента категории «критическая». Дыра между «патч вышел» и «патч доехал» — это окно, в котором работают все оценки времени компрометации, и это окно измеряется не часами.
Проверить собственную машину — минута:
curl https://dv-g3b-emudhra.we-have-a-problem.com/
curl https://dv-g3b-aaa-emudhra.we-have-a-problem.com/
curl https://dv-g3b-noct-emudhra.we-have-a-problem.com/
Ошибка сертификата — хорошо, страница — плохо: обновляй ca-certificates, certifi, пересобери контейнеры. Если работаешь с Go — не забывай про пересборку. Если владеешь сертификатом emSign — проверь, какой корень в твоей цепочке: всё, что ходит через emSign ECC Root CA - G3, перестанет валидироваться в Firefox сразу и в Chrome Root Store версии 43 примерно 27 октября. eMudhra переносит около 30 000 сертификатов на RSA-иерархию emSign Root CA - G1 — при ACME-выпуске это форсированный renewal, но проверить, что renewal случился, стоит руками. Для владельцев инфраструктуры с TLS-инспекцией на границе — middleboxes, UTM, прокси с перетерминированием — отдельно: они верифицируют по vendor-хранилищу, чинятся по собственному графику поставщика, и fenko.nz советует точечно заблокировать восемь публичных ключей по SPKI-хешу там, где продукт умеет.
Что остаётся, когда отболит всё вторичное? Три вещи.
Первое. Проверка по метке вместо проверки по значению — это тот тип ошибки, который не мыслится как ошибка. Человек выбрал «не тот ключ» потому, что имена похожи; всё остальное — подписи, CSR, кросс-подпись Sectigo, верификация корня, TLS-handshake, curl, Python, Node — сработало идеально, подтверждая чужую ошибку на каждом шаге. Уверенная цепочка инструментов, где каждое звено рапортует «всё валидно», не заменяет одного простого равенства: публичный ключ в CSR должен быть публичным ключом, записанным в лог церемонии, а не файлом с похожим названием. fenko.nz заканчивает разбор фразой, которую стоит распечатать и повесить над любым HSM: «сигнатура твоим собственным ключом, не проходящая верификацию против твоего же сертификата, — это не ошибка конфигурации, которую можно переподписать. Это сертификат, сообщающий тебе, какой ключ в нём на самом деле».
Второе. Сертифицированное железо не сертифицирует процедуру. FIPS 140-2 Level 3 на Utimaco уговоров не нарушал: он сделал, что сказано. Валидность криптомодуля не покрывает скрипт, который им рулит, и не может отличить «ключ, рождённый честным RNG» от «ключа, пришедшего через wrap/unwrap с дурацким скаляром». Вся надежда — на тесты поверх: проверить длину скаляра там, где он доступен; прогнать recovery-цикл против публичной части там, где не доступен; сверить CSR с логом. Церемония, не содержащая ни одной из этих проверок, — не церемония, а ритуал с дорогим железом.
Третье. Инструменты нападения стали бесплатными, и это меняет обязанности защиты. Scott Helme восстанавливал ключи любым доступным способом, включая агента с одним промптом; eMudhra проверяла свои остальные иерархии «математическими моделями» с AI; мой чистый Python справился за полсекунды без каких-либо библиотек. Когда перебор 1..255 стоит ноль, обязанность CA — не «хорошо хранить ключи», а «самому найти слабый ключ раньше любого, кто проверит». В мире, где аудит одного сертификата стоит долю процента CPU-секунды, отсутствие пробегающей проверки — не оплошность, а выбор.
И где-то на периферии остаётся маленькая эстетическая заметка, из тех, что делает историю не только тревожной, но и человечьей. Кто-то, likely навсегда неизвестный, назвал свой ключевой инструмент и своё сообщение так, что они звучат из любой точки мира одинаково: «We have a problem here». За этой фразой — восемь чисел меньше 256, семьдесят с лишним нулевых байтов в двоичном представлении, семнадцать месяцев тишины и вся криптографическая инфраструктура интернета, которая сработала именно так, как должна: нашла проблему, когда ей её показали.