Перейти к содержимому

Безопасность / Подписание: CMS, метки времени RFC 3161, LTV, доверие

На этой странице описана область подписей в NextPDF Core: формирование подписи Content Management Syntax (CMS), применение метки времени Request for Comments (RFC) 3161, проверка цепочки сертификатов по RFC 5280 и проверка отзыва через Online Certificate Status Protocol (OCSP) и список отозванных сертификатов (CRL). Материал ограничивается поведением. Классы реализации в Core являются внутренними: рабочий код использует контракт SignerInterface, а не конкретные типы NextPDF\Security\Signature. Именно верификатор и настроенные для него якоря доверия решают, проходит ли проверку сформированная подпись. Этот результат не контролируется стороной, формирующей подпись; на этой странице это уточняется везде, где важно.

Окно терминала
composer require nextpdf/core:^3

Core строит структуру CMS SignedData на основе диапазона байтов, а затем сохраняет её как данные в кодировке Distinguished Encoding Rules (DER) в записи Contents словаря подписи — ISO 32000-2 §12.8.1. Структура содержит подписанные атрибуты SignerInfo, включая content-type и message-digest — RFC 5652 §5.3. Верификатор заново вычисляет дайджест содержимого и сравнивает его с атрибутом message-digest. Чтобы подпись была действительной, результаты должны совпасть — RFC 5652 §5.4. SignerInfo также содержит идентификатор алгоритма дайджеста и блок подписанных атрибутов — RFC 5652 §5. Core использует phpseclib3 для поставляемых им программных вариантов подписания: RSA, RSASSA-PSS, ECDSA и Ed25519.

Метка времени RFC 3161 создаётся в обмене запросом и ответом со службой меток времени (Time-Stamping Authority, TSA), которая возвращает структуру TSTInfo — RFC 3161 §2.4.1. Каждый токен содержит serialNumber, уникальный для выпускающей TSA — RFC 3161 §2.4.2, — и поле genTime, выраженное во всемирном координированном времени (UTC), то есть момент создания токена — RFC 3161 §2.4.2.

Проверка доверия состоит из двух частей. Проверка пути идёт от сертификата подписанта до якоря доверия и проверяет базовые ограничения и входные данные построения пути — RFC 5280 §6.1. Проверка отзыва обращается к ответчику OCSP или читает CRL: ответ OCSP сообщает good, revoked или unknown — RFC 6960 §2.2, — а поля thisUpdate и nextUpdate ответа ограничивают свежесть этого статуса — RFC 6960 §4.2. Вызывающая сторона предоставляет якоря доверия и политику свежести отзыва. Движок выполняет проверку на основе этих входных данных и не поставляет встроенный список доверия.

ТипВидРольСтабильностьНачиная с
SignerInterfaceинтерфейс (NextPDF\Contracts)Контракт подписания, на который опираются вызывающие стороныстабильный1.0.0
SignatureLevelперечислениеВыбор уровня PDF Advanced Electronic Signatures (PAdES) и проверка доступностистабильный1.0.0
Rfc5280PathValidatorинтерфейсТочка входа для проверки пути сертификации (validate(...))стабильный (заморожен в 3.1.0)3.1.0
RevocationStatusперечислениеРезультат OCSP / CRL: good, revoked, unknownстабильный3.1.0
CaTrustAnchorBundleтипНабор якорей доверия, предоставляемый вызывающей сторонойстабильный3.1.0
TstInfoтипПоля метки времени RFC 3161 после разборастабильный3.2.0

SignerInterface::sign() возвращает SignatureResult. Метод toHex() возвращает шестнадцатеричную строку /Contents, а свойство cmsSignedData содержит необработанные байты DER. Конкретные классы NextPDF\Security\Signature, реализующие это поведение, являются внутренними (stability: internal в манифесте модуля). Они не входят в публичный API и могут измениться без повышения мажорной версии. Полагайтесь на приведённые выше контракты и перечисления.

examples/contracts/signing-quickstart.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\SignerInterface;
/**
* Produce the CMS SignedData hex for a PDF /Contents field.
*
* @param SignerInterface $signer A Core or Premium signer.
* @param string $byteRange The PDF byte range to sign.
*
* @return string Hex-encoded CMS SignedData.
*/
function sign(SignerInterface $signer, string $byteRange): string
{
return $signer->sign($byteRange)->toHex();
}

Вызывающая сторона зависит от контракта. Программный подписант Core и подписант Premium на основе аппаратного модуля безопасности (Hardware Security Module, HSM) оба реализуют SignerInterface, поэтому этот код не меняется при переходе между редакциями.

examples/contracts/signing-trust.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\SignerInterface;
use NextPDF\Contracts\TimestampProviderInterface;
use NextPDF\Exception\NextPdfException;
use Psr\Log\LoggerInterface;
final readonly class TimestampedSigner
{
public function __construct(
private SignerInterface $signer,
private TimestampProviderInterface $timestamps,
private LoggerInterface $logger,
) {}
/**
* Sign a byte range, then timestamp the CMS structure.
*
* The timestamp's trust still depends on the verifier accepting the
* Time-Stamping Authority; this method only produces the structure.
*
* @param string $byteRange The PDF byte range to sign.
*
* @return array{cms: string, tst: string}
*/
public function sign(string $byteRange): array
{
try {
$signature = $this->signer->sign($byteRange);
$token = $this->timestamps->getTimestamp($signature->cmsSignedData);
return ['cms' => $signature->toHex(), 'tst' => $token];
} catch (NextPdfException $e) {
$this->logger->error('Signing failed', ['error' => $e->getMessage()]);
throw $e;
}
}
}

Поставщик меток времени внедряется, чтобы развёртывание могло зафиксировать собственную службу меток времени. Блок catch записывает ошибку в журнал и повторно выбрасывает исключение. Он никогда не подавляет сбой, поэтому путь подписания работает по принципу fail-closed.

  • Сформированная подпись — это ещё не проверенная подпись. Проверка пути и отзыва выполняется на стороне верификатора с использованием его якорей доверия. Сторона, формирующая подпись, не может гарантировать результат проверки.
  • Дайджест диапазона байтов не включает значение подписи. Дайджест, охватывающий октеты Contents, не может пройти проверку — ISO 32000-2 §12.8.1.
  • У статуса отзыва есть окно свежести. Ответ OCSP актуален только для своего интервала thisUpdate / nextUpdate — RFC 6960 §4.2. Устаревший ответ не заменяет свежую проверку в момент валидации.
  • Движок не поставляет встроенный список доверия. CaTrustAnchorBundle предоставляется вызывающей стороной; пустой набор означает, что ни одна цепочка не проходит проверку, — так задумано.
  • OCSP unknown — это не good. Рассматривайте unknown как отсутствие определённости, а не как неявное успешное прохождение — RFC 6960 §2.2.
  • Хранение ключей в HSM, отложенное и облачное подписание, а также создание PAdES B-LT / B-LTA в Core отсутствуют. Попытка выбрать эти пути в дистрибутиве Core завершается отказом по принципу fail-closed с сообщением, называющим отсутствующий компонент Enterprise.

Программная подпись занимает единицы миллисекунд. Метка времени добавляет один сетевой обмен с TSA. Проверка пути выполняется локально, как только сертификаты находятся в памяти; проверка отзыва добавляет один запрос OCSP или CRL на каждый сертификат в цепочке. Бюджет 1500 мс по реальному времени выполнения покрывает одну подпись с меткой времени при удалённой TSA на прогретом соединении. Проверка отзыва на медленной конечной точке превышает этот бюджет и должна выполняться за пределами пути обработки запроса. Профиль воспроизводимости — structural: метка времени встраивает момент подписания, поэтому два запуска отличаются байтами метки времени, тогда как структура документа идентична.

Это основная криптографическая граница движка, поэтому модель угроз задана явно. Движок сам вычисляет диапазон байтов и никогда не принимает его от вызывающей стороны. Путь подписания работает по принципу fail-closed: сбой примитива или нехватка возможностей вызывает типизированное исключение и никогда незаметно не понижает алгоритм до более слабого. На уровне, где требуется метка времени (B-T, B-LT, B-LTA), служба меток времени, возвращающая пустой токен, считается терминальным сбоем: подпись отклоняется, а не выпускается без метки времени в незаметно пониженном состоянии, если только обработчик сбоев не подключён для санкционирования документированного снижения. По замыслу доверие контролирует вызывающая сторона: якоря и политика отзыва являются входными данными, а не значениями движка по умолчанию, потому что сторона, сама утверждающая доверие, утверждала бы факт, который может установить только верификатор. Доверие к метке времени сводится к доверию к службе меток времени; она внедряется, чтобы развёртывание могло зафиксировать собственную. Эта страница помечена export_control_class: legal-review-required, потому что она касается криптографического подписания; каждый нормативный источник изложен своими словами и ни один не воспроизводится, согласно правилам безопасного цитирования.

УтверждениеСтандартПунктПодтверждение
Подпись CMS хранится в кодировке DER в записи Contents словаря подписи.ISO 32000-2§12.8.1
SignerInfo несёт подписанные атрибуты content-type и message-digest.RFC 5652§5.3
Верификатор заново вычисляет дайджест содержимого и сравнивает его с атрибутом message-digest.RFC 5652§5.4
Токен метки времени формирует RFC 3161 TSA; он несёт уникальный serialNumber и genTime в UTC.RFC 3161§2.4.1, §2.4.2,,
Проверка пути сертификации проверяет базовые ограничения и входные данные пути от подписанта до якоря доверия.RFC 5280§6.1,
OCSP сообщает certStatus как good, revoked или unknown; свежесть статуса ограничена thisUpdate / nextUpdate.RFC 6960§2.2, §4.2,

Все пункты изложены своими словами. NextPDF не воспроизводит нормативный текст. За авторитетной формулировкой обращайтесь к опубликованным стандартам.

Core поставляет программный подписант CMS (RSA, RSASSA-PSS, ECDSA, Ed25519), использование меток времени RFC 3161, проверку пути по RFC 5280 и проверку отзыва через OCSP / CRL. Хранение ключей в HSM и по стандарту Public-Key Cryptography Standards #11 (PKCS#11), отложенное и облачное подписание, создание PAdES B-LT и B-LTA, а также профиль криптографической политики Federal Information Processing Standards (FIPS) 140-3 поставляются в редакциях Pro и Enterprise. Core подключает их во время выполнения по контракту, поэтому движок с открытым исходным кодом не несёт коммерческой зависимости, а API не меняется при обновлении.