Ir al contenido

Contrato del proveedor de KMS

HsmSignerInterface es el contrato público que implementa un tercero para proporcionar custodia de claves a NextPDF. El código del proveedor conserva la clave privada. El motor construye la estructura CMS.

Ventana de terminal
composer require nextpdf/core:^3

NextPDF separa el ensamblaje de la firma de la custodia de claves. El motor prepara el rango de bytes y ensambla la estructura CMS SignedData. No conserva la clave privada. La custodia de claves se delega a un backend de firma mediante un contrato público.

Debe implementarse uno de tres contratos públicos:

  • SignerInterface. Contrato base. Devuelve un SignatureResult para los datos indicados. Aplica una marca de tiempo. Indica si admite validación a largo plazo. Usar este contrato cuando la lógica de firma se ejecute en el mismo proceso.
  • HsmSignerInterface. Contrato de custodia de claves. La implementación debe realizar la operación de firma dentro del límite del hardware. La clave privada no debe salir de ese límite. Un proveedor de sistema de gestión de claves implementa este contrato.
  • DeferredSignerInterface. Extensión de SignerInterface para backends asíncronos. El código llamador envía los datos, recibe un identificador de trabajo y recupera el resultado más tarde.

Esta página especifica el contrato público. No describe ninguna implementación interna de NextPDF Pro ni de NextPDF Enterprise. Una edición de pago puede aportar una implementación compatible de este contrato. Se depende del contrato, no de la implementación.

El contrato exige que la clave privada nunca salga del límite seguro. La práctica estándar respalda este requisito. NIST SP 800-57 Parte 1 Revisión 5 define la gestión de claves como el manejo de una clave durante todo su ciclo de vida. Ese ciclo de vida comprende la generación, el almacenamiento, la distribución, el uso y la destrucción seguros. PKCS#11 v3.1 permite hacer exigible ese límite. Cuando un objeto de clave privada establece CKA_SENSITIVE en true o CKA_EXTRACTABLE en false, el token no debe revelar el valor de la clave en texto plano fuera del token.

La implementación es responsable de cumplir este requisito. El motor no puede aplicarlo en su lugar. Si el método sign() puede leer los bytes en bruto de la clave en la memoria del proceso, se pierde la propiedad de seguridad del contrato.

NextPDF\Contracts\HsmSignerInterface (estable, desde 1.0.0):

MétodoDevuelvePropósito
sign(string $data, string $algorithm)stringFirma los datos dentro del límite del hardware. Devuelve los bytes de la firma en bruto. Lanza RuntimeException si se produce un fallo.
getCertificateDer()stringDevuelve el certificado X.509 del firmante en formato DER.
getCertificateChainDer()array<string>Devuelve los certificados intermedios, del emisor a la raíz, excluyendo el certificado del firmante.
getPublicKeyAlgorithm()stringDevuelve el identificador del algoritmo de clave pública.

NextPDF\Contracts\SignerInterface (estable, desde 1.0.0):

MétodoDevuelvePropósito
sign(string $data)SignatureResultDevuelve el CMS SignedData codificado en DER.
timestamp(string $signatureValue)stringDevuelve un token de marca de tiempo RFC 3161 codificado en DER.
supportsLtv()boolIndica si se pueden incrustar datos de validación a largo plazo.

NextPDF\Contracts\DeferredSignerInterface (experimental, desde 3.0.0) extiende SignerInterface con submitForSigning(), retrieveSignature() e isComplete() para backends asíncronos.

Los niveles de firma que produce el motor siguen los perfiles PAdES. ETSI EN 319 142-2 define perfiles PAdES ampliados basados en los bloques de construcción de referencia de EN 319 142-1. El motor asigna los niveles B-B, B-T, B-LT y B-LTA a esos bloques de construcción. El firmante aporta la operación criptográfica y el material de certificado que el motor necesita.

Este ejemplo muestra un proveedor mínimo de estilo PKCS#11. La llamada de firma se delega en el token. El valor de la clave nunca se lee en PHP.

<?php
declare(strict_types=1);
use NextPDF\Contracts\HsmSignerInterface;
final class TokenSigner implements HsmSignerInterface
{
public function __construct(private readonly TokenSession $session) {}
public function sign(string $data, string $algorithm = 'sha256WithRSAEncryption'): string
{
// The token computes the signature. The key stays inside the token.
return $this->session->c_sign($data, $algorithm);
}
public function getCertificateDer(): string
{
return $this->session->readCertificate();
}
/** @return array<string> */
public function getCertificateChainDer(): array
{
return $this->session->readChain();
}
public function getPublicKeyAlgorithm(): string
{
return 'rsaEncryption';
}
}

Un proveedor de producción valida las entradas, falla de forma segura ante un error del token y nunca registra material de clave ni de firma.

