Assinatura em escala, sem concessões
Spec: ISO 32000-2, §12.8ISO 32000-2 §12.8Spec: ETSI EN 319 142-1ETSI EN 319 142-1Spec: RFC 5652, §5.1RFC 5652 §5.1
Visão geral
Seção intitulada “Visão geral”Assinar um documento é uma operação criptográfica. Assinar cem mil em cima de um prazo é a mesma operação, repetida, onde a falha perigosa já não é “foi lento”, mas “um deles saiu sem assinatura e ninguém percebeu”. Esta página é sobre fazer a segunda coisa sem abrir mão da primeira: assinatura em massa e concorrente onde cada assinatura ainda está correta, a execução se recusa a emitir um arquivo que não conseguiu assinar, e um trabalho grande retoma em vez de começar do zero.
Por que isso importa
Seção intitulada “Por que isso importa”Uma assinatura é um fato por documento. Seu resumo é calculado sobre uma faixa de bytes declarada que exclui o próprio valor da assinatura (Spec: ISO 32000-2, §12.8ISO 32000-2 §12.8), então não existe maneira honesta de assinar mil documentos “como um lote” de uma só vez — cada um carrega seu próprio CMS SignedData sobre seus próprios bytes (Spec: RFC 5652, §5.1RFC 5652 §5.1). A escala, portanto, multiplica as chances de exatamente uma coisa dar errado em silêncio: um handle de chave que falhou por um instante, uma autoridade de carimbo de tempo que excedeu o tempo, um worker que morreu segurando um arquivo escrito pela metade.
O resultado caro não é uma queda. Uma queda é barulhenta e você a repete. O resultado caro é um silencioso — um PDF não assinado que parece pronto parado em um arquivo morto, descoberto meses depois pelo validador de um auditor. Em volume, “quase todos assinados” é indistinguível de “assinados” até o exato momento em que o que importa é verificado. O propósito inteiro de assinar em escala é tornar esse resultado estruturalmente impossível, não estatisticamente raro.
A versão resumida
Seção intitulada “A versão resumida”- Cada documento é assinado individualmente, sobre sua própria faixa de bytes. Lote é uma palavra de agendamento, não de criptografia. Não há assinatura compartilhada.
- O nível é um contrato, não uma sugestão. Você nomeia um nível baseline do PAdES e o motor produz exatamente esse nível para cada documento, ou falha esse documento de forma ruidosa (Spec: ETSI EN 319 142-1ETSI EN 319 142-1).
- O pipeline é fail-closed. Um documento que não pode ser assinado corretamente não passa adiante como bytes simples. Ele é retido, não repassado.
- A concorrência é por documento, e segura por construção. Unidades de assinatura não compartilham estado mutável, então dois workers não conseguem corromper a saída um do outro.
- Execuções grandes são duráveis. A saída já confirmada não é reemitida na retomada; uma execução que caiu continua a partir do seu último checkpoint em vez de reassinar tudo.
Como o NextPDF aborda isso
Seção intitulada “Como o NextPDF aborda isso”O design se apoia em uma separação: produzir a assinatura é um passo pequeno, determinístico, por documento; executar milhares deles com segurança é um passo de orquestração. Manter esses dois separados é o que permite que cada um continue simples.
O passo da assinatura é o que jamais pode fazer concessões. Você pede um nível —
um caso do enum SignatureLevel, nunca uma string que o motor tenha de
interpretar — e esse nível é tratado como um contrato para aquele documento. O
motor produz o nível solicitado ou para com um erro acionável; ele não assina em
silêncio em um nível mais baixo e deixa um registro afirmar um mais alto. A
correção não relaxa porque há mais documentos atrás deste. A centésima milésima
assinatura é calculada exatamente com o mesmo cuidado que a primeira.
A regra fail-closed é o que torna isso confiável em volume. O caminho de
assinatura do NextPDF recusa-se a emitir um artefato de aparência plausível, mas
não assinado, no lugar daquele que você pediu. A rota de aplicação suportada é a
API de alto nível Document: você configura a assinatura com
Document::setSignature() e então pede os bytes com Document::getPdfData() (ou
save() / output()), e esse único passo de escrita ou emite um PDF
corretamente assinado ou lança uma exceção antes de devolver os bytes — nunca
um arquivo não assinado que o chamador acredita estar assinado. Aplicada a um
lote, esta é a regra que converte “um escapou sem assinatura” de um defeito
latente e silencioso em um único trabalho falho e passível de retentativa.
- Aqueça o material de assinatura uma vezNa inicialização do worker, abra a fonte de chave/certificado e o cliente de carimbo de tempo. Esse custo é pago uma vez por worker, não uma vez por documento.
- Enfileire os documentosUma fila guarda os trabalhos por documento. A fila é o controle de vazão — os workers de assinatura escalam horizontalmente por trás dela.
- Renderize e assine um documentoUma unidade descartável renderiza o documento e então o assina sobre sua própria faixa de bytes no nível PAdES solicitado. Nada é compartilhado com o próximo documento.
- Confirme em caso de sucesso, retenha em caso de falhaUm arquivo corretamente assinado é confirmado uma vez. Um documento que não pôde ser assinado é falhado e retentado — nunca emitido como bytes não assinados.
- Faça checkpoint, e retome após uma quedaUma execução durável registra o que foi confirmado. Após uma queda, ela continua a partir do último checkpoint em vez de reassinar o lote inteiro.
O Core lhe dá a correção criptográfica: assinatura CMS em software e PAdES B-B (com B-T pelo cliente de carimbo de tempo) onde cada documento é assinado individualmente e fail-closed. A orquestração que torna uma execução grande durável, concorrente e exactly-once — o motor de renderização sem efeitos colaterais mais o committer, o checkpoint, a idempotência e os stores de dead-letter — é o módulo Stream nas edições avançadas; a assinatura com respaldo de hardware por um HSM ou um KMS na nuvem é, da mesma forma, uma costura das edições avançadas. O Core prova que cada assinatura está certa; as edições avançadas tornam um milhão delas sobreviventes.
Exemplo prático
Seção intitulada “Exemplo prático”O formato abaixo é a unidade de assinatura por documento dentro de um laço de lote. Cada iteração assina um documento em um nível nomeado e ou produz um resultado corretamente assinado ou falha esse único trabalho — ela nunca retorna bytes não assinados disfarçados de resultado.
<?php
declare(strict_types=1);
use NextPDF\Contracts\DocumentFactoryInterface;use NextPDF\Security\Signature\CertificateInfo;use NextPDF\Security\Signature\SignatureLevel;use NextPDF\Exception\SignatureException;use Psr\Log\LoggerInterface;
/** * One signing-batch iteration: render, sign at a named level, commit or fail. * * The factory and the certificate source ($certInfo, the warmed signing * material) are process-lifetime singletons; the document is disposable. A * document that cannot be signed at the requested level fails this job loudly — * it is never committed unsigned. * * @param iterable<int, callable(\NextPDF\Core\Document): \NextPDF\Core\Document> $jobs */function signBatch( DocumentFactoryInterface $factory, CertificateInfo $certInfo, LoggerInterface $logger, iterable $jobs,): void { // The level is an explicit, ordered contract — not a flag we hope is honoured. $level = SignatureLevel::PAdES_B_T;
foreach ($jobs as $jobId => $build) { // Fresh, disposable unit — shares the warmed signing material only. $doc = $factory->create(); $doc = $build($doc);
try { // Sign over this document's own byte range, at exactly $level, // or throw. There is no "signed lower, reported higher" path. $doc->setSignature(certInfo: $certInfo, level: $level); $signed = $doc->getPdfData(); } catch (SignatureException $e) { // Fail-closed: this document does NOT continue as unsigned bytes. // The job is failed and left for retry / dead-letter handling. $logger->error('pdf.sign.failed', ['job_id' => $jobId, 'reason' => $e->getMessage()]); continue; }
// Only a correctly-signed result reaches the commit step. commitSignedOutput($jobId, $signed); unset($doc, $signed); // release per-document state before the next iteration
$logger->info('pdf.sign.committed', ['job_id' => $jobId, 'level' => $level->value]); }}O catch é a linha que sustenta tudo. É a diferença entre uma execução que
retém os documentos que não conseguiu assinar e uma execução que os entrega
mesmo assim. O continue não encobre a falha — o trabalho é registrado e deixado
para retentativa, de modo que o lote termina com uma lista conhecida e completa do
que foi assinado e do que não foi, nunca com uma lacuna silenciosa.
Equívoco comum
Seção intitulada “Equívoco comum”O primeiro equívoco é achar que “assinatura em lote” significa uma assinatura aplicada a muitos arquivos. Não significa, e qualquer sistema que o afirme não está produzindo assinaturas PAdES válidas — o resumo de cada documento está vinculado aos seus próprios bytes (Spec: ISO 32000-2, §12.8ISO 32000-2 §12.8). Lote diz respeito puramente a quantos e com que rapidez, nunca a compartilhar a unidade criptográfica.
O segundo é achar que concorrência significa relaxar a correção em troca de velocidade — que um assinador rápido tem de cortar um canto que o cuidadoso não corta. Não tem. Como as unidades de assinatura não compartilham estado mutável, executá-las em paralelo muda o agendamento, não os bytes. Cada assinatura paralela é calculada com o mesmo rigor que uma única; o paralelismo está na orquestração ao redor delas.
O terceiro é achar que durabilidade é algo que você aparafusa depois da primeira execução noturna que falhou. A essa altura você já perdeu a execução. Um pipeline retomável tem de saber, por documento, o que foi confirmado e o que não foi antes da queda — que é exatamente o que os stores de checkpoint e de idempotência existem para registrar.
Limites e fronteiras
Seção intitulada “Limites e fronteiras”- Cada assinatura é por documento e vinculada aos padrões; não há atalho de lote. O volume muda o agendamento, não a unidade criptográfica. O NextPDF assina cada documento sobre sua própria faixa de bytes.
- O Core faz assinatura CMS em software e PAdES B-B (B-T via um cliente de carimbo de tempo). O motor durável, concorrente e exactly-once de renderização-e-assinatura é o módulo Stream nas edições avançadas; a custódia de chave com respaldo de HSM/KMS é uma costura das edições avançadas. Esta página não reivindica essa orquestração como Core.
- Fail-closed é o comportamento do motor, não uma garantia sobre o seu
encanamento. O NextPDF recusa-se a emitir um arquivo não assinado mas
acreditado assinado e expõe a rota de assinatura suportada. Um pipeline que
captura o erro resultante e confirma mesmo assim escolheu derrotar a garantia —
o enquadramento que o
catch/continuedo exemplo existe para evitar. - O nível PAdES é imposto por documento, não certificado para a execução. O motor produz o nível baseline solicitado ou falha; isso é uma imposição estrutural, não um veredito de conformidade de terceiros para os arquivos produzidos. A progressão de nível em si é coberta em perfis baseline do PAdES.
- A fila, a custódia de chave, a autoridade de carimbo de tempo e o object store são seus. O NextPDF fornece a correção de assinatura por documento e, nas edições avançadas, as primitivas de orquestração durável. Ele não opera a sua infraestrutura nem responde pela sua TSA.
| Edition | Availability |
|---|---|
| Core | Assinatura CMS em software por documento, PAdES B-B (B-T com um cliente de carimbo de tempo), assinada individualmente sobre a própria faixa de bytes de cada documento, fail-closed contra saída silenciosamente não assinada. A assinatura simples por documento não precisa de nenhuma camada comercial. |
| Pro | Adiciona o módulo Stream: um motor de renderização sem efeitos colaterais mais committer durável, checkpoint, idempotência e stores de dead-letter — execuções de lote concorrentes, à prova de quedas e exactly-once que retomam em vez de recomeçar. |
| Enterprise | Adiciona custódia de chave com respaldo de hardware (HSM via PKCS#11, ou um KMS na nuvem) para que a chave privada nunca deixe o dispositivo, e os níveis PAdES de longo prazo (B-LT, B-LTA) que mantêm um arquivo morto de alto volume verificável por décadas. |
Documentos relacionados
Seção intitulada “Documentos relacionados”- Geração de documentos em alto volume — o modelo de lote enfileirado e com memória limitada sobre o qual esta página assina; leia-o primeiro para a disciplina de vazão e de medição.
- Perfis baseline do PAdES — o que cada nível (B-B a B-LTA) adiciona, para que você assine no nível que a obrigação exige.
- Como as assinaturas ficam em um PDF — a fundação de faixa de bytes e dicionário que torna uma assinatura por documento.
- Assinatura com respaldo de HSM — onde fica a fronteira da chave privada quando o material de assinatura vive em hardware.
- Stream (Pro) — o motor de renderização durável, concorrente e exactly-once que transforma uma única unidade de assinatura em uma execução retomável.
Glossário
Seção intitulada “Glossário”- Assinatura em lote — assinar muitos documentos sob um agendamento. Um conceito de agendamento; cada documento ainda é assinado individualmente sobre seus próprios bytes.
- Fail-closed — diante de uma falha que de outra forma produziria uma saída não assinada ou errada, o pipeline retém o documento e reporta, em vez de repassá-lo como bytes simples.
- Confirmação exactly-once — uma propriedade de pipeline durável em que uma saída corretamente assinada é publicada uma vez e não é reemitida quando uma execução que caiu retoma.
- Checkpoint — registro durável por documento do que foi confirmado, para que uma execução possa continuar de onde parou em vez de reassinar tudo.
- CMS SignedData — o contêiner criptográfico para assinaturas sobre conteúdo (pode carregar múltiplos signatários); este pipeline produz a assinatura PDF de um signatário por documento, a unidade por documento que um lote produz.
- PAdES — PDF Advanced Electronic Signatures, a família de perfis ETSI EN 319 142 para assinatura de PDF; seus níveis baseline vão de B-B a B-LTA.