Ir al contenido
getnextpdf.com

Enterprise edición

Firma con HSM — Referencia detallada

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.

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.

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ímboloParámetrosComportamiento predeterminadoDevuelveLanza o falla conNotas
Pkcs11Signer::__construct()string $libraryPath, int $slotId, string $pin, string $certLabel, ?string $keyLabel = null, array $chainDer = [], bool $enablePostQuantum = false, ?FipsSignatureEnforcer $fipsEnforcer = nullAbre la biblioteca del proveedor, inicia sesión en el slot y carga el certificado y los metadatos del algoritmo de clave desde el tokenHsmOperationException cuando ext-pkcs11 no está presente o falla el acceso al tokenSe 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 DERstring con los bytes de la firma en brutoHsmOperationException (clave no encontrada, fallo del token); InvalidArgumentException (algoritmo no mapeado); excepciones de la barrera FIPS antes de firmar cuando hay un enforcer conectadoConjunto de algoritmos cerrado; véase el Contrato de comportamiento
Pkcs11Signer::signPqs()string $data, Pkcs11PqsAlgorithm $algorithm, string $context = '', bool $randomized = trueRechazado salvo que se haya establecido $enablePostQuantum; despacha el mecanismo PQ provisional de PKCS#11string con los bytes de la firma en brutoHsmOperationException (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()NingunoInforma del indicador de participación del constructorboolNinguna
Pkcs11Signer::getCertificateDer()NingunoDevuelve el certificado del firmante leído desde el tokenstring (DER)NingunaSe carga una vez durante la construcción
Pkcs11Signer::getCertificateChainDer()NingunoDevuelve los intermedios suministrados por el constructorarray<string> (DER)NingunaExcluye 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 = nullVerifica proc_open, sondea el binario y la versión, resuelve el backend y carga los certificadosHsmOperationException (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 0600string con los bytes de la firma en brutoHsmOperationException (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 firmarEl subproceso se termina tras $timeoutSeconds; stderr se redacta antes de llegar a los mensajes
Superficie de accesores de OpenSslCliSignerNingunoResultados de construcción de solo lecturastring / array<string> / OpenSslCliBackendNingunagetCertificateDer, getCertificateChainDer, getPublicKeyAlgorithm, getCertificatePem, getResolvedBackend, getOpensslVersion
HsmSignerProviderAdapter::__construct()HsmSignerInterface $hsm, string $providerId, SignatureAlgorithm $algorithm = SignatureAlgorithm::Pkcs1v15Envuelve una implementación concreta de HSM como un SignerProviderInterfaceNingunaConvenciones de id de proveedor: pkcs11-{module-id}, openssl-cli
HsmSignerProviderAdapter::providerId()NingunoDevuelve el id suministrado por el constructornon-empty-stringNinguna
HsmSignerProviderAdapter::supportsAlgorithm()SignatureAlgorithm $algoMapea el enum a un nombre de estilo OpenSSL y luego lo interseca con el conjunto permitido del backendboolNingunaRechaza los algoritmos de solo digest; los id openssl-engine no anuncian nada
HsmSignerProviderAdapter::sign()string $data, ?string $keyVersion = nullDespacha a través del firmante envuelto con el algoritmo configuradonon-empty-stringKeyManagementException ($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'): string
public function signPqs(string $data, Pkcs11PqsAlgorithm $algorithm, string $context = '', bool $randomized = true): string
public function isPostQuantumEnabled(): bool
public function getCertificateDer(): string
public function getCertificateChainDer(): array
public 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'): string
public function getCertificateDer(): string
public function getCertificateChainDer(): array
public function getPublicKeyAlgorithm(): string
public function getCertificatePem(): string
public function getResolvedBackend(): OpenSslCliBackend
public function getOpensslVersion(): string
public function __construct(private HsmSignerInterface $hsm, private string $providerId, private SignatureAlgorithm $algorithm = SignatureAlgorithm::Pkcs1v15)
public function providerId(): string
public function supportsAlgorithm(SignatureAlgorithm $algo): bool
public function sign(string $data, ?string $keyVersion = null): string
  • Custodia de claves. La clave privada nunca abandona el límite del token. Pkcs11Signer delega la operación en el token; OpenSslCliSigner pasa una referencia a la clave — un URI PKCS#11 — al subproceso openssl. Ninguno de los dos firmantes puede exportar la clave.
  • Sesión e inicio de sesión. Pkcs11Signer almacena 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. Pkcs11Signer acepta además ecdsa-raw. Cualquier otro identificador lanza InvalidArgumentException: 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 forma ECDSA-Sig-Value codificada 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-source del 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 como pin-value en 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. OpenSslCliSigner lanza 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 $keyVersion no nulo con KeyManagementException en 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 lanza SignatureFailedException.
  • Preview poscuántica. signPqs() está protegida tras el indicador de constructor $enablePostQuantum y, 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ámetros Pkcs11PqsAlgorithm seleccionado, 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.
  • Construir Pkcs11Signer sin ext-pkcs11 lanza HsmOperationException de 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 HsmOperationException indicando 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).
  • OpenSslCliSigner rechaza en la construcción un $keyUri que ya contiene pin-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 HsmOperationException en lugar de posponer el fallo al momento de firmar.
  • Un subproceso que supera $timeoutSeconds se 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.
  • HsmSignerProviderAdapter con el id de proveedor retirado openssl-engine no 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.

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.

DeclaraciónEstándarClá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.1CKA_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 204HashML-DSA context handling
La validación FIPS 140-3 se adhiere a los módulos criptográficos a través del CMVP.FIPS 140-3CMVP 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.

  • El mecanismo de entrega del PIN sigue la convención pin-source del 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-pkcs11 antes de construir Pkcs11Signer; la construcción falla rápido cuando la extensión no está presente. El firmante por CLI necesita proc_open habilitado y un binario openssl con 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 HsmSignerProviderAdapter cuando quien invoca trabaja a través de SignerProviderInterface. Pasar el id de proveedor canónico de la clase envuelta — pkcs11-{module-id} u openssl-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() y getOpensslVersion() existen para el registro de evidencias; persistirlos junto con la evidencia de firma cuando su programa de cumplimiento requiera reproducibilidad.

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.