Enterprise редакция
Подпись через HSM — глубокий справочник
Эта страница — глубокий справочник по поверхности подписи через HSM в NextPDF Enterprise. Она охватывает три публичных типа. NextPDF\Enterprise\Security\Signature\Hsm\Pkcs11Signer подписывает через токен PKCS#11 с помощью расширения ext-pkcs11. NextPDF\Enterprise\Security\Signature\Hsm\OpenSslCliSigner подписывает через бинарник openssl в подпроцессе — для ключей за провайдером или движком, которые PHP ext-openssl загрузить не может. NextPDF\Enterprise\Security\Signature\Hsm\Provider\HsmSignerProviderAdapter предоставляет любой из конкретных типов как единый SignerProviderInterface. На любом пути закрытый ключ остаётся внутри границы токена; NextPDF передаёт байты для подписи и получает подпись. Постквантовый путь (signPqs) — это предпросмотр: по умолчанию отключён, не несёт заявления о соответствии и не имеет поддерживаемого пути проверки в текущих валидаторах PDF. NextPDF не имеет сертификации и не предоставляет её; поддержка не равна соответствию, а соответствие не равно сертификации.
Доступность и лицензирование
Заголовок раздела «Доступность и лицензирование»Эта возможность поставляется в составе NextPDF Enterprise (nextpdf/enterprise) и активируется лицензионным конвертом уровня Enterprise. Развёртывание без этого права не загружает классы этой возможности. Сравните редакции и получите лицензию.
Публичная поверхность API
Заголовок раздела «Публичная поверхность API»Все три типа находятся в NextPDF\Enterprise\Security\Signature\Hsm; адаптер расположен в его подпространстве имён Provider. Оба подписанта реализуют контракт Core NextPDF\Contracts\HsmSignerInterface.
| Символ | Параметры | Поведение по умолчанию | Возвращает | Бросает или отказывает с | Примечания |
|---|---|---|---|---|---|
Pkcs11Signer::__construct() | string $libraryPath, int $slotId, string $pin, string $certLabel, ?string $keyLabel = null, array $chainDer = [], bool $enablePostQuantum = false, ?FipsSignatureEnforcer $fipsEnforcer = null | Открывает библиотеку производителя, входит в слот и загружает сертификат и метаданные алгоритма ключа с токена | — | HsmOperationException, когда ext-pkcs11 отсутствует или доступ к токену не удаётся | Один дескриптор модуля кэшируется на путь библиотеки на процесс; PIN и метки помечены #[SensitiveParameter] |
Pkcs11Signer::sign() | string $data, string $algorithm = 'sha256WithRSAEncryption' | Подписывает на токене; сырой вывод ECDSA преобразуется в DER ECDSA-Sig-Value | string сырые байты подписи | HsmOperationException (ключ не найден, отказ токена); InvalidArgumentException (неотображённый алгоритм); исключения FIPS-барьера до подписи, когда подключён enforcer | Закрытый набор алгоритмов; см. Контракт поведения |
Pkcs11Signer::signPqs() | string $data, Pkcs11PqsAlgorithm $algorithm, string $context = '', bool $randomized = true | Отклоняется, если не был задан $enablePostQuantum; диспетчеризует предварительный PQ-механизм PKCS#11 | string сырые байты подписи | HsmOperationException (отключён, отказ токена, несоответствие длины подписи); InvalidArgumentException (контекст свыше 255 байт) | Предпросмотр; без заявления о соответствии; идентификаторы механизмов предварительные |
Pkcs11Signer::isPostQuantumEnabled() | Нет | Сообщает флаг подключения, заданный в конструкторе | bool | Нет | — |
Pkcs11Signer::getCertificateDer() | Нет | Возвращает сертификат подписанта, прочитанный с токена | string (DER) | Нет | Загружается один раз при конструировании |
Pkcs11Signer::getCertificateChainDer() | Нет | Возвращает промежуточные сертификаты, переданные в конструктор | array<string> (DER) | Нет | Исключает сертификат подписанта |
OpenSslCliSigner::__construct() | string $keyUri, string $certPath, string $pin, array $extraCertPaths = [], OpenSslCliBackend $backend = OpenSslCliBackend::Auto, string $opensslBinary = 'openssl', int $timeoutSeconds = 30, ?string $modulePath = null, ?string $configPath = null, bool $legacyPinDelivery = false, ?FipsSignatureEnforcer $fipsEnforcer = null | Проверяет proc_open, зондирует бинарник и версию, определяет бэкенд и загружает сертификаты | — | HsmOperationException (proc_open отключён, отсутствует файл модуля/конфигурации/сертификата, сбой бинарника, нет бэкенда); InvalidArgumentException (pin-value внутри $keyUri) | OpenSslCliBackend::Auto предпочитает провайдер OpenSSL 3.x, затем движок |
OpenSslCliSigner::sign() | string $data, string $algorithm = 'sha256WithRSAEncryption' | Запускает openssl dgst в подпроцессе; по умолчанию PIN проходит через эфемерный файл-источник PIN с правами 0600 | string сырые байты подписи | HsmOperationException (тайм-аут, PIN отклонён, ключ не найден, сбой загрузки модуля, пустой вывод, сбой pin-файла); InvalidArgumentException (неотображённый алгоритм); исключения FIPS-барьера до подписи | Подпроцесс завершается после $timeoutSeconds; stderr редактируется прежде, чем попадёт в сообщения |
Поверхность аксессоров OpenSslCliSigner | Нет | Результаты конструирования только для чтения | string / array<string> / OpenSslCliBackend | Нет | getCertificateDer, getCertificateChainDer, getPublicKeyAlgorithm, getCertificatePem, getResolvedBackend, getOpensslVersion |
HsmSignerProviderAdapter::__construct() | HsmSignerInterface $hsm, string $providerId, SignatureAlgorithm $algorithm = SignatureAlgorithm::Pkcs1v15 | Оборачивает конкретный HSM как SignerProviderInterface | — | Нет | Соглашения об id провайдера: pkcs11-{module-id}, openssl-cli |
HsmSignerProviderAdapter::providerId() | Нет | Возвращает id, заданный в конструкторе | non-empty-string | Нет | — |
HsmSignerProviderAdapter::supportsAlgorithm() | SignatureAlgorithm $algo | Отображает перечисление на имя в стиле OpenSSL, затем пересекает с allow-набором бэкенда | bool | Нет | Отклоняет алгоритмы только для дайджеста; id openssl-engine не объявляют ничего |
HsmSignerProviderAdapter::sign() | string $data, ?string $keyVersion = null | Диспетчеризует через обёрнутый подписант с настроенным алгоритмом | non-empty-string | KeyManagementException (ненулевой $keyVersion); SignatureFailedException (неотображаемый алгоритм, сбой драйвера, пустая подпись) | Отказоустойчивый (fail-closed) контракт SPI; каждая ошибка драйвера всплывает типизированной |
public function __construct(private readonly string $libraryPath, private readonly int $slotId, #[SensitiveParameter] private readonly string $pin, #[SensitiveParameter] private readonly string $certLabel, #[SensitiveParameter] private readonly ?string $keyLabel = null, array $chainDer = [], private readonly bool $enablePostQuantum = false, ?FipsSignatureEnforcer $fipsEnforcer = null)public function sign(string $data, string $algorithm = 'sha256WithRSAEncryption'): stringpublic function signPqs(string $data, Pkcs11PqsAlgorithm $algorithm, string $context = '', bool $randomized = true): stringpublic function isPostQuantumEnabled(): boolpublic function getCertificateDer(): stringpublic function getCertificateChainDer(): arraypublic function __construct(private string $keyUri, string $certPath, #[SensitiveParameter] private string $pin, array $extraCertPaths = [], private OpenSslCliBackend $backend = OpenSslCliBackend::Auto, private string $opensslBinary = 'openssl', private int $timeoutSeconds = 30, private ?string $modulePath = null, private ?string $configPath = null, private bool $legacyPinDelivery = false, private ?FipsSignatureEnforcer $fipsEnforcer = null)public function sign(string $data, string $algorithm = 'sha256WithRSAEncryption'): stringpublic function getCertificateDer(): stringpublic function getCertificateChainDer(): arraypublic function getPublicKeyAlgorithm(): stringpublic function getCertificatePem(): stringpublic function getResolvedBackend(): OpenSslCliBackendpublic function getOpensslVersion(): stringpublic function __construct(private HsmSignerInterface $hsm, private string $providerId, private SignatureAlgorithm $algorithm = SignatureAlgorithm::Pkcs1v15)public function providerId(): stringpublic function supportsAlgorithm(SignatureAlgorithm $algo): boolpublic function sign(string $data, ?string $keyVersion = null): stringКонтракт поведения
Заголовок раздела «Контракт поведения»- Хранение ключа. Закрытый ключ никогда не покидает границу токена.
Pkcs11Signerделегирует операцию токену;OpenSslCliSignerпередаёт ссылку на ключ — URI PKCS#11 — подпроцессуopenssl. Ни один из подписантов не может экспортировать ключ. - Сессия и вход.
Pkcs11Signerкэширует один дескриптор модуля PKCS#11 на путь библиотеки на процесс, поскольку интерфейс токена должен инициализироваться ровно один раз на процесс. Каждая операция открывает сессию и входит по PIN; вход аутентифицирует пользователя перед любым использованием закрытого ключа (PKCS#11 v3.1 §5.6.8). Когда слот сообщает о существующем входе, подписант выходит и входит снова, поэтому токены, требующие свежий PIN на каждую операцию, его получают. - Набор алгоритмов (закрытый). Оба подписанта принимают ровно:
sha256WithRSAEncryption,sha384WithRSAEncryption,sha512WithRSAEncryption;RSASSA-PSS,RSASSA-PSS-SHA256,RSASSA-PSS-SHA384,RSASSA-PSS-SHA512;ecdsa-with-SHA256,ecdsa-with-SHA384,ecdsa-with-SHA512.Pkcs11Signerдополнительно принимаетecdsa-raw. Любой другой идентификатор вызываетInvalidArgumentException— никакой заменяющий алгоритм никогда не подписывается. - Привязка соли PSS. Для каждого варианта PSS длина соли равна длине дайджеста — 32, 48 или 64 байта — а параметры хеша и MGF соответствуют выбранному дайджесту. Это следует из структуры параметров механизма PSS, где длина соли обычно равна длине хеша сообщения (PKCS#11 v3.1 §6.1.9). Оба подписанта применяют одну и ту же пару, поэтому конфигурация, действительная на одном бэкенде, действительна и на другом.
- Преобразование ECDSA. Токен возвращает подпись ECDSA как сырую конкатенацию r и s с дополнением нулями (PKCS#11 v3.1 §6.3.1).
Pkcs11Signer::sign()преобразует этот вывод в DER-кодированную формуECDSA-Sig-Value, которую ожидают валидаторы PDF и OpenSSL. Вызывающая сторона никогда не работает с сырой формой. - Доставка PIN (путь CLI). В безопасном режиме по умолчанию PIN записывается в эфемерный файл, создаваемый исключительно с правами только для владельца, на который ссылается атрибут
pin-sourceURI PKCS#11, и удаляется после завершения подпроцесса. В этом режиме PIN не помещается в командную строку и не экспортируется в окружение подпроцесса. При$legacyPinDelivery = truePIN встраивается какpin-valueв URI, что наблюдаемо в командной строке процесса; этот режим только по явному согласию. - Дисциплина подпроцесса.
OpenSslCliSignerзапускает бинарник с массивом аргументов — без интерполяции оболочкой — применяет$timeoutSeconds, завершает подпроцесс по истечении и классифицирует stderr в типизированные ошибки. Секреты редактируются из stderr прежде, чем он цитируется в сообщении исключения. - Семантика адаптера. У токена HSM нет концепции управляемой версии ключа; ключ на токене и есть версия. Поэтому
HsmSignerProviderAdapter::sign()отклоняет любой ненулевой$keyVersionсKeyManagementException, а не игнорирует его.supportsAlgorithm()пересекает отображение перечисления с принятым набором обёрнутого бэкенда, поэтому адаптер никогда не объявляет механизм, который бэкенд отверг бы во время подписи. Пустая подпись от драйвера вызываетSignatureFailedException. - Постквантовый предпросмотр.
signPqs()закрыт за флагом конструктора$enablePostQuantumи в противном случае отказывается работать. Строка контекста ограничена 255 байтами, что соответствует границе контекста ML-DSA (FIPS 204). Возвращённая подпись должна точно совпадать по длине в байтах с выбранным набором параметровPkcs11PqsAlgorithm, иначе вызов завершается неудачей. Идентификаторы механизмов следуют предварительному PQ-расширению PKCS#11 и не являются окончательными. Профили PAdES не распознают постквантовые наборы, большинство валидаторов PDF отвергают такие подписи, и NextPDF не предоставляет для них пути проверки. Соответствие не заявляется.
Крайние случаи и режимы отказа
Заголовок раздела «Крайние случаи и режимы отказа»- Конструирование
Pkcs11Signerбезext-pkcs11немедленно вызываетHsmOperationException; это расширение не входит в стандартные дистрибутивы PHP. - Метка сертификата или закрытого ключа, не совпадающая ни с одним объектом на токене, вызывает
HsmOperationExceptionс указанием отсутствующего класса объекта. Метка ключа на некоторых токенах может законно отличаться от метки сертификата. - Повторные неудачные входы могут заблокировать PIN на токене; эту политику применяет токен, а не NextPDF. Токены, ключи которых требуют аутентификации при каждом использовании, получают свежий вход через путь выход-и-повтор (PKCS#11 v3.1, семантика always-authenticate).
OpenSslCliSignerотказывается от$keyUri, уже содержащегоpin-value, при конструировании, отказоустойчиво (fail-closed), потому что такая доставка обошла бы безопасный путь PIN.- В Windows безопасный режим pin-файла отказоустойчиво завершается с
HsmOperationException: биты прав файла там не могут ограничить разрешения на чтение ACL, поэтому подписант отказывается оставлять PIN в открытом виде под ACL временного каталога. Устаревшая доставка PIN — документированная альтернатива по явному согласию для доверенных хостов Windows. - Автоопределение бэкенда требует OpenSSL 3.x для пути провайдера; LibreSSL никогда не разрешается в провайдер. Когда не удаётся ни зондирование провайдера, ни движка, конструирование завершается с
HsmOperationException, а не откладывает отказ до момента подписи. - Подпроцесс, превысивший
$timeoutSeconds, завершается и сообщается как тайм-аут; подпроцесс, завершившийся чисто с пустым выводом, сообщается как отказ по пустой подписи. Ни одно из этих условий не может породить частично подписанный документ. - Постквантовая подпись, длина которой в байтах не совпадает с выбранным набором параметров, отклоняется прежде, чем сможет достичь CMS-кодирования.
HsmSignerProviderAdapterс выведенным из обихода id провайдераopenssl-engineне объявляет алгоритмов, поэтому устаревшая конфигурация отказывает при выборе провайдера, а не во время подписи.
Поведение в режиме FIPS
Заголовок раздела «Поведение в режиме FIPS»Оба подписанта принимают необязательный FipsSignatureEnforcer. Когда он подключён, режим FIPS активен для этого подписанта: sign() отвергает недопустимый алгоритм подписи или ключ ниже порога до какой-либо подписи на токене или в подпроцессе. Пороги следуют таблице генерации подписи — модули RSA менее 2048 бит и порядки ECDSA менее 224 бит недопустимы (NIST SP 800-131A Rev.2 §3 Table 2). Без enforcer поведение не меняется. Барьер покрывает только классический путь sign(); signPqs() управляется собственным флагом предпросмотра. Это заявления о возможностях кода NextPDF: валидация FIPS 140-3 привязывается к криптографическому модулю через CMVP, которым в этом развёртывании является HSM или провайдер оператора — NextPDF не является валидированным модулем, не имеет сертификации и не предоставляет её.
Соответствие
Заголовок раздела «Соответствие»| Заявление | Стандарт | Пункт |
|---|---|---|
| Вход аутентифицирует пользователя на токене перед операциями с закрытым ключом; неверный PIN отказывает в доступе. | PKCS#11 v3.1 | §5.6.8 |
| Ключи always-authenticate требуют свежий вход на каждое использование; повторная неудачная повторная аутентификация может заблокировать PIN. | PKCS#11 v3.1 | CKA_ALWAYS_AUTHENTICATE re-authentication |
| Подпись ECDSA токена — это сырая конкатенация r‖s; подписант преобразует её в DER для совместимости с PDF. | PKCS#11 v3.1 | §6.3.1 |
| Параметры PSS связывают хеш, MGF и длину соли; подписанты задают длину соли равной длине дайджеста. | PKCS#11 v3.1 | §6.1.9 |
| FIPS-барьер отказывает в генерации подписи с RSA менее 2048 бит или порядком ECDSA менее 224 бит. | NIST SP 800-131A Rev.2 | §3 Table 2 |
| Постквантовая строка контекста ограничена 255 байтами. | FIPS 204 | HashML-DSA context handling |
| Валидация FIPS 140-3 привязывается к криптографическим модулям через CMVP. | FIPS 140-3 | CMVP program scope |
Все пункты изложены в пересказе; нормативный текст не воспроизводится. NextPDF не делает заявления о сертификации. Подписанты согласуют своё поведение с цитируемыми пунктами как возможность. Проверяется ли произведённая подпись — решение проверяющей стороны относительно её якорей доверия; безопасность ключа зависит от токена, HSM и оператора, а не только от NextPDF.
Заметки по разработке
Заголовок раздела «Заметки по разработке»-
Механизм доставки PIN следует соглашению
pin-sourceURI PKCS#11 (RFC 7512); этот RFC вне цитируемого корпуса, поэтому описанное выше поведение обосновано исходным кодом продукта, а не цитатой из спецификации. -
Убедитесь, что среда выполнения загружает
ext-pkcs11перед конструированиемPkcs11Signer; конструирование быстро отказывает, когда расширение отсутствует. Подписанту CLI нужен включённыйproc_openи установленный бинарникopensslс провайдером или движком PKCS#11. -
PIN, метка сертификата и метка ключа помечены
#[SensitiveParameter], поэтому они исключаются из трассировок стека. Поставляйте PIN из менеджера секретов; никогда не записывайте его в исходный код, конфигурацию под контролем версий или логи. -
Конструирование — дорогой шаг у обоих подписантов: путь PKCS#11 входит и читает сертификат, а путь CLI зондирует бинарник и бэкенд. Конструируйте один раз и переиспользуйте экземпляр; кэш модуля на библиотеку делает повторное конструирование против той же библиотеки безопасным.
-
Оборачивайте подписант в
HsmSignerProviderAdapter, когда вызывающая сторона работает черезSignerProviderInterface. Передавайте канонический id провайдера для обёрнутого класса —pkcs11-{module-id}илиopenssl-cli— чтобы проверки возможностей использовали правильный allow-набор бэкенда. -
Перед включением постквантового предпросмотра сверьте идентификаторы механизмов прошивки токена с предварительными значениями, которые регистрирует NextPDF; несоответствие отказывает во время подписи. Не включайте предпросмотр для промышленного вывода PAdES.
-
getResolvedBackend()иgetOpensslVersion()существуют для записи доказательств; сохраняйте их вместе с доказательствами подписи, когда ваша программа соответствия требует воспроизводимости.
См. также
Заголовок раздела «См. также»- Подпись через аппаратный модуль безопасности (PKCS#11) — страница возможности с шагами настройки, конфигурации и проверки.
- Безопасность — глубокий справочник — объединённая поверхность безопасности Enterprise.
- Подпись — глубокий справочник — производитель долговременных подписей PAdES B-LT / B-LTA.
- FIPS 140 — глубокий справочник — криптополитика, батарея самотестов и барьер
FipsSignatureEnforcer. - Предпросмотр PQC — глубокий справочник — поверхность постквантового предпросмотра и её границы.
- Безопасность / Подпись (Core) — CMS-подписант Core и контракты подписи.
Граница публикации
Заголовок раздела «Граница публикации»Эта страница документирует только внешне наблюдаемое поведение и поддерживаемую публичную поверхность API. Внутренние пути пространств имён, вспомогательные классы, таблицы механизмов, имена файлов runbook и префиксы тикетов вне области рассмотрения.