Контракты / Подписание
В подсистему подписания входят шесть контрактов. Они определяют, как создавать подпись Cryptographic Message Syntax (CMS), применять метку времени Request for Comments (RFC) 3161, подписывать ключом аппаратного модуля безопасности (HSM) и включать долгосрочную проверку (LTV). Core публикует контракты; редакции Pro и Enterprise поставляют готовые реализации.
Установка
Заголовок раздела «Установка»composer require nextpdf/core:^3Концептуальный обзор
Заголовок раздела «Концептуальный обзор»Цифровая подпись в Portable Document Format (PDF) — это структура CMS SignedData, которая хранится в словаре подписи. Запись Contents содержит структуру в кодировке Distinguished Encoding Rules (DER). Запись ByteRange задаёт диапазоны байтов, входящие в дайджест. Дайджест охватывает весь файл, исключая само значение подписи; см. International Organization for Standardization (ISO) 32000-2 §12.8.1. Структура CMS соответствует RFC 5652 §5.1: версия, алгоритмы дайджеста, инкапсулированное содержимое и сведения о подписанте.
SignerInterface — основной контракт. Он создаёт CMS SignedData для диапазона байтов, применяет метку времени к значению подписи и сообщает, поддерживает ли он LTV. Он поддерживает базовые уровни PDF Advanced Electronic Signatures (PAdES) от B-B до B-LTA, как определено European Telecommunications Standards Institute (ETSI) EN 319 142. Каждый более высокий уровень добавляет проверочный материал. B-B содержит базовую подпись. B-T добавляет метку времени подписи. B-LT добавляет данные об отзыве. B-LTA добавляет архивную метку времени.
HsmSignerInterface использует для подписи ключ, хранящийся в аппаратном модуле безопасности. Закрытый ключ никогда не покидает границу аппаратного обеспечения. Контракт возвращает сертификат подписанта и цепочку сертификатов в DER-формате, чтобы слой CMS мог собрать структуру. DeferredSignerInterface расширяет SignerInterface для асинхронного подписания. Вызывающая сторона отправляет данные, получает идентификатор задания, проверяет статус до завершения и забирает результат. Используйте его, когда удалённый HSM или облачная служба ключей не возвращает результат немедленно.
TimestampProviderInterface запрашивает токен метки времени RFC 3161. Протокол работает по схеме запрос-ответ с Time-Stamping Authority (TSA); см. RFC 3161 §2.4. Поле messageImprint в токене — это хеш значения подписи; см. RFC 3161 §2.4.2. LtvManagerInterface включает LTV. Он собирает цепочку сертификатов, получает ответы Online Certificate Status Protocol (OCSP) и списки отзыва сертификатов (CRL), строит Document Security Store (DSS) и добавляет метку времени документа для B-LTA. CryptoPolicyInterface ограничивает допустимые алгоритмы хеширования, подписи и шифрования, а также стойкость ключей до выполнения любой криптографической операции.
Поверхность API
Заголовок раздела «Поверхность API»| Тип | Вид | Ключевые члены | Стабильность | С версии |
|---|---|---|---|---|
SignerInterface | interface | sign(string): SignatureResult, timestamp(string): string, supportsLtv(): bool | стабильно | 1.0.0 |
HsmSignerInterface | interface | sign(string, string): string, getCertificateDer(), getCertificateChainDer(), getPublicKeyAlgorithm() | стабильно | 1.0.0 |
DeferredSignerInterface | interface | submitForSigning(string): string, retrieveSignature(string), isComplete(string) (расширяет SignerInterface) | экспериментально | 3.0.0 |
TimestampProviderInterface | interface | getTimestamp(string): string | экспериментально | 3.0.0 |
LtvManagerInterface | interface | enableLtv(...), addDocumentTimestamp(...) | стабильно | 1.10.0 |
CryptoPolicyInterface | interface | isHashAlgorithmAllowed(), isSignatureAlgorithmAllowed(), isEncryptionAlgorithmAllowed(), isKeyStrengthAllowed(), getPreferredHashAlgorithm(), getName() | стабильно | 1.9.0 |
SignerInterface::sign() возвращает NextPDF\Security\Signature\SignatureResult. Публичное свойство $cmsSignedData содержит CMS в кодировке DER. Публичное свойство $digestHex содержит дайджест SHA-256. Публичное свойство $timestampToken содержит необязательный токен TSA. Методы доступа — toHex() / toHexPadded(int) / getSize() / hasTimestamp(). HsmSignerInterface::sign() для Rivest-Shamir-Adleman (RSA) возвращает байты в кодировке DER, а для Elliptic Curve Digital Signature Algorithm (ECDSA) — необработанные байты r‖s. В аргументе алгоритма используются идентификаторы OpenSSL.
Пример кода — быстрый старт
Заголовок раздела «Пример кода — быстрый старт»<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\SignerInterface;
/** * Sign a byte range with any SignerInterface implementation. * * @param SignerInterface $signer A core or Premium signer. * @param string $byteRange The PDF byte range to sign. * * @return string Hex-encoded CMS SignedData for the PDF /Contents field. */function signByteRange(SignerInterface $signer, string $byteRange): string{ $result = $signer->sign($byteRange);
return $result->toHex();}SignerInterface::sign() возвращает SignatureResult. toHex() возвращает шестнадцатеричную строку, которую writer помещает в поле /Contents; необработанные байты DER доступны в публичном свойстве $cmsSignedData. Функция зависит от контракта, а не от конкретного класса. Этому контракту соответствуют как тестовый подписант core, так и подписант Premium HSM.
Пример кода — рабочая версия
Заголовок раздела «Пример кода — рабочая версия»<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\CryptoPolicyInterface;use NextPDF\Contracts\SignerInterface;use NextPDF\Contracts\TimestampProviderInterface;use NextPDF\Exception\NextPdfException;use Psr\Log\LoggerInterface;
final readonly class TimestampedSigningService{ public function __construct( private SignerInterface $signer, private TimestampProviderInterface $timestamps, private CryptoPolicyInterface $policy, private LoggerInterface $logger, ) {}
/** * Sign a byte range, then timestamp the CMS structure. * * @param string $byteRange The PDF byte range to sign. * * @return array{cms: string, digest: string, tst: string} */ public function sign(string $byteRange): array { if (!$this->policy->isHashAlgorithmAllowed($this->policy->getPreferredHashAlgorithm())) { throw new \LogicException('Preferred hash rejected by crypto policy.'); }
try { $signature = $this->signer->sign($byteRange); $token = $this->timestamps->getTimestamp($signature->cmsSignedData);
return [ 'cms' => $signature->toHex(), 'digest' => $signature->digestHex, 'tst' => $token, ]; } catch (NextPdfException $e) { $this->logger->error('Signing failed', [ 'policy' => $this->policy->getName(), 'error' => $e->getMessage(), ]);
throw $e; } }}Сервис получает три контракта через внедрение зависимостей. Перед подписанием он проверяет криптополитику. SignatureResult предоставляет байты CMS в публичном свойстве $cmsSignedData, дайджест SHA-256 в $digestHex и шестнадцатеричную строку /Contents через toHex(). Поставщик меток времени получает байты CMS и возвращает токен в кодировке DER. Блок catch записывает в журнал имя политики и повторно выбрасывает исключение. Сбой никогда не подавляется.
Граничные случаи и подводные камни
Заголовок раздела «Граничные случаи и подводные камни»- Дайджест диапазона байтов должен исключать значение подписи. Дайджест, охватывающий запись
Contents, даёт непроверяемую подпись; см. ISO 32000-2 §12.8.1. SignerInterface::supportsLtv()сообщает о возможности, а не о состоянии. Подписант может поддерживать LTV и всё же создавать подпись B-B, когда служба меток времени не настроена.DeferredSignerInterface::retrieveSignature()возвращаетnull, пока задание не завершено. Сначала опрашивайтеisComplete(), чтобы не передавать полезную нагрузку при каждой проверке. После появления результата извлечение идемпотентно.LtvManagerInterface::addDocumentTimestamp()должен выполняться после записи Document Security Store. Если вызвать его первым, получится недопустимая структура B-LTA.CryptoPolicyInterfaceвозвращаетtrueдля каждого алгоритма, когда политика не задана. В регулируемой среде задайте явную политику; не полагайтесь на разрешающее значение по умолчанию.HsmSignerInterface::getCertificateChainDer()исключает сертификат подписанта. ИспользуйтеgetCertificateDer()для конечного сертификата подписанта, а метод цепочки — для промежуточных сертификатов.
Производительность
Заголовок раздела «Производительность»Стоимость подписания определяется прежде всего криптографической операцией и сетевым обменом, а не самим контрактом. Локальная программная подпись обычно занимает единицы миллисекунд. Подписание через HSM добавляет обмен с устройством. Метка времени требует сетевого обмена с Time-Stamping Authority. Долгосрочная проверка добавляет один запрос OCSP или CRL на каждый сертификат в цепочке. Параметр performance_budget в 1500 мс реального времени охватывает одну подпись с меткой времени от удалённого TSA при прогретом соединении. Долгосрочная проверка через медленную конечную точку отзыва выходит за этот бюджет и должна выполняться вне пути обработки запроса. Профиль воспроизводимости — structural, а не bitwise. Метка времени встраивает момент подписания, поэтому два запуска отличаются байтами метки времени, тогда как структура документа остаётся идентичной.
Замечания по безопасности
Заголовок раздела «Замечания по безопасности»Контракты подписания — основная криптографическая граница движка, поэтому модель угроз задана явно. Первый аспект — хранение ключа: HsmSignerInterface держит закрытый ключ внутри границы аппаратного обеспечения, и контракт никогда не раскрывает материал ключа. Второй аспект — понижение алгоритма: CryptoPolicyInterface блокирует слабые хеши и короткие ключи до выполнения операции, позволяя в развёртывании соблюдать профиль Federal Information Processing Standards (FIPS) 140-3 или electronic identification, authentication, and trust services (eIDAS) без форка движка. Третий аспект — доверие к метке времени: токен RFC 3161 заслуживает доверия ровно настолько, насколько доверен Time-Stamping Authority, поэтому контракт поставщика является внедряемым, а в развёртывании закрепляется свой доверенный Time-Stamping Authority. Четвёртый аспект — долгосрочная проверка: данные об отзыве получают во время подписания и хранят в Document Security Store, чтобы проверка оставалась возможной после истечения срока действия сертификата. Считайте любые входные данные подписанта недоверенными. Движок вычисляет диапазон байтов; он никогда не принимается от вызывающей стороны. Эта страница помечена export_control_class: legal-review-required, поскольку контракты управляют криптографическим подписанием. Текст пересказывает все нормативные источники и, согласно гигиене цитирования, не воспроизводит их дословно.
Соответствие
Заголовок раздела «Соответствие»| Утверждение | Стандарт | Пункт | Подтверждение |
|---|---|---|---|
Запись Contents словаря подписи хранит значение подписи как CMS SignedData в кодировке DER или как TimeStampToken. | ISO 32000-2 | §12.8.1 | |
Дайджест вычисляется по диапазону байтов, заданному массивом ByteRange, и исключает значение подписи. | ISO 32000-2 | §12.8.1 | , |
| Document Security Store содержит материал долгосрочной проверки с записями validation-related information (VRI), OCSP, CRL и сертификатами. | ISO 32000-2 | §12.8.4.3 | , |
| При долгосрочной проверке PAdES данные проверки размещаются в словарях DSS и VRI. | ETSI EN 319 142-2 | §6.3 | , |
Токен метки времени RFC 3161 связывает хеш значения подписи через messageImprint; обмен с TSA выполняется по схеме запрос-ответ. | RFC 3161 | §2.4.2, §2.4 | , |
| Последовательность CMS SignedData содержит версию, алгоритмы дайджеста, инкапсулированное содержимое и сведения о подписанте. | RFC 5652 | §5.1 |
Все пункты пересказаны. NextPDF не воспроизводит нормативный текст. За авторитетной формулировкой обращайтесь к опубликованным стандартам.
Коммерческий контекст
Заголовок раздела «Коммерческий контекст»Core публикует и фиксирует контракты подписания. Редакции Pro и Enterprise поставляют готовые реализации для HsmSignerInterface, LtvManagerInterface и отложенного подписанта, включая интеграцию с аппаратными средствами через Public-Key Cryptography Standards #11 (PKCS#11) и PAdES B-LT и B-LTA. Core находит эти реализации во время выполнения с помощью class_exists() и приводит их к контракту, поэтому движок с открытым исходным кодом не содержит коммерческой зависимости, а API не меняется при обновлении.
См. также
Заголовок раздела «См. также»- Contracts: 41 public interfaces (SPI): обзор интерфейсов поставщика услуг и уровней стабильности.
- Contracts / Security Policy: ограничение алгоритмов и ключей
CryptoPolicyInterface. - Contracts / Extraction: обеспечение соблюдения PDF/A, которое сочетается с подписанным архивированием.
- Security: реализация шифрования и подписи.
- Audit: аудит имени политики и событий подписания.
- Exception: иерархия
NextPdfException, выбрасываемая подписантами.