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

Контракты / Подписание

В подсистему подписания входят шесть контрактов. Они определяют, как создавать подпись 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 ограничивает допустимые алгоритмы хеширования, подписи и шифрования, а также стойкость ключей до выполнения любой криптографической операции.

ТипВидКлючевые членыСтабильностьС версии
SignerInterfaceinterfacesign(string): SignatureResult, timestamp(string): string, supportsLtv(): boolстабильно1.0.0
HsmSignerInterfaceinterfacesign(string, string): string, getCertificateDer(), getCertificateChainDer(), getPublicKeyAlgorithm()стабильно1.0.0
DeferredSignerInterfaceinterfacesubmitForSigning(string): string, retrieveSignature(string), isComplete(string) (расширяет SignerInterface)экспериментально3.0.0
TimestampProviderInterfaceinterfacegetTimestamp(string): stringэкспериментально3.0.0
LtvManagerInterfaceinterfaceenableLtv(...), addDocumentTimestamp(...)стабильно1.10.0
CryptoPolicyInterfaceinterfaceisHashAlgorithmAllowed(), 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.

examples/contracts/signing-quickstart.php
<?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.

examples/contracts/signing-production.php
<?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, выбрасываемая подписантами.