Enterprise edición
Firma con HSM — Referencia detallada
De un vistazo
Sección titulada «De un vistazo»Esta página es la referencia detallada de la superficie de firma con HSM de NextPDF Enterprise. Cubre tres tipos públicos. NextPDF\Enterprise\Security\Signature\Hsm\Pkcs11Signer firma a través de un token PKCS#11 mediante la extensión ext-pkcs11. NextPDF\Enterprise\Security\Signature\Hsm\OpenSslCliSigner firma a través del binario openssl en un subproceso, para claves respaldadas por un provider o un engine que PHP ext-openssl no puede cargar. NextPDF\Enterprise\Security\Signature\Hsm\Provider\HsmSignerProviderAdapter expone cualquiera de las dos implementaciones concretas como un SignerProviderInterface unificado. En todas las vías la clave privada permanece dentro del límite del token; NextPDF entrega los bytes que hay que firmar y recibe la firma. La vía poscuántica (signPqs) es una preview: está desactivada de forma predeterminada, no conlleva ninguna declaración de conformidad y no tiene una vía de verificación soportada en los validadores de PDF actuales. NextPDF no posee ninguna certificación ni la otorga; soporte no equivale a conformidad, y conformidad no equivale a certificación.
Disponibilidad y licencias
Sección titulada «Disponibilidad y licencias»Esta capacidad se distribuye en NextPDF Enterprise (nextpdf/enterprise) y se activa con un sobre de licencia de nivel Enterprise. Un despliegue sin esa titularidad no carga las clases de la capacidad. Comparar ediciones y obtener una licencia.
Superficie de API pública
Sección titulada «Superficie de API pública»Los tres tipos residen en NextPDF\Enterprise\Security\Signature\Hsm; el adaptador se encuentra en su subespacio de nombres Provider. Ambos firmantes implementan el contrato NextPDF\Contracts\HsmSignerInterface de Core.
| Símbolo | Parámetros | Comportamiento predeterminado | Devuelve | Lanza o falla con | Notas |
|---|---|---|---|---|---|
Pkcs11Signer::__construct() | string $libraryPath, int $slotId, string $pin, string $certLabel, ?string $keyLabel = null, array $chainDer = [], bool $enablePostQuantum = false, ?FipsSignatureEnforcer $fipsEnforcer = null | Abre la biblioteca del proveedor, inicia sesión en el slot y carga el certificado y los metadatos del algoritmo de clave desde el token | — | HsmOperationException cuando ext-pkcs11 no está presente o falla el acceso al token | Se almacena en caché un handle de módulo por ruta de biblioteca y por proceso; el PIN y las etiquetas son #[SensitiveParameter] |
Pkcs11Signer::sign() | string $data, string $algorithm = 'sha256WithRSAEncryption' | Firma en el token; la salida ECDSA en bruto se convierte a ECDSA-Sig-Value en DER | string con los bytes de la firma en bruto | HsmOperationException (clave no encontrada, fallo del token); InvalidArgumentException (algoritmo no mapeado); excepciones de la barrera FIPS antes de firmar cuando hay un enforcer conectado | Conjunto de algoritmos cerrado; véase el Contrato de comportamiento |
Pkcs11Signer::signPqs() | string $data, Pkcs11PqsAlgorithm $algorithm, string $context = '', bool $randomized = true | Rechazado salvo que se haya establecido $enablePostQuantum; despacha el mecanismo PQ provisional de PKCS#11 | string con los bytes de la firma en bruto | HsmOperationException (desactivado, fallo del token, discrepancia en la longitud de la firma); InvalidArgumentException (contexto de más de 255 bytes) | Preview; sin declaración de conformidad; los identificadores de mecanismo son provisionales |
Pkcs11Signer::isPostQuantumEnabled() | Ninguno | Informa del indicador de participación del constructor | bool | Ninguna | — |
Pkcs11Signer::getCertificateDer() | Ninguno | Devuelve el certificado del firmante leído desde el token | string (DER) | Ninguna | Se carga una vez durante la construcción |
Pkcs11Signer::getCertificateChainDer() | Ninguno | Devuelve los intermedios suministrados por el constructor | array<string> (DER) | Ninguna | Excluye el certificado del firmante |
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 | Verifica proc_open, sondea el binario y la versión, resuelve el backend y carga los certificados | — | HsmOperationException (proc_open desactivado, falta el archivo de módulo/config/certificado, fallo del binario, sin backend); InvalidArgumentException (pin-value dentro de $keyUri) | OpenSslCliBackend::Auto prefiere el provider de OpenSSL 3.x y luego el engine |
OpenSslCliSigner::sign() | string $data, string $algorithm = 'sha256WithRSAEncryption' | Ejecuta openssl dgst en un subproceso; de forma predeterminada el PIN viaja a través de un archivo pin-source efímero con permisos 0600 | string con los bytes de la firma en bruto | HsmOperationException (timeout, PIN rechazado, clave no encontrada, fallo al cargar el módulo, salida vacía, fallo del archivo de PIN); InvalidArgumentException (algoritmo no mapeado); excepciones de la barrera FIPS antes de firmar | El subproceso se termina tras $timeoutSeconds; stderr se redacta antes de llegar a los mensajes |
Superficie de accesores de OpenSslCliSigner | Ninguno | Resultados de construcción de solo lectura | string / array<string> / OpenSslCliBackend | Ninguna | getCertificateDer, getCertificateChainDer, getPublicKeyAlgorithm, getCertificatePem, getResolvedBackend, getOpensslVersion |
HsmSignerProviderAdapter::__construct() | HsmSignerInterface $hsm, string $providerId, SignatureAlgorithm $algorithm = SignatureAlgorithm::Pkcs1v15 | Envuelve una implementación concreta de HSM como un SignerProviderInterface | — | Ninguna | Convenciones de id de proveedor: pkcs11-{module-id}, openssl-cli |
HsmSignerProviderAdapter::providerId() | Ninguno | Devuelve el id suministrado por el constructor | non-empty-string | Ninguna | — |
HsmSignerProviderAdapter::supportsAlgorithm() | SignatureAlgorithm $algo | Mapea el enum a un nombre de estilo OpenSSL y luego lo interseca con el conjunto permitido del backend | bool | Ninguna | Rechaza los algoritmos de solo digest; los id openssl-engine no anuncian nada |
HsmSignerProviderAdapter::sign() | string $data, ?string $keyVersion = null | Despacha a través del firmante envuelto con el algoritmo configurado | non-empty-string | KeyManagementException ($keyVersion no nulo); SignatureFailedException (algoritmo no mapeable, fallo del driver, firma vacía) | Contrato SPI fail-closed; todo error del driver se expone tipado |
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): stringContrato de comportamiento
Sección titulada «Contrato de comportamiento»- Custodia de claves. La clave privada nunca abandona el límite del token.
Pkcs11Signerdelega la operación en el token;OpenSslCliSignerpasa una referencia a la clave — un URI PKCS#11 — al subprocesoopenssl. Ninguno de los dos firmantes puede exportar la clave. - Sesión e inicio de sesión.
Pkcs11Signeralmacena en caché un handle de módulo PKCS#11 por ruta de biblioteca y por proceso, porque la interfaz del token debe inicializarse exactamente una vez por proceso. Cada operación abre una sesión e inicia sesión con el PIN; el inicio de sesión autentica al usuario antes de cualquier uso de la clave privada (PKCS#11 v3.1 §5.6.8). Cuando el slot informa de un inicio de sesión existente, el firmante cierra la sesión y vuelve a iniciarla, de modo que los tokens que exigen un PIN nuevo por operación reciben uno. - Conjunto de algoritmos (cerrado). Ambos firmantes aceptan exactamente:
sha256WithRSAEncryption,sha384WithRSAEncryption,sha512WithRSAEncryption;RSASSA-PSS,RSASSA-PSS-SHA256,RSASSA-PSS-SHA384,RSASSA-PSS-SHA512;ecdsa-with-SHA256,ecdsa-with-SHA384,ecdsa-with-SHA512.Pkcs11Signeracepta ademásecdsa-raw. Cualquier otro identificador lanzaInvalidArgumentException: nunca se firma con un algoritmo sustituto. - Vinculación de la sal en PSS. En cada variante de PSS la longitud de la sal es igual a la longitud del digest — 32, 48 o 64 bytes — y los parámetros de hash y MGF coinciden con el digest elegido. Esto sigue la estructura de parámetros del mecanismo PSS, donde la longitud de la sal es normalmente la longitud del hash del mensaje (PKCS#11 v3.1 §6.1.9). Ambos firmantes aplican el mismo emparejamiento, de modo que una configuración válida en un backend es válida en el otro.
- Conversión de ECDSA. Un token devuelve una firma ECDSA como la concatenación en bruto, rellenada con ceros, de r y s (PKCS#11 v3.1 §6.3.1).
Pkcs11Signer::sign()convierte esa salida a la formaECDSA-Sig-Valuecodificada en DER que esperan los validadores de PDF y OpenSSL. Quien invoca nunca maneja la forma en bruto. - Entrega del PIN (vía CLI). En el valor predeterminado seguro, el PIN se escribe en un archivo efímero creado exclusivamente con permisos solo para el propietario, se referencia mediante el atributo
pin-sourcedel URI PKCS#11 y se desvincula tras la salida del subproceso. En este modo, el PIN no se coloca en la línea de comandos ni se exporta al entorno del subproceso. Con$legacyPinDelivery = true, el PIN se incrusta comopin-valueen el URI, lo que es observable en la línea de comandos del proceso; este modo es solo de participación explícita. - Disciplina del subproceso.
OpenSslCliSignerlanza el binario con un array de argumentos — sin interpolación de shell —, aplica$timeoutSeconds, termina el subproceso al expirar y clasifica stderr en errores tipados. Los secretos se redactan de stderr antes de citarlo en un mensaje de excepción. - Semántica del adaptador. Un token HSM no tiene un concepto gestionado de versión de clave; la clave del token es la versión. Por ello,
HsmSignerProviderAdapter::sign()rechaza cualquier$keyVersionno nulo conKeyManagementExceptionen lugar de ignorarlo.supportsAlgorithm()interseca el mapeo del enum con el conjunto aceptado del backend envuelto, de modo que el adaptador nunca anuncia un mecanismo que el backend rechazaría en el momento de firmar. Una firma vacía del driver lanzaSignatureFailedException. - Preview poscuántica.
signPqs()está protegida tras el indicador de constructor$enablePostQuantumy, de lo contrario, se niega a ejecutarse. La cadena de contexto está limitada a 255 bytes, coincidiendo con el límite de contexto de ML-DSA (FIPS 204). La firma devuelta debe coincidir con la longitud exacta en bytes del conjunto de parámetrosPkcs11PqsAlgorithmseleccionado, o la llamada falla. Los identificadores de mecanismo siguen una extensión PQ provisional de PKCS#11 y no son definitivos. Los perfiles PAdES no reconocen las suites poscuánticas, la mayoría de los validadores de PDF rechazan tales firmas y NextPDF no ofrece ninguna vía de verificación para ellas. No se declara ninguna conformidad.
Casos límite y modos de fallo
Sección titulada «Casos límite y modos de fallo»- Construir
Pkcs11Signersinext-pkcs11lanzaHsmOperationExceptionde inmediato; la extensión no viene incluida en las distribuciones estándar de PHP. - Una etiqueta de certificado o de clave privada que no coincide con ningún objeto del token lanza
HsmOperationExceptionindicando la clase de objeto que falta. La etiqueta de clave puede diferir legítimamente de la etiqueta de certificado en algunos tokens. - Los inicios de sesión fallidos repetidos pueden bloquear el PIN en el token; esa política la aplica el token, no NextPDF. Los tokens cuyas claves requieren autenticación en cada uso reciben un inicio de sesión nuevo mediante la vía de cierre de sesión y reintento (PKCS#11 v3.1, semántica always-authenticate).
OpenSslCliSignerrechaza en la construcción un$keyUrique ya contienepin-value, en modo fail-closed, porque esa entrega eludiría la vía segura del PIN.- En Windows, el modo seguro de archivo de PIN falla cerrado con
HsmOperationException: allí los bits de permisos de archivo no pueden restringir las concesiones de lectura de la ACL, por lo que el firmante se niega a dejar un PIN en texto claro en la ACL del directorio temporal. La entrega heredada del PIN es la alternativa documentada, de participación explícita, para hosts de Windows de confianza. - La detección automática del backend requiere OpenSSL 3.x para la vía del provider; LibreSSL nunca se resuelve al provider. Cuando no tiene éxito ni un sondeo de provider ni de engine, la construcción falla con
HsmOperationExceptionen lugar de posponer el fallo al momento de firmar. - Un subproceso que supera
$timeoutSecondsse termina y se reporta como timeout; un subproceso que sale limpiamente con salida vacía se reporta como un fallo de firma vacía. Ninguna de las dos condiciones puede producir un documento parcialmente firmado. - Una firma poscuántica cuya longitud en bytes no coincide con el conjunto de parámetros seleccionado se rechaza antes de que pueda llegar a la codificación CMS.
HsmSignerProviderAdaptercon el id de proveedor retiradoopenssl-engineno anuncia ningún algoritmo, de modo que una configuración obsoleta falla en la selección del proveedor en lugar de en el momento de firmar.
Comportamiento en modo FIPS
Sección titulada «Comportamiento en modo FIPS»Ambos firmantes aceptan un FipsSignatureEnforcer opcional. Cuando hay uno conectado, el modo FIPS está activo para ese firmante: sign() rechaza un algoritmo de firma no permitido o una clave por debajo del piso antes de que ocurra cualquier firma en el token o el subproceso. Los pisos siguen la tabla de generación de firmas — los módulos RSA por debajo de 2048 bits y los órdenes ECDSA por debajo de 224 bits no están permitidos (NIST SP 800-131A Rev.2 §3 Table 2). Sin enforcer, el comportamiento no cambia. La barrera cubre únicamente la vía clásica sign(); signPqs() se rige por su propio indicador de preview. Estas son declaraciones de capacidad sobre el código de NextPDF: la validación FIPS 140-3 se adhiere a un módulo criptográfico a través del CMVP, que en este despliegue es el HSM o el provider del operador — NextPDF no es un módulo validado, no posee ninguna certificación ni la otorga.
Conformidad
Sección titulada «Conformidad»| Declaración | Estándar | Cláusula |
|---|---|---|
| El inicio de sesión autentica al usuario ante el token antes de las operaciones con clave privada; un PIN incorrecto deniega el acceso. | PKCS#11 v3.1 | §5.6.8 |
| Las claves always-authenticate necesitan un inicio de sesión nuevo por uso; una reautenticación fallida repetida puede bloquear el PIN. | PKCS#11 v3.1 | CKA_ALWAYS_AUTHENTICATE re-authentication |
| Una firma ECDSA de un token es la concatenación en bruto r‖s; el firmante la convierte a DER para la interoperabilidad con PDF. | PKCS#11 v3.1 | §6.3.1 |
| Los parámetros PSS vinculan hash, MGF y longitud de sal; los firmantes establecen la sal igual a la longitud del digest. | PKCS#11 v3.1 | §6.1.9 |
| La barrera FIPS deniega la generación de firmas con RSA por debajo de 2048 bits u orden ECDSA por debajo de 224 bits. | NIST SP 800-131A Rev.2 | §3 Table 2 |
| La cadena de contexto poscuántica está limitada a 255 bytes. | FIPS 204 | HashML-DSA context handling |
| La validación FIPS 140-3 se adhiere a los módulos criptográficos a través del CMVP. | FIPS 140-3 | CMVP program scope |
Todas las cláusulas están parafraseadas; no se reproduce ningún texto normativo. NextPDF no realiza ninguna declaración de certificación. Los firmantes alinean su comportamiento con las cláusulas citadas como una capacidad. Que una firma producida se verifique es una decisión del verificador frente a sus anclas de confianza; la seguridad de la clave depende del token, del HSM y del operador — no solo de NextPDF.
Notas de desarrollo
Sección titulada «Notas de desarrollo»-
El mecanismo de entrega del PIN sigue la convención
pin-sourcedel URI PKCS#11 (RFC 7512); ese RFC está fuera del corpus citado, por lo que el comportamiento anterior se fundamenta en el código fuente del producto, no en una cita de especificación. -
Confirmar que el runtime carga
ext-pkcs11antes de construirPkcs11Signer; la construcción falla rápido cuando la extensión no está presente. El firmante por CLI necesitaproc_openhabilitado y un binarioopensslcon un provider o engine PKCS#11 instalado. -
El PIN, la etiqueta del certificado y la etiqueta de la clave son
#[SensitiveParameter], por lo que se excluyen de las trazas de pila. Suministrar el PIN desde un gestor de secretos; nunca escribirlo en el código fuente, en configuración incluida en el control de versiones ni en los registros. -
La construcción es el paso costoso en ambos firmantes: la vía PKCS#11 inicia sesión y lee el certificado, y la vía CLI sondea el binario y el backend. Construir una vez y reutilizar la instancia; la caché de módulo por biblioteca hace segura la construcción repetida contra la misma biblioteca.
-
Envolver un firmante en
HsmSignerProviderAdaptercuando quien invoca trabaja a través deSignerProviderInterface. Pasar el id de proveedor canónico de la clase envuelta —pkcs11-{module-id}uopenssl-cli— para que las comprobaciones de capacidad usen el conjunto permitido correcto del backend. -
Antes de habilitar la preview poscuántica, verificar los identificadores de mecanismo del firmware del token frente a los valores provisionales que NextPDF registra; una discrepancia falla en el momento de firmar. No habilitar la preview para la salida PAdES de producción.
-
getResolvedBackend()ygetOpensslVersion()existen para el registro de evidencias; persistirlos junto con la evidencia de firma cuando su programa de cumplimiento requiera reproducibilidad.
Véase también
Sección titulada «Véase también»- Firma con módulo de seguridad hardware (PKCS#11) — la página de la capacidad con los pasos de instalación, configuración y verificación.
- Seguridad — Referencia detallada — la superficie de seguridad combinada de Enterprise.
- Firma — Referencia detallada — el productor a largo plazo PAdES B-LT / B-LTA.
- FIPS 140 — Referencia detallada — la política criptográfica, la batería de autopruebas y la barrera
FipsSignatureEnforcer. - Preview de PQC — Referencia detallada — la superficie de la preview poscuántica y sus límites.
- Seguridad / Firma (Core) — el firmante CMS de Core y los contratos de firma.
Límite de publicación
Sección titulada «Límite de publicación»Esta página documenta únicamente el comportamiento observable externamente y la superficie de API pública soportada. Las rutas de espacios de nombres internas, las clases auxiliares, las tablas de mecanismos, los nombres de archivo de runbook y los prefijos de tickets quedan fuera del alcance.