Pular para o conteúdo

Contracts / Signing

O domínio de assinatura reúne seis contratos. Eles definem como você gera uma Cryptographic Message Syntax (CMS) signature, aplica um carimbo de tempo Request for Comments (RFC) 3161, assina com uma chave de hardware security module (HSM) e habilita a validação de longo prazo (LTV). O Core publica os contratos; as edições Pro e Enterprise fornecem as implementações de produção.

Terminal window
composer require nextpdf/core:^3

Uma assinatura digital Portable Document Format (PDF) é uma estrutura CMS SignedData armazenada no dicionário de assinatura. A entrada Contents contém a estrutura codificada em Distinguished Encoding Rules (DER). A entrada ByteRange indica os intervalos de bytes cobertos pelo digest. O digest abrange o arquivo inteiro e exclui o próprio valor da assinatura; consulte International Organization for Standardization (ISO) 32000-2 §12.8.1. A estrutura CMS segue RFC 5652 §5.1: versão, algoritmos de digest, conteúdo encapsulado e informações do signatário.

SignerInterface é o contrato central. Ele produz CMS SignedData para um intervalo de bytes, aplica um carimbo de tempo a um valor de assinatura e informa se dá suporte a LTV. Ele cobre os níveis baseline PDF Advanced Electronic Signatures (PAdES) B-B até B-LTA conforme definido pelo European Telecommunications Standards Institute (ETSI) EN 319 142. Cada nível superior adiciona material de validação. B-B contém a assinatura base. B-T adiciona um carimbo de tempo de assinatura. B-LT adiciona dados de revogação. B-LTA adiciona um carimbo de tempo de arquivamento.

HsmSignerInterface assina com uma chave armazenada em um módulo de segurança de hardware. A chave privada nunca sai do limite do hardware. O contrato retorna o certificado do signatário e a cadeia de certificados em formato DER, para que a camada CMS possa montar a estrutura. DeferredSignerInterface estende SignerInterface para assinatura assíncrona. O chamador envia os dados, recebe um identificador de job, faz polling até a conclusão e recupera o resultado. Use esse contrato quando um HSM remoto ou um serviço de chaves na nuvem não retornar um resultado imediatamente.

TimestampProviderInterface solicita um token de carimbo de tempo RFC 3161. O protocolo é uma troca de requisição-resposta com uma Time-Stamping Authority (TSA); consulte RFC 3161 §2.4. O messageImprint no token é um hash do valor da assinatura; consulte RFC 3161 §2.4.2. LtvManagerInterface habilita a LTV. Ele coleta a cadeia de certificados, busca respostas Online Certificate Status Protocol (OCSP) e dados de certificate revocation list (CRL), constrói o Document Security Store (DSS) e adiciona um carimbo de tempo de documento para B-LTA. CryptoPolicyInterface controla os algoritmos de hash, assinatura e criptografia permitidos, além das forças de chave, antes que qualquer operação criptográfica seja executada.

TipoCategoriaMembros principaisEstabilidadeDesde
SignerInterfaceinterfacesign(string): SignatureResult, timestamp(string): string, supportsLtv(): boolstable1.0.0
HsmSignerInterfaceinterfacesign(string, string): string, getCertificateDer(), getCertificateChainDer(), getPublicKeyAlgorithm()stable1.0.0
DeferredSignerInterfaceinterfacesubmitForSigning(string): string, retrieveSignature(string), isComplete(string) (estende SignerInterface)experimental3.0.0
TimestampProviderInterfaceinterfacegetTimestamp(string): stringexperimental3.0.0
LtvManagerInterfaceinterfaceenableLtv(...), addDocumentTimestamp(...)stable1.10.0
CryptoPolicyInterfaceinterfaceisHashAlgorithmAllowed(), isSignatureAlgorithmAllowed(), isEncryptionAlgorithmAllowed(), isKeyStrengthAllowed(), getPreferredHashAlgorithm(), getName()stable1.9.0

SignerInterface::sign() retorna um NextPDF\Security\Signature\SignatureResult. A propriedade pública $cmsSignedData contém o CMS codificado em DER. A propriedade pública $digestHex contém o digest SHA-256. A propriedade pública $timestampToken contém o token TSA opcional. Os acessores são toHex() / toHexPadded(int) / getSize() / hasTimestamp(). HsmSignerInterface::sign() retorna bytes codificados em DER para Rivest-Shamir-Adleman (RSA) e bytes r‖s brutos para Elliptic Curve Digital Signature Algorithm (ECDSA). O argumento do algoritmo usa identificadores 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() retorna um SignatureResult. toHex() produz a string hexadecimal que o writer coloca no campo /Contents; os bytes DER brutos estão disponíveis na propriedade pública $cmsSignedData. A função depende do contrato, não de uma classe concreta. Tanto um signatário de teste do core quanto um signatário HSM do Premium satisfazem esse 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;
}
}
}

