Ir al contenido

Seguridad y firma: CMS, marca de tiempo RFC 3161, LTV y confianza

Esta página describe la superficie de firma incluida en NextPDF Core: producir una firma CMS, aplicar una marca de tiempo RFC 3161, validar una cadena de certificados frente a RFC 5280 y comprobar la revocación mediante OCSP y CRL. Se mantiene en el plano del comportamiento. Las clases de implementación de Core son internas: el código de producción consume el contrato SignerInterface, no los tipos concretos NextPDF\Security\Signature. Que una firma producida se verifique lo decide el verificador y las anclas de confianza con las que esté configurado. Ese resultado queda fuera del control del productor, y esta página lo señala en los puntos pertinentes.

Ventana de terminal
composer require nextpdf/core:^3

Core construye una estructura CMS SignedData a partir del intervalo de bytes y luego la almacena codificada en DER en la entrada Contents del diccionario de firma: ISO 32000-2 §12.8.1. La estructura incluye atributos firmados de SignerInfo —entre ellos content-type y message-digest—: RFC 5652 §5.3. Un verificador recalcula el resumen del contenido y lo compara con el atributo message-digest. La comparación debe coincidir para que la firma sea válida: RFC 5652 §5.4. SignerInfo también incluye el identificador del algoritmo de resumen y el bloque de atributos firmados: RFC 5652 §5. En las rutas de software incluidas, Core firma mediante phpseclib3: RSA, RSASSA-PSS, ECDSA y Ed25519.

Una marca de tiempo RFC 3161 consiste en un intercambio de petición y respuesta con una autoridad de sellado de tiempo que devuelve una estructura TSTInfo: RFC 3161 §2.4.1. Cada token lleva un serialNumber único de la TSA emisora —RFC 3161 §2.4.2— y un genTime expresado en UTC, que indica el instante en que se creó el token: RFC 3161 §2.4.2.

La validación de confianza consta de dos comprobaciones. La validación de ruta recorre desde el certificado del firmante hasta un ancla de confianza, comprobando las restricciones básicas y las entradas de construcción de ruta: RFC 5280 §6.1. La comprobación de revocación consulta a un respondedor OCSP o lee una CRL: una respuesta OCSP informa good, revoked o unknown —RFC 6960 §2.2— y los campos thisUpdate y nextUpdate de la respuesta delimitan la frescura de ese estado: RFC 6960 §4.2. Las anclas de confianza y la política de frescura de la revocación las proporciona el llamador. El motor valida con lo que recibe y no incluye una lista de confianza integrada.

TipoClaseRolEstabilidadDesde
SignerInterfaceinterfaz (NextPDF\Contracts)Contrato de firma del que dependen los llamadoresestable1.0.0
SignatureLevelenumSelector de nivel PAdES y sonda de disponibilidadestable1.0.0
Rfc5280PathValidatorinterfazPunto de entrada de la validación de ruta de certificación (validate(...))estable (congelado en 3.1.0)3.1.0
RevocationStatusenumResultado de OCSP / CRL: good, revoked, unknownestable3.1.0
CaTrustAnchorBundletipoConjunto de anclas de confianza proporcionado por el llamadorestable3.1.0
TstInfotipoCampos analizados de marca de tiempo RFC 3161estable3.2.0

SignerInterface::sign() devuelve un SignatureResult cuyo toHex() produce la cadena hexadecimal de /Contents y cuya propiedad cmsSignedData contiene los bytes DER en bruto. Las clases concretas NextPDF\Security\Signature que implementan estos comportamientos son internas (stability: internal en el manifiesto del módulo); no forman parte de la API pública y pueden cambiar sin un incremento de versión mayor. La integración debe depender de los contratos y de los enums anteriores.

examples/contracts/signing-quickstart.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\SignerInterface;
/**
* Produce the CMS SignedData hex for a PDF /Contents field.
*
* @param SignerInterface $signer A Core or Premium signer.
* @param string $byteRange The PDF byte range to sign.
*
* @return string Hex-encoded CMS SignedData.
*/
function sign(SignerInterface $signer, string $byteRange): string
{
return $signer->sign($byteRange)->toHex();
}

El llamador depende del contrato. Un firmante de software de Core y un firmante con HSM de Premium satisfacen ambos SignerInterface, por lo que este código no cambia entre ediciones.

examples/contracts/signing-trust.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\SignerInterface;
use NextPDF\Contracts\TimestampProviderInterface;
use NextPDF\Exception\NextPdfException;
use Psr\Log\LoggerInterface;
final readonly class TimestampedSigner
{
public function __construct(
private SignerInterface $signer,
private TimestampProviderInterface $timestamps,
private LoggerInterface $logger,
) {}
/**
* Sign a byte range, then timestamp the CMS structure.
*
* The timestamp's trust still depends on the verifier accepting the
* Time-Stamping Authority; this method only produces the structure.
*
* @param string $byteRange The PDF byte range to sign.
*
* @return array{cms: string, tst: string}
*/
public function sign(string $byteRange): array
{
try {
$signature = $this->signer->sign($byteRange);
$token = $this->timestamps->getTimestamp($signature->cmsSignedData);
return ['cms' => $signature->toHex(), 'tst' => $token];
} catch (NextPdfException $e) {
$this->logger->error('Signing failed', ['error' => $e->getMessage()]);
throw $e;
}
}
}

El proveedor de marcas de tiempo se inyecta para que un despliegue defina su propia autoridad de sellado de tiempo. El bloque catch registra el error y vuelve a lanzarlo. Nunca oculta el fallo, lo que mantiene la ruta de firma en modo cerrado ante fallos.

  • Una firma producida no es una firma verificada. La validación de ruta y la revocación se ejecutan en el verificador con las anclas de confianza de ese verificador. El productor no puede afirmar el resultado.
  • El resumen del intervalo de bytes excluye el valor de la firma. Un resumen que cubra los octetos de Contents no puede verificarse: ISO 32000-2 §12.8.1.
  • El estado de revocación tiene una ventana de frescura. Una respuesta OCSP solo está tan actualizada como su intervalo thisUpdate / nextUpdate: RFC 6960 §4.2. Una respuesta obsoleta no sustituye a una comprobación reciente en el momento de la validación.
  • El motor no incluye ninguna lista de confianza integrada. CaTrustAnchorBundle lo proporciona el llamador; un conjunto vacío significa que ninguna cadena valida, por diseño.
  • OCSP unknown no es good. Debe tratarse unknown como una indeterminación, no como una aprobación implícita: RFC 6960 §2.2.
  • La custodia de claves en HSM, la firma diferida y en la nube, y el productor PAdES B-LT / B-LTA no están en Core. Seleccionar esas rutas en la distribución de Core falla en modo cerrado con un mensaje que nombra el componente Enterprise que falta.

Una firma de software tarda milisegundos de una sola cifra. Una marca de tiempo añade un viaje de ida y vuelta por la red a la TSA. La validación de ruta es local una vez que los certificados están en memoria; la revocación añade una consulta OCSP o CRL por cada certificado de la cadena. El presupuesto de 1500 ms de tiempo de reloj cubre una única firma con marca de tiempo con una TSA remota sobre una conexión ya establecida. La revocación frente a un extremo lento lo supera y queda fuera de la ruta de la petición. El perfil de reproducibilidad es structural: 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 es idéntica.

Este es el límite criptográfico primario del motor, por lo que el modelo de amenazas es explícito. El intervalo de bytes lo calcula el motor y nunca se acepta del llamador. La ruta de firma opera en modo cerrado ante fallos: un fallo de primitiva o una carencia de capacidad lanza una excepción tipada y nunca se degrada silenciosamente a un algoritmo más débil. En un nivel que requiere marca de tiempo (B-T, B-LT, B-LTA), una autoridad de sellado de tiempo que devuelve un token vacío constituye un fallo terminal: la firma se rechaza; no se emite en un estado silenciosamente sin sellar y de nivel rebajado, salvo que se conecte un manejador de fallos para autorizar una degradación documentada. La confianza está controlada por el llamador por diseño —las anclas y la política de revocación son entradas, no valores predeterminados del motor— porque un productor que afirmara su propia confianza estaría afirmando un hecho que solo el verificador puede establecer. La confianza en la marca de tiempo depende de la confianza en la autoridad de sellado de tiempo, que es inyectable para que un despliegue defina la suya. Esta página está marcada como export_control_class: legal-review-required porque trata sobre firma criptográfica; toda fuente normativa se parafrasea y ninguna se reproduce, conforme a la higiene de citación.

AfirmaciónEstándarCláusulaEvidencia
La firma CMS se almacena codificada en DER en la entrada Contents del diccionario de firma.ISO 32000-2§12.8.1
SignerInfo incluye atributos firmados content-type y message-digest.RFC 5652§5.3
El verificador recalcula el resumen del contenido y lo compara con el atributo message-digest.RFC 5652§5.4
Un token de marca de tiempo es producido por una RFC 3161 TSA y lleva un serialNumber único y un genTime en UTC.RFC 3161§2.4.1, §2.4.2,,
La validación de ruta de certificación comprueba las restricciones básicas y las entradas de ruta desde el firmante hasta un ancla de confianza.RFC 5280§6.1,
OCSP informa certStatus como good, revoked o unknown, delimitado por thisUpdate / nextUpdate.RFC 6960§2.2, §4.2,

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

Core incluye el firmante de software CMS (RSA, RSASSA-PSS, ECDSA, Ed25519), el consumo de marcas de tiempo RFC 3161, la validación de ruta RFC 5280 y la comprobación de revocación por OCSP / CRL. La custodia de claves con HSM y PKCS#11, la firma diferida y en la nube, el productor PAdES B-LT y B-LTA, y el perfil de política criptográfica FIPS 140-3 se incluyen en las ediciones Pro y Enterprise. Core los resuelve en tiempo de ejecución frente al contrato, por lo que el motor de código abierto no arrastra ninguna dependencia comercial y la API no cambia al actualizar.