Соответствие приказам МИИ РК №522/НҚ и №500/НҚ (в силе с сентября 2026) - #17
Merged
Merged
Conversation
Сверка новых Правил выдачи, хранения и отзыва сертификатов открытого ключа ЭЦП с v4 — разбор и обоснование решений в rules-2026-compliance.md. Приказ регулирует удостоверяющие центры, поэтому применимы только профили сертификатов, СОС, OCSP- и TSP-сертификатов; сама процедура проверки подписи живёт в другом приказе (п. 27/34 отсылают к «Правилам формирования и проверки подлинности ЭЦП»). По существу профили кодифицируют то, что НУЦ уже выпускает — сверено с ключами NCA SDK 2.0 из репозитория. Расхождения: Разностный СОС в профиле помечен `freshestCRL 2.5.29.46, critical`, а не `deltaCRLIndicator`. Расширение внесено в allowlist критичных (указатель охват списка не сужает), а base теперь ищется только среди файлов не с delta-эндпоинта: иначе delta без индикатора выигрывала бы отбор по CRLNumber (на бою 57 725 против 1 346), и всё, отозванное только в полном списке, вернулось бы как ACTIVE. Ответ-ошибка OCSP (`status != 0`) больше не UNKNOWN, а UNAVAILABLE: он не подписан и о сертификате ничего не сообщает (RFC 6960 §4.2.1), тогда как UNKNOWN в isValid фатален — `tryLater` от одного из двух обязательных теперь респондеров объявлял валидную подпись недействительной. Там же закрыт NPE на пустом теле ответа: он уходил мимо всех catch и давал 500 на весь verify. Зеркала из профилей (ocsp1 и crl1) больше не удваивают работу: OCSP прекращает обход на первом авторитетном ответе, а адреса CRL группируются по точкам распространения (внутри точки это один и тот же список, RFC 5280 §4.2.1.13) и качается первый сработавший. Расширение freshestCRL теперь читается — адрес delta НУЦ публикует именно там, и для издателя вне конфигурации мы работали на одном полном списке. Шаблоны «цифровая система» физического и юридического лица и «Казначейство — Клиент» добавлены в CertificateKeyUser, UID (OID цифровой системы) — в CertificateSubject; оба OID есть на тестовых ключах и молча выпадали. Дефолты: оба адреса респондеров НУЦ, NCANODE_CA_CRL_TTL 1440 → 720 (КУЦ обновляет свой СОС не реже раза в 24 часа). Тесты 467 → 486, coverage 90%. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThqBhEqRNDMuHx39ZRxqXo
Правила формирования и проверки подлинности ЭЦП (приказ МИИ РК №500/НҚ от 21.08.2026) — тот самый документ, на который ссылаются Правила выдачи сертификатов, и профильный для сервиса: он описывает саму процедуру, а не работу удостоверяющего центра. Разбор и обоснования — в rules-2026-verification-compliance.md. Срок действия теперь проверяется у всей цепочки сертификации, а не только у непосредственного издателя (п. 16): истечение любого звена — отрицательный результат. Обход идёт по уже проставленным ссылкам issuerCertificate, поэтому дополнительных поисков не делает, и останавливается на самоподписанном сертификате либо там, где издателя в бандле нет — всё, что настроено в NCANODE_CA_URL, считается якорем доверия. Иначе проверка подписей старой GOST-2004 иерархии ломалась бы у всех, у кого снятого с публикации корня в бандле нет. Истёкший CRL больше не даёт положительного вердикта (п. 18): появился исход EXPIRED. Раньше список за пределами nextUpdate использовался как обычный, и в режиме revocationCheck=[CRL] молча выдавал «не отозван» всё, что издатель опубликовал после nextUpdate. Нефатален он ровно тогда, когда авторитетный ACTIVE уже получен от OCSP — п. 16 разрешает проверять отзыв «посредством сервиса OCSP либо CRL». REVOKED из протухшего списка остаётся в силе. Назначение ключа обязано допускать подпись (п. 16): keyUsage должен разрешать digitalSignature либо nonRepudiation, отсутствие расширения ограничением не считается. Номер политики не enforce'им — его условия задаёт УЦ и из сертификата не выводимы; вместо этого публикуем certificates[].policies, чтобы проверяющая сторона применила свои. Привязка signingCertificateV2 сверяется и обычным /cms/verify (п. 8), а не только AdES-путём: контейнер, у которого атрибут указывает на другой сертификат, внутренне противоречив независимо от эндпойнта. Сознательно оставлены расхождения по п. 17.2 (издатель TSA), п. 19.5 (EKU у самого CA) и п. 19.6 (буквально невыполним) — обоснования в документе. Не сделаны проверки сертификата перед подписанием (п. 4): нужен выключенный по умолчанию режим и решение по дефолту. Тесты 486 → 495, coverage 90%. Эталоны NCALayer прогнаны отдельно: 15/15. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThqBhEqRNDMuHx39ZRxqXo
Правила формирования и проверки подлинности ЭЦП (приказ МИИ РК №500/НҚ, п. 4) требуют, чтобы до формирования подписи подписывающая сторона проверила сертификат: подпись удостоверяющего центра, срок действия, отсутствие отзыва (OCSP, при его недоступности — CRL) и допустимость назначения ключа. При подписании через цифровую систему это обязанность её владельца (п. 22), то есть наша. `CertificateService.ensureSignerCertificateUsable` вызывается во всех sign-путях: /cms, /xml, /pdf, /wsse, /jwt, /jws, /x509/sign, /cades, /xades, /pades. Непригодный ключ получает 400 с субъектом сертификата и статусами отзыва вместо подписи, которая всё равно не прошла бы проверку. Вердикт выносится теми же средствами, что и верификация (attachValidationData + isValid), поэтому «подписали — проверили» не расходится. Криптопроверку сертификата ключом УЦ даёт сам поиск издателя: getRootCertificateFor принимает издателя, только если подпись сертификата сходится с его ключом. Выключено по умолчанию: включение меняет поведение всех sign-эндпойнтов — просроченный или отозванный ключ перестаёт подписывать — и добавляет обращение к OCSP на каждое подписание. Развёртывание, обязанное соответствовать Правилам, включает NCANODE_SIGN_CERT_CHECK=true. Тесты 495 → 499, coverage 90%. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThqBhEqRNDMuHx39ZRxqXo
…Казначейства Тестовые ключи НУЦ показали три поля, которые до сих пор не доходили до ответа. Отчество не парсилось вообще: `X500Principal.toString()` печатает его как GIVENNAME, а разбор ждал только "G", поэтому `subject.lastName` был пуст на всех сертификатах НУЦ. Тест это не ловил — он лишь упоминал поле в комментарии. businessCategory (2.5.4.15) и DC — обязательные поля шаблона «участник цифровой системы "Казначейство – Клиент"» (приказ №522/НҚ, приложение 3, структура 6): код клиента вида KS01234 и роль вида ROLE01. Первое приходит в имени от JDK как `OID.2.5.4.15` — keyword'а у него нет, поэтому нужен явный разбор. Тест на UID переведён с синтетического сертификата на настоящий `legal_infosystem_valid.p12`: у него в Subject лежит UID=1.2.398.6.10.1.1, то есть OID цифровой системы, ради которого поле и добавлялось. Фикстуры на «цифровую систему физического лица» (1.2.398.3.3.4.1.1.1) в тест-паке нет — эта ветка `CertificateKeyUser` остаётся непокрытой. Тесты 493 (+15 эталонов NCALayer, прогнаны в этом заходе — 15/15). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThqBhEqRNDMuHx39ZRxqXo
…yUsage Оба замечания подтвердились по коду. Разностный список, скачанный по freshestCRL, попадал в общий on-demand каталог, а провенанс (`fromDeltaEndpoint`) выводится из каталога — то есть защита отбора base, добавленная в этом же PR для конфигурационного пути, обходилась через путь, ради которого чтение freshestCRL и появилось. Теперь такие списки лежат в `crl/<type>/ondemand-delta`; имя файла осталось `sha1(url)`, поэтому дедуп с конфигурационным delta-кэшем работает как раньше. Потолок NCANODE_CRL_ONDEMAND_MAX общий на оба каталога — это один кэш, разложенный по происхождению, — и прогрев тоже видит новый каталог. Требование keyUsage перенесено с общей валидности на путь проверки подписи: `isValid(..., requireSigningKeyUsage = true)` передают Cms, Xml, Pdf, Wsse, Jws, TspService, SBA-verify и проверка перед подписанием. У CA-сертификатов НУЦ keyUsage = keyCertSign + cRLSign, поэтому глобальная проверка объявляла их недействительными в /x509/info, /pkcs12/info и verifyCerts — эндпойнтах, которые описывают сертификат, а не проверяют подпись им. Освобождать CA:TRUE не стали: это сняло бы проверку и с подписанта, а п. 16 Правил №500/НҚ требует её именно там. Заодно восстановлены семь тестов, которые предыдущий коммит удалил непреднамеренно: правка вырезала текст от заменяемого теста до конца файла вместо одного блока. Тесты 493 → 504 (в том числе +1 регрессия на CA-сертификат в info-пути, +1 на провенанс on-demand delta, +1 на общий потолок двух каталогов). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThqBhEqRNDMuHx39ZRxqXo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Сверка v4 с двумя новыми приказами и приведение кода в соответствие.
rules-2026-compliance.md.rules-2026-verification-compliance.md.По существу мы обоим соответствовали: профили кодифицируют то, что НУЦ уже выпускает (сверено с ключами NCA SDK 2.0 из репозитория), а костяк процедуры проверки был на месте. Ниже — то, что расходилось.
Профили сертификатов (№522/НҚ)
freshestCRL 2.5.29.46, criticalвместоdeltaCRLIndicator. Расширение внесено в allowlist критичных (указатель охват списка не сужает), а base теперь ищется только среди списков не с delta-эндпоинта: иначе delta без индикатора выигрывала бы отбор поCRLNumber(на бою 57 725 против 1 346), и всё, отозванное только в полном списке, вернулось бы как ACTIVE. Провенанс определяется каталогом кэша, поэтому скачанное поfreshestCRLлежит в отдельномcrl/<type>/ondemand-delta— иначе та же дыра открывалась бы через on-demand путь.isValidфатален —tryLaterот одного из двух обязательных теперь респондеров объявлял валидную подпись недействительной. Там же закрыт NPE на пустом теле ответа: он уходил мимо всех catch и давал 500 на весь verify.ocsp1,crl1) больше не удваивают работу. OCSP прекращает обход на первом авторитетном ответе; адреса CRL группируются по точкам распространения (внутри точки это один и тот же список, RFC 5280 §4.2.1.13) и качается первый сработавший.freshestCRLтеперь читается — адрес delta НУЦ публикует именно там, и для издателя вне конфигурации мы работали на одном полном списке.CertificateKeyUser;UID(OID цифровой системы),businessCategory(код клиента Казначейства) иDC(роль) — вCertificateSubject. Все они есть на тестовых ключах и молча выпадали. Попутно чинилось отчество:X500Principal.toString()печатает его какGIVENNAME, а разбор ждал толькоG, поэтомуsubject.lastNameбыл пуст на всех сертификатах НУЦ.NCANODE_CA_CRL_TTL1440 → 720 (КУЦ обновляет свой СОС не реже раза в 24 часа).Процедура проверки (№500/НҚ)
issuerCertificate, поэтому лишних поисков не делает, и останавливается там, где издателя в бандле нет — всё изNCANODE_CA_URLсчитается настроенным якорем доверия (иначе ломалась бы проверка подписей старой ГОСТ-2004 иерархии).EXPIREDвrevocations[].result. Раньше список за пределамиnextUpdateв режимеrevocationCheck: ["CRL"]молча выдавал «не отозван» всё, что опубликовано после этой даты. Не фатален ровно тогда, когда авторитетный ACTIVE уже получен от OCSP — п. 16 разрешает проверять отзыв «посредством сервиса OCSP либо CRL».keyUsageобязан разрешать digitalSignature либо nonRepudiation. Требование относится к сертификату подписывающего лица, поэтому включается параметромisValid(..., requireSigningKeyUsage = true)на путях проверки подписи;/x509/info,/pkcs12/infoиverifyCertsописывают сертификат, а не проверяют подпись, и там оно не применяется — у CA-сертификатов НУЦkeyUsage= keyCertSign + cRLSign. Номер политики не enforce'им (условия задаёт УЦ и из сертификата не выводимы) — вместо этого публикуемcertificates[].policies.signingCertificateV2сверяется и обычным/cms/verify(п. 8), а не только AdES-путём.NCANODE_SIGN_CERT_CHECK— проверка сертификата подписанта перед подписанием (п. 4) во всех sign-путях, отказ 400 на непригодном ключе. Выключено по умолчанию: включение меняет поведение всех sign-эндпойнтов и добавляет OCSP-запрос на каждое подписание.Сознательные расхождения (обоснования в
rules-2026-verification-compliance.md): не требуем совпадения издателя TSA с издателем подписанта (п. 17.2 — иначе отвергали бы боевые RSA-метки НУЦ на ГОСТ-подписях), не требуем EKU OCSPSigning от самого CA (п. 19.5 — RFC 6960 §4.2.2.2 это разрешает, так НУЦ и отвечает), п. 19.6 читаем как «квитанция должна свидетельствовать о моменте проверки» (буквально невыполним).Проверка
Тесты 467 → 504, coverage 90%. Эталонные подписи NCALayer прогнаны отдельно — 15/15 (боевая PKI отвечает через раз, при её молчании спека пропускается).
Совместимость
Старые эндпойнты работают как раньше. Новые поля аддитивные (
policies,uid,businessCategory,domainComponent,EXPIRED, новые значенияkeyUser),openapi.ymlобновлён. Два изменения поведения по умолчанию:revocations[]может содержать меньше записей, чем адресов OCSP в AIA: обход прекращается на первом авторитетном ответе. До появления сертификатов с парой респондеров (с 11.09.2026) видимой разницы нет.🤖 Generated with Claude Code
https://claude.ai/code/session_01ThqBhEqRNDMuHx39ZRxqXo