Ir al contenido

Contratos / Firma

El dominio de firma contiene seis contratos. Definen cómo producir una firma CMS, aplicar una marca de tiempo RFC 3161, firmar con una clave de hardware y habilitar la validación a largo plazo. El núcleo expone los contratos; las ediciones Pro y Enterprise incluyen las implementaciones de producción.

Ventana de terminal
composer require nextpdf/core:^3

Una firma digital de PDF es una estructura CMS SignedData almacenada en el diccionario de firma. La entrada Contents contiene la estructura codificada en DER. La entrada ByteRange identifica los intervalos de bytes cubiertos por el resumen. El resumen cubre todo el archivo y excluye el propio valor de la firma; véase ISO 32000-2 §12.8.1. La estructura CMS sigue RFC 5652 §5.1: una secuencia de versión, algoritmos de resumen, contenido encapsulado e información del firmante.

SignerInterface es el contrato del núcleo. Produce el CMS SignedData para un rango de bytes, aplica una marca de tiempo a un valor de firma e informa si admite la validación a largo plazo. Cubre los niveles base de PAdES desde B-B hasta B-LTA, según se definen en ETSI EN 319 142. Cada nivel superior añade material adicional. B-B contiene la firma base. B-T añade una marca de tiempo de firma. B-LT añade datos de revocación. B-LTA añade una marca de tiempo de archivo.

HsmSignerInterface firma con una clave alojada en un Módulo de Seguridad de Hardware. La clave privada no sale del perímetro del hardware. El contrato devuelve el certificado del firmante y la cadena de certificados en formato DER para que la capa CMS pueda construir la estructura. DeferredSignerInterface extiende SignerInterface para la firma asíncrona. El invocador envía los datos, recibe un identificador de trabajo, consulta periódicamente hasta que se completa y luego recupera el resultado. Usar este contrato cuando un HSM remoto o un servicio de claves en la nube no devuelve un resultado de inmediato.

TimestampProviderInterface solicita un token de marca de tiempo RFC 3161. El protocolo consiste en una solicitud y una respuesta intercambiadas con una Autoridad de Sellado de Tiempo; véase RFC 3161 §2.4. El messageImprint del token es un hash del valor de la firma; RFC 3161 §2.4.2. LtvManagerInterface habilita la validación a largo plazo. Recopila la cadena de certificados, obtiene respuestas OCSP y CRL, construye el Document Security Store y añade una marca de tiempo de documento para B-LTA. CryptoPolicyInterface controla qué algoritmos de hash, firma y cifrado, así como qué fortalezas de clave, se permiten antes de que se ejecute cualquier operación criptográfica.

TipoClaseMiembros claveEstabilidadDesde
SignerInterfaceinterfacesign(string): SignatureResult, timestamp(string): string, supportsLtv(): boolestable1.0.0
HsmSignerInterfaceinterfacesign(string, string): string, getCertificateDer(), getCertificateChainDer(), getPublicKeyAlgorithm()estable1.0.0
DeferredSignerInterfaceinterfacesubmitForSigning(string): string, retrieveSignature(string), isComplete(string) (extiende SignerInterface)experimental3.0.0
TimestampProviderInterfaceinterfacegetTimestamp(string): stringexperimental3.0.0
LtvManagerInterfaceinterfaceenableLtv(...), addDocumentTimestamp(...)estable1.10.0
CryptoPolicyInterfaceinterfaceisHashAlgorithmAllowed(), isSignatureAlgorithmAllowed(), isEncryptionAlgorithmAllowed(), isKeyStrengthAllowed(), getPreferredHashAlgorithm(), getName()estable1.9.0

SignerInterface::sign() devuelve un NextPDF\Security\Signature\SignatureResult. La propiedad pública $cmsSignedData contiene el CMS codificado en DER. La propiedad pública $digestHex contiene el resumen SHA-256. La propiedad pública $timestampToken contiene el token TSA opcional. Los métodos de acceso son toHex() / toHexPadded(int) / getSize() / hasTimestamp(). HsmSignerInterface::sign() devuelve bytes codificados en DER para RSA y bytes r‖s en bruto para ECDSA. El argumento de algoritmo utiliza identificadores de 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() devuelve un SignatureResult. toHex() produce la cadena hexadecimal que el escritor coloca en el campo /Contents; los bytes DER en bruto están en la propiedad pública $cmsSignedData. La función depende del contrato, no de una clase concreta. Tanto un firmante de prueba del núcleo como un firmante con HSM de Premium satisfacen ese contrato.

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;
}
}
}