O serviço injeta três contratos. Ele verifica a política de criptografia antes de assinar. SignatureResult expõe os bytes CMS na propriedade pública $cmsSignedData, o digest SHA-256 em $digestHex e a string hexadecimal de /Contents por meio de toHex(). O provedor de carimbo de tempo recebe os bytes CMS e retorna um token codificado em DER. O bloco catch registra o nome da política e relança a exceção; ele nunca engole a falha.

  • O digest do intervalo de bytes deve excluir o valor da assinatura. Um digest que cobre a entrada Contents produz uma assinatura que nunca pode ser verificada; consulte ISO 32000-2 §12.8.1.
  • SignerInterface::supportsLtv() informa a capacidade, não o estado. Um signatário pode oferecer suporte a LTV e ainda assim produzir uma assinatura B-B quando nenhum serviço de carimbo de tempo está configurado.
  • DeferredSignerInterface::retrieveSignature() retorna null até que o job seja concluído. Faça polling de isComplete() primeiro para que você não transfira o payload a cada verificação. A recuperação é idempotente quando já existe um resultado.
  • LtvManagerInterface::addDocumentTimestamp() deve ser executado depois que o Document Security Store for gravado. Chamá-lo primeiro produz uma estrutura B-LTA inválida.
  • CryptoPolicyInterface retorna true para todos os algoritmos quando nenhuma política está definida. Em um ambiente regulado, defina uma política explícita; não confie no padrão aberto.
  • HsmSignerInterface::getCertificateChainDer() exclui o certificado do signatário. Use getCertificateDer() para a folha do signatário e o método de cadeia para os certificados intermediários.

O custo da assinatura vem principalmente da operação criptográfica e de qualquer ida e volta de rede, não do contrato. Uma assinatura de software local normalmente leva poucos milissegundos, geralmente em um dígito. Uma assinatura HSM adiciona a ida e volta até o dispositivo. Um carimbo de tempo adiciona uma ida e volta de rede até a Time-Stamping Authority. A validação de longo prazo adiciona uma busca OCSP ou CRL por certificado na cadeia. O performance_budget de 1500 ms wall cobre uma única assinatura com carimbo de tempo usando uma TSA remota em uma conexão já aquecida. A validação de longo prazo contra um endpoint de revogação lento excede esse orçamento e deve ser executada fora do caminho da requisição. O perfil de reprodutibilidade é structural, não bitwise. Um carimbo de tempo incorpora o instante da assinatura, de modo que duas execuções diferem nos bytes do carimbo de tempo, enquanto a estrutura do documento permanece idêntica.

Os contratos de assinatura são o limite criptográfico primário do motor, portanto o modelo de ameaças é explícito. A custódia de chaves é a principal preocupação: HsmSignerInterface mantém a chave privada dentro do limite do hardware, e o contrato nunca expõe material de chave. O rebaixamento de algoritmo é a segunda: CryptoPolicyInterface bloqueia hashes fracos e chaves curtas antes da operação, permitindo que uma implantação imponha um perfil Federal Information Processing Standards (FIPS) 140-3 ou electronic identification, authentication, and trust services (eIDAS) sem fazer um fork do motor. A confiança no carimbo de tempo é a terceira: um token RFC 3161 é tão confiável quanto a Time-Stamping Authority, portanto o contrato do provedor é injetável e uma implantação define sua própria autoridade. A validação de longo prazo é a quarta: o material de revogação é buscado no momento da assinatura e armazenado no Document Security Store, de modo que a verificação sobrevive à expiração do certificado. Trate toda entrada do signatário como não confiável. O motor calcula o intervalo de bytes; ele nunca é aceito do chamador. Esta página está marcada como export_control_class: legal-review-required porque os contratos regem a assinatura criptográfica. O texto parafraseia todas as fontes normativas e não cita nenhuma, conforme as boas práticas de citação.

AfirmaçãoPadrãoCláusulaEvidência
A entrada Contents do dicionário de assinatura armazena o valor da assinatura como CMS SignedData codificado em DER ou um TimeStampToken.ISO 32000-2§12.8.1
O digest é calculado sobre o intervalo de bytes definido pelo array ByteRange e exclui o valor da assinatura.ISO 32000-2§12.8.1,
O Document Security Store transporta material de validação de longo prazo com validation-related information (VRI), OCSP, CRL e entradas de certificado.ISO 32000-2§12.8.4.3,
A validação de longo prazo PAdES coloca os dados de validação nos dicionários DSS e VRI.ETSI EN 319 142-2§6.3,
Um token de carimbo de tempo RFC 3161 vincula um hash do valor da assinatura por meio de messageImprint em uma troca de requisição-resposta TSA.RFC 3161§2.4.2, §2.4,
A sequência CMS SignedData transporta versão, algoritmos de digest, conteúdo encapsulado e informações do signatário.RFC 5652§5.1

Todas as cláusulas são parafraseadas. NextPDF não reproduz texto normativo. Consulte os padrões publicados para a redação autorizada.

O Core publica e congela os contratos de assinatura. As edições Pro e Enterprise fornecem as implementações de produção por trás de HsmSignerInterface, LtvManagerInterface e do signatário diferido, incluindo a integração de hardware Public-Key Cryptography Standards #11 (PKCS#11) e PAdES B-LT e B-LTA. O Core resolve essas implementações em tempo de execução com class_exists() e as converte para o contrato, de modo que o motor open-source não carrega nenhuma dependência comercial e a API não muda ao atualizar.