Enterprise редакция
Привязка доверия ASiC
Контейнер ASiC объединяет подписанные файлы с подписями, которые их защищают. Сложный вопрос не «вычисляется ли подпись?», а «кто стоит за подписантом?». NextPDF\Enterprise\Security\Asic\AsicTrustBinder отвечает именно на этот вопрос. Вы передаёте ему сертификат подписи из подписи контейнера, доверенный список и время валидации. Он отвечает объектом AsicTrustBindingResult: вердиктом доверенный/недоверенный, версией набора якорей, против которого было принято решение, и машиночитаемыми причинами. Каждый отказ называет свою причину, поэтому доказательства для аудита формируются сами собой.
Одна граница задана намеренно, и о ней стоит сказать заранее. Этот API не разбирает контейнеры ASiC. Ваши инструменты открывают контейнер и извлекают сертификат подписи; за решение о доверии отвечает NextPDF.
Доступность и лицензирование
Заголовок раздела «Доступность и лицензирование»Эта возможность поставляется в NextPDF Enterprise (nextpdf/enterprise) и активируется лицензионным конвертом уровня Enterprise. Развёртывание без этого права не загружает классы возможности. Сравните редакции и получите лицензию.
Установка
Заголовок раздела «Установка»composer require nextpdf/enterpriseАктивация требует вашего лицензионного конверта Enterprise. См. Установка и аутентификация. Классы на этой странице находятся в пространствах имён NextPDF\Enterprise\Security\Asic и NextPDF\Enterprise\Security\Tsl.
Концептуальный обзор
Заголовок раздела «Концептуальный обзор»ASiC (Associated Signature Containers, ETSI EN 319 162-1) упаковывает файлы данных и подписи в один архив. Базовый контейнер ASiC встраивает только базовые подписи CAdES или XAdES. Базовая подпись CAdES несёт свой сертификат подписи внутри SignedData.certificates, поэтому предполагается, что верификатор извлечёт его из подписи контейнера, когда подпись корректно сформирована и поддерживается инструментами для контейнеров. Этот извлечённый сертификат и есть входные данные для данного API.
Источник доверия — доверенный список (TSL) ETSI TS 119 612: подписанный XML-документ, перечисляющий поставщиков услуг доверия и сертификаты их услуг. NextPDF\Enterprise\Security\Tsl\TslTrustAnchorProvider преобразует разобранный TslDocument в набор якорей. Только услуги, находящиеся одновременно в статусе granted и относящиеся к типу услуги CA/QC, наполняют набор якорей. Набор несёт строку версии, полученную из порядкового номера TSL и территории, а также контрольную сумму целостности SHA-256.
Перед любым сравнением якорей выполняются два fail-closed контроля:
- Свежесть TSL. Доверенный список, у которого момент
NextUpdateуже прошёл, должен быть отброшен как просроченный.AsicTrustBinder::verify()проверяет свежесть на переданное время валидации до вывода хотя бы одного якоря. Устаревший список или значениеNextUpdateбез явного обозначения UTC вызываетTslParseException. - Срок действия подписанта. Валидация пути по RFC 5280 требует, чтобы срок действия сертификата включал время валидации. Криптографически целостная подпись, чей сертификат на тот момент был просрочен или ещё не действовал, отклоняется с точным кодом причины.
Только затем связыватель проверяет сертификат подписи против каждого якоря. Совпадение даёт trusted: true с причиной anchor_signature_match. Отсутствие совпадения даёт trusted: false с причиной no_anchor_chain.
Почему это работает именно так
Заголовок раздела «Почему это работает именно так»Несущее проектное решение — строгое разделение механики контейнера и решения о доверии, при котором решение о доверии вынуждено быть явным в отношении времени. Форматы контейнеров различаются (ASiC-S, ASiC-E, полезные данные CAdES или XAdES), но вопрос доверия — это одно инвариантное ядро: связывается ли этот сертификат с якорем из свежего доверенного списка в заявленный момент? Освобождение этого ядра от разбора ZIP и XML сохраняет его достаточно малым, чтобы тестировать исчерпывающе и закрываться на отказ на каждом контроле. Та же логика запрещает молчаливый вариант по умолчанию now: время валидации меняет вердикт, поэтому вызывающая сторона должна владеть им. Свежесть проверяется внутри самого пути вывода якорей, а не в необязательном коллабораторе, поэтому ни один порождающий путь не может её пропустить.
Проектная предыстория: Как цифровая подпись доказывает, кто подписал.
Поверхность API
Заголовок раздела «Поверхность API»AsicTrustBinder
Заголовок раздела «AsicTrustBinder»Конструктор принимает поставщик якорей, превращающий доверенные списки в наборы якорей.
public function __construct( private readonly TslTrustAnchorProvider $anchorProvider,) {}Основная точка входа проверяет сертификат подписанта против доверенного списка:
public function verify( string $signerCertPem, TslDocument $tsl, DateTimeInterface $validationTime,): AsicTrustBindingResult$signerCertPem— непустая строка PEM: сертификат подписи из подписи ASiC.$tsl— разобранный, аутентифицированный доверенный список.$validationTime— момент, который должен включать срок действия сертификата подписанта. Значения по умолчанию нет.
Бросает или завершается с ошибкой: NextPDF\Enterprise\Security\Tsl\TslParseException, когда TSL устарел (NextUpdate прошёл), когда NextUpdate не является каноническим значением UTC или когда список не содержит активных услуг CA/QC. Недоверенные подписанты не вызывают исключений; они возвращают результат с trusted: false и кодом причины.
Для пакетных нагрузок проверяйте против заранее собранного набора:
public function verifyAgainstBundle( string $signerCertPem, EnterpriseCaTrustAnchorBundle $bundle, DateTimeInterface $validationTime,): AsicTrustBindingResultБросает или завершается с ошибкой: собственных исключений нет; каждый исход — это AsicTrustBindingResult. Получайте набор из TslTrustAnchorProvider::buildBundle() — не конструируйте его вручную.
TslTrustAnchorProvider
Заголовок раздела «TslTrustAnchorProvider»public function buildBundle(TslDocument $tsl, DateTimeImmutable $now): EnterpriseCaTrustAnchorBundleБросает или завершается с ошибкой: TslParseException, если TSL устарел, его NextUpdate не является каноническим значением UTC или в нём нет активных услуг CA/QC.
AsicTrustBindingResult
Заголовок раздела «AsicTrustBindingResult»public function __construct( public bool $trusted, public string $anchorBundleVersion, public array $reasons,) {}$reasons — это list<non-empty-string> машиночитаемых кодов. $anchorBundleVersion фиксирует использованный набор якорей в форме tsl-<territory>-seq<N> (например tsl-eu-seq42).
| Код причины | Значение |
|---|---|
anchor_signature_match | Сертификат подписанта проверяется против якоря, полученного из TSL. Доверенный. |
no_anchor_chain | Ни один якорь в наборе не проверяет сертификат подписанта. Недоверенный. |
signer_cert_expired | Время валидации приходится после notAfter сертификата. Недоверенный. |
signer_cert_not_yet_valid | Время валидации приходится до notBefore сертификата. Недоверенный. |
cannot_parse_signer_cert | Переданный PEM не разбирается как сертификат X.509. Недоверенный. |
Пример кода — Быстрый старт
Заголовок раздела «Пример кода — Быстрый старт»Ваши инструменты для контейнеров уже извлекли сертификат подписи. Привяжите его к доверенному списку государства-члена, который вы получили и аутентифицировали (см. Доверенные списки).
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Enterprise\Security\Asic\AsicTrustBinder;use NextPDF\Enterprise\Security\Tsl\TslParseException;use NextPDF\Enterprise\Security\Tsl\TslTrustAnchorProvider;use NextPDF\Enterprise\Security\Tsl\TslXmlParser;
// Extracted by YOUR tooling from META-INF/signature.p7s or signatures.xml.$signerCertPem = (string) file_get_contents(__DIR__ . '/asic-signer.pem');
// A trusted list you have already fetched and authenticated.$tslXml = (string) file_get_contents(__DIR__ . '/member-state-tsl.xml');
$binder = new AsicTrustBinder(new TslTrustAnchorProvider());
try { $tsl = (new TslXmlParser())->parse($tslXml);
$result = $binder->verify( signerCertPem: $signerCertPem, tsl: $tsl, validationTime: new DateTimeImmutable('2026-07-03T12:00:00Z'), );} catch (TslParseException $e) { // Fail closed: stale TSL, malformed NextUpdate, or no active CA/QC services. fwrite(STDERR, 'Trusted list rejected: ' . $e->getMessage() . PHP_EOL); exit(1);}
echo $result->trusted ? "TRUSTED\n" : "NOT TRUSTED\n";echo 'Anchors: ' . $result->anchorBundleVersion . "\n";echo 'Reasons: ' . implode(', ', $result->reasons) . "\n";Ожидаемый вывод для подписанта, выпущенного перечисленной услугой CA/QC:
TRUSTEDAnchors: tsl-eu-seq42Reasons: anchor_signature_matchПример кода — Продакшн
Заголовок раздела «Пример кода — Продакшн»Выведите набор якорей один раз на доверенный список, затем проверяйте против него множество подписантов контейнеров. Один устаревший или непригодный TSL закрывает на отказ всю пачку; проблемы отдельных подписантов проявляются по каждому контейнеру.
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Enterprise\Security\Asic\AsicTrustBinder;use NextPDF\Enterprise\Security\Asic\AsicTrustBindingResult;use NextPDF\Enterprise\Security\Tsl\TslDocument;use NextPDF\Enterprise\Security\Tsl\TslParseException;use NextPDF\Enterprise\Security\Tsl\TslTrustAnchorProvider;use NextPDF\Enterprise\Security\Tsl\TslXmlParser;
/** * @param array<string, non-empty-string> $signerPemsByContainer PEM per container path. * @return array<string, AsicTrustBindingResult> * @throws TslParseException When no anchor set can be derived from the TSL. */function bindBatch( TslDocument $tsl, array $signerPemsByContainer, DateTimeImmutable $validationTime,): array { $provider = new TslTrustAnchorProvider();
// Derive the anchor set ONCE; a throw here means the trusted list itself // is unusable at this validation time. $bundle = $provider->buildBundle($tsl, $validationTime);
$binder = new AsicTrustBinder($provider);
$results = []; foreach ($signerPemsByContainer as $container => $signerPem) { $results[$container] = $binder->verifyAgainstBundle( signerCertPem: $signerPem, bundle: $bundle, validationTime: $validationTime, ); }
return $results;}
$tsl = (new TslXmlParser())->parse( (string) file_get_contents(__DIR__ . '/member-state-tsl.xml'),);
$signerPems = [ 'invoice-2026-06.asice' => (string) file_get_contents(__DIR__ . '/signer-a.pem'), 'tender-2019.asice' => (string) file_get_contents(__DIR__ . '/signer-b.pem'),];
try { $results = bindBatch( tsl: $tsl, signerPemsByContainer: $signerPems, validationTime: new DateTimeImmutable('now', new DateTimeZone('UTC')), );} catch (TslParseException $e) { // Fail closed for the WHOLE batch: no trustworthy anchor set exists. fwrite(STDERR, 'Anchor derivation failed: ' . $e->getMessage() . PHP_EOL); exit(1);}
foreach ($results as $container => $result) { printf( "%s => %s (%s; anchors %s)\n", $container, $result->trusted ? 'trusted' : 'rejected', implode(',', $result->reasons), $result->anchorBundleVersion, );}Ожидаемый вывод, когда один сертификат подписанта просрочен:
invoice-2026-06.asice => trusted (anchor_signature_match; anchors tsl-eu-seq42)tender-2019.asice => rejected (signer_cert_expired; anchors tsl-eu-seq42)Крайние случаи и подводные камни
Заголовок раздела «Крайние случаи и подводные камни»- Время валидации обязательно и решающе. Молчаливого варианта по умолчанию
nowнет. Подпись, прошедшая проверку в 2019 году, сообщаетsigner_cert_expired, когда вы проводите валидацию в момент 2026 года, прошедшийnotAfter. Для исторических материалов передавайте время, которое подтверждают ваши доказательства (например, время подтверждения существования), а не стенные часы. - Устаревший TSL бросает исключение; это не вердикт «недоверенный».
TslParseExceptionизverify()илиbuildBundle()означает, что источник доверия непригоден. Трактуйте это как эксплуатационный сбой: обновите список, не записывайте это как отказ подписанту. - Якоря проверяются как прямые издатели. Каждый якорь пробуется как сертификат, подписавший сертификат подписанта. TSL государств-членов ЕС перечисляют выпускающие сертификаты услуг CA/QC, поэтому конечные квалифицированные сертификаты обычно совпадают напрямую. Подписант, выпущенный промежуточным CA, который сам не является перечисленной активной услугой CA/QC, даёт
no_anchor_chain. - Вывод якорей фильтрует строго. Услуги, которые отозваны или относятся к любому типу, отличному от CA/QC, никогда не становятся якорями. Список, у которого набор активных CA/QC пуст, бросает исключение, а не создаёт пустой набор.
NextUpdateдолжен быть каноническим UTC. Значение без явногоZили числового обозначения смещения отклоняется fail-closed, а не переинтерпретируется в локальном часовом поясе сервера.- Некорректный ввод деградирует точно. PEM, который не разбирается, возвращает
cannot_parse_signer_cert; ещё не действующий сертификат отличается от просроченного. - Записывайте
anchorBundleVersion. Он называет точный набор якорей (tsl-<territory>-seq<N>) за каждым вердиктом — именно это спросит аудитор.
Замечания по безопасности
Заголовок раздела «Замечания по безопасности»- Закрытие на отказ по построению. Свежесть проверяется до вывода любого якоря. Контроль срока действия подписанта выполняется до любого сравнения якорей. Непригодный материал доверия бросает исключение; сомнительные подписанты отклоняются с причинами. Ни один путь не деградирует до молчаливого пропуска.
- Привязка доверия — это один слой, а не вся валидация. Этот API не проверяет значение подписи CAdES над содержимым контейнера, не проверяет отзыв (без запросов CRL или OCSP) и не аутентифицирует сам документ TSL. Сначала аутентифицируйте список через конвейер доверенных списков (см. Доверенные списки), проверьте подпись криптографически своими инструментами подписи и добавьте проверку отзыва согласно вашей политике.
- Выбирайте время валидации осознанно. Вердикт — это функция передаваемого вами времени. Выводите его из надёжных доказательств (квалифицированной метки времени, архивной записи), а не из часов, на которые может повлиять злоумышленник.
- Выходные доказательства детерминированы.
trusted,anchorBundleVersionиreasons— это стабильные, машиночитаемые значения, пригодные для подписанных журналов аудита.
Соответствие
Заголовок раздела «Соответствие»AsicTrustBinder поддерживает рабочие процессы, согласованные с ETSI EN 319 162-1 (базовые контейнеры ASiC), ETSI EN 319 122-1 (базовые подписи CAdES) и ETSI TS 119 612 (доверенные списки), и применяет контроль срока действия по RFC 5280 на переданное время валидации.
Поддержка — это не соответствие, а соответствие — это не сертификация. NextPDF реализует проверки, описанные на этой странице; он не был сертифицирован на соответствие этим стандартам каким-либо органом, и использование этого API само по себе не делает ваш вывод «квалифицированным» или юридически действительным согласно eIDAS или иному режиму. NextPDF не имеет сертификации и не предоставляет её. Отвечает ли полный процесс валидации данному юридическому или закупочному требованию — это определение для ваших оценщиков.
Поведение в режиме FIPS
Заголовок раздела «Поведение в режиме FIPS»Привязка доверия выполняет проверки сигнатуры сертификата X.509 внутри процесса; она не проходит через защитный механизм режима FIPS уровня Enterprise, и включение режима FIPS не меняет её поведения. Это не FIPS-валидированная криптографическая служба, и никакая сертификация FIPS 140 не заявляется. Развёртывания с обязательствами по FIPS должны ограничить область этого API соответствующим образом и см. Криптографическая политика FIPS 140-2/3.
Контракт поведения
Заголовок раздела «Контракт поведения»verify()выводит якоря только из TSL, который свеж на переданное время валидации; устаревший или некорректный список бросаетTslParseExceptionдо того, как появится хоть один якорь.- Якоря выводятся исключительно из услуг TSL в статусе granted с типом услуги CA/QC; пустой активный набор бросает исключение.
- Срок действия сертификата подписанта должен включать время валидации; нарушения возвращают
signer_cert_expiredилиsigner_cert_not_yet_valid. - Каждый исход — это
AsicTrustBindingResult, несущийtrusted,anchorBundleVersionи хотя бы один код причины; вердикта без причины не бывает. - Недоверенные подписанты возвращаются, а не бросаются; непригодный материал доверия бросается, а не возвращается как вердикт.
- Разбор контейнера никогда не происходит внутри этого API; входными данными являются извлечённый PEM, доверенный список и время валидации.
Резервный вариант в Core
Заголовок раздела «Резервный вариант в Core»NextPDF Core валидирует подписи PDF (CMS/PAdES) против якорей доверия, которые вы явно закрепляете через его контракт CaTrustAnchorBundle — см. Безопасность Core. Core не имеет приёма доверенных списков (TSL) и не имеет специфичной для ASiC привязки доверия. С одним лишь Core вы можете поддерживать собственный набор якорей для валидации подписей PDF; вывод якорей из доверенного списка ETSI TS 119 612 и привязка к ним подписантов контейнеров ASiC требуют NextPDF Enterprise.
Граница публикации
Заголовок раздела «Граница публикации»Эта страница документирует только внешне наблюдаемое поведение и поддерживаемую публичную поверхность API. Внутренние пути пространств имён, вспомогательные классы, таблицы механизмов, имена файлов runbook и префиксы тикетов выходят за рамки.
См. также
Заголовок раздела «См. также»- Доверенные списки — получение, аутентификация и разбор TSL, питающего поставщик якорей.
- Проверка подписи — поверхность верификации Enterprise для подписей PDF.
- Криптографическая политика FIPS 140-2/3 — позиция режима FIPS уровня Enterprise.
- Как цифровая подпись доказывает, кто подписал — фундаментальная предыстория.
- Долгосрочная валидация — почему важны время валидации и сохранённые доказательства.