El servicio inyecta tres contratos. La política criptográfica se consulta antes de la operación de firma. SignatureResult expone los bytes CMS en la propiedad pública $cmsSignedData, el resumen SHA-256 en $digestHex y toHex() para la cadena hexadecimal de /Contents. El proveedor de marca de tiempo recibe los bytes CMS y devuelve un token codificado en DER. El bloque catch registra el nombre de la política y vuelve a lanzar la excepción. En ningún caso silencia el fallo.

  • El resumen del rango de bytes debe excluir el valor de la firma. Un resumen que cubre la entrada Contents produce una firma que no puede verificarse nunca; ISO 32000-2 §12.8.1.
  • SignerInterface::supportsLtv() informa sobre la capacidad, no sobre el estado. Un firmante puede admitir la validación a largo plazo y aun así producir una firma B-B cuando no hay configurado ningún servicio de marca de tiempo.
  • DeferredSignerInterface::retrieveSignature() devuelve null hasta que el trabajo se completa. Conviene consultar primero isComplete() para evitar transferir la carga útil en cada comprobación. La recuperación es idempotente una vez que existe un resultado.
  • LtvManagerInterface::addDocumentTimestamp() debe ejecutarse después de que se escriba el Document Security Store. Ejecutarlo primero produce una estructura B-LTA no válida.
  • CryptoPolicyInterface devuelve true para todos los algoritmos cuando no se establece ninguna política. En un entorno regulado, establecer una política explícita; no confiar en el valor predeterminado abierto.
  • HsmSignerInterface::getCertificateChainDer() excluye el certificado del firmante. Usar getCertificateDer() para el certificado hoja del firmante y el método de cadena para los intermedios.

El costo de la firma está dominado por la operación criptográfica y cualquier intercambio de red de ida y vuelta, no por el contrato. Una firma de software local tarda milisegundos de un solo dígito. Una firma con HSM añade el intercambio de ida y vuelta con el dispositivo. Una marca de tiempo añade un intercambio de red de ida y vuelta con la Autoridad de Sellado de Tiempo. La validación a largo plazo añade una obtención de OCSP o CRL por cada certificado de la cadena. El performance_budget de 1500 ms de tiempo de reloj cubre una única firma con marca de tiempo con una TSA remota en una conexión activa. La validación a largo plazo contra un punto de conexión de revocación lento lo supera y debería ejecutarse fuera de la ruta de la solicitud. El perfil de reproducibilidad es structural, no bitwise. Una marca de tiempo incrusta el instante de firma, por lo que dos ejecuciones difieren en los bytes de la marca de tiempo mientras la estructura del documento permanece idéntica.

Los contratos de firma son el límite criptográfico principal del motor, por lo que el modelo de amenazas es explícito. La custodia de claves es la primera preocupación: HsmSignerInterface mantiene la clave privada dentro del perímetro del hardware, y el contrato nunca expone el material de la clave. La degradación de algoritmos es la segunda: CryptoPolicyInterface bloquea los hashes débiles y las claves cortas antes de la operación, lo que permite que un despliegue aplique un perfil FIPS 140-3 o eIDAS sin bifurcar el motor. La confianza en la marca de tiempo es la tercera: un token RFC 3161 solo es tan confiable como la Autoridad de Sellado de Tiempo, por lo que el contrato del proveedor es inyectable y cada despliegue fija su propia autoridad. La validación a largo plazo es la cuarta: el material de revocación se obtiene en el momento de la firma y se almacena en el Document Security Store para que la verificación sobreviva a la expiración del certificado. Tratar como no confiable toda entrada del firmante. El motor calcula el rango de bytes; nunca se acepta del invocador. Esta página está marcada como export_control_class: legal-review-required porque los contratos rigen la firma criptográfica. La prosa parafrasea todas las fuentes normativas y no cita ninguna, conforme a la higiene de citas.

AfirmaciónEstándarCláusulaEvidencia
El valor de la firma se almacena codificado en DER en la entrada Contents del diccionario de firma, como CMS SignedData o un TimeStampToken.ISO 32000-2§12.8.1
El resumen se calcula sobre el rango de bytes definido por la matriz ByteRange y excluye el valor de la firma.ISO 32000-2§12.8.1,
El material de validación a largo plazo se transporta en el Document Security Store con entradas VRI, OCSP, CRL y de certificado.ISO 32000-2§12.8.4.3,
La validación a largo plazo de PAdES coloca los datos de validación en los diccionarios DSS y VRI.ETSI EN 319 142-2§6.3,
Un token de marca de tiempo RFC 3161 vincula un hash del valor de la firma a través de messageImprint, intercambiado mediante una solicitud y una respuesta TSA.RFC 3161§2.4.2, §2.4,
La secuencia CMS SignedData contiene la versión, los algoritmos de resumen, el contenido encapsulado y la información del firmante.RFC 5652§5.1

Todas las cláusulas están parafraseadas. NextPDF no reproduce texto normativo. Consultar los estándares publicados para conocer la redacción autorizada.

El núcleo expone y congela los contratos de firma. Las implementaciones de producción de HsmSignerInterface, LtvManagerInterface y el firmante diferido se incluyen en las ediciones Pro y Enterprise, junto con la integración de hardware PKCS#11 y PAdES B-LT y B-LTA. El núcleo las resuelve en tiempo de ejecución con class_exists() y las adapta al contrato, de modo que el motor de código abierto no incorpora ninguna dependencia comercial y la API no cambia al actualizar.