<?php
declare(strict_types=1);
use NextPDF\Contracts\HsmSignerInterface;
use Psr\Log\LoggerInterface;
use RuntimeException;
final class KmsSigner implements HsmSignerInterface
{
public function __construct(
private readonly RemoteKmsClient $kms,
private readonly string $keyId,
private readonly LoggerInterface $logger,
) {}
public function sign(string $data, string $algorithm = 'sha256WithRSAEncryption'): string
{
if ($data === '') {
throw new RuntimeException('Refusing to sign empty data');
}
try {
// The KMS performs the operation. The key never reaches this process.
return $this->kms->sign($this->keyId, $data, $algorithm);
} catch (\Throwable $error) {
// Fail closed. Do not log key material or signature bytes.
$this->logger->error('kms.sign.failed', ['key' => $this->keyId]);
throw new RuntimeException('KMS signing failed', previous: $error);
}
}
public function getCertificateDer(): string
{
return $this->kms->certificate($this->keyId);
}
/** @return array<string> */
public function getCertificateChainDer(): array
{
return $this->kms->chain($this->keyId);
}
public function getPublicKeyAlgorithm(): string
{
return $this->kms->algorithm($this->keyId);
}
}
  • Formato de los bytes de la firma. Devolver los bytes de la firma en bruto. Para RSA, los bytes son DER. Para ECDSA, los bytes son el valor r en bruto seguido del valor s.
  • Orden de la cadena. getCertificateChainDer() excluye el certificado del firmante. Los intermedios deben ordenarse del emisor a la raíz.
  • Identificadores de algoritmo. sign() usa identificadores de estilo OpenSSL. getPublicKeyAlgorithm() devuelve el nombre del algoritmo X.509.
  • El fallo es de tipo fail-closed. Lanzar RuntimeException ante cualquier error del token o del KMS. No devolver una firma parcial ni vacía.
  • Destrucción de la clave. Cuando una clave llega al final de su criptoperíodo, debe destruirse conforme al procedimiento de gestión de claves. NIST SP 800-57 Parte 1 Revisión 5 §8.2.1.2 indica que una clave debe destruirse en cuanto deja de ser necesaria. La destrucción en sí sigue el §8.3.4.

El contrato solo transfiere los datos que se van a firmar y el material de certificado. No transfiere el contenido del documento ni datos personales al backend de firma. Mantener el rango de bytes al mínimo. No colocar datos personales en los campos de motivo o de ubicación de la firma. Esos campos son observables en el archivo firmado.

Telemetría segura y depuración de registros

Sección titulada «Telemetría segura y depuración de registros»

No registrar nunca los datos que se pasan a sign(), los bytes de la firma devueltos, el identificador de la clave de forma recuperable ni ningún componente privado del certificado. Registrar solo el resultado de la operación y una referencia no reversible. El hook SignatureAppliedEvent es el anclaje de auditoría compatible. Consultar Disparadores de acciones.

ActivoAmenazaMitigación
Clave privadaExposición en la memoria del procesoEl contrato exige la firma dentro del límite; CKA_SENSITIVE / CKA_EXTRACTABLE de PKCS#11 imponen la no extractabilidad
Oráculo de firmaSolicitudes de firma ilimitadasLa implementación aplica límites de tasa y autentica a los llamadores
Cadena de certificadosSustituciónLa implementación devuelve una cadena que llega hasta una raíz de confianza
Bytes de la firmaDivulgación por medio de los registrosRuta de error de tipo fail-closed; sin material de firma en la telemetría
Clave más allá del criptoperíodoUso continuadoDestrucción de la clave conforme a NIST SP 800-57 Parte 1 Revisión 5 §8.2.1.2

Los niveles de firma se ajustan a los perfiles PAdES. ETSI EN 319 142-2 §5.1 define perfiles PAdES ampliados sobre los bloques de construcción de referencia de EN 319 142-1. El motor ensambla esos bloques de construcción a partir del material que aporta el firmante. La gestión de claves se alinea con el ciclo de vida de NIST SP 800-57 Parte 1 Revisión 5, incluido el requisito de destrucción del §8.2.1.2. La custodia de claves en hardware se alinea con los atributos de clave no extractable de PKCS#11 v3.1. Las citas quedan registradas en el front matter de la página.

Control de exportación y gobernanza de revisión

Sección titulada «Control de exportación y gobernanza de revisión»

Esta página trata la custodia de claves con PKCS#11 y módulos de seguridad de hardware, por lo que su front matter establece export_control_class: legal-review-required. Según la política de revisión de la documentación (plan §17 gate 6), cualquier página con modo de seguridad, o cuya ruta o contenido coincida con PAdES, FIPS, HSM o PKCS#11, requiere la aprobación del equipo de GitHub @nextpdf-labs/crypto-reviewers antes de poder publicarse. Esa aprobación de CODEOWNERS es un control de fusión obligatorio: la página permanece como publish: false hasta que se hayan completado tanto la revisión legal de control de exportación como la revisión de @nextpdf-labs/crypto-reviewers.

NextPDF Enterprise proporciona una implementación compatible de este contrato con custodia respaldada por un sistema de gestión de claves, ensamblaje de la cadena de certificados e integración de auditoría. En Core, se implementa HsmSignerInterface de forma propia, o se consume la implementación de Enterprise a través del mismo contrato público sin cambiar el código.

El glosario define sistema de gestión de claves, criptoperíodo y HSM; consultar el glosario publicado para cada definición canónica.