Inspecione uma assinatura existente e entenda o limite da confiança
Visão geral
Seção intitulada “Visão geral”Use o inspetor do Core para detectar se um PDF contém um dicionário de assinatura. O inspetor roda offline e não usa o sidecar Spectrum. Esta receita também deixa claro onde fica o limite de confiança: detectar uma assinatura não é o mesmo que verificá-la. A verificação criptográfica, a validação do caminho de confiança e a checagem de revogação ficam no Premium ou em ferramentas externas.
Instalação
Seção intitulada “Instalação”composer require nextpdf/core:^3Visão conceitual
Seção intitulada “Visão conceitual”Uma assinatura de PDF é um campo de assinatura cujo valor é um dicionário de assinatura (ISO 32000-2 §12.7.4). A entrada Contents do dicionário contém Cryptographic Message Syntax (CMS) SignedData codificada em DER (ISO 32000-2 §12.8.1). O fallback Quick do Inspector detecta a presença dessa estrutura ao varrer marcadores de assinatura. Ele não analisa o CMS, não recalcula o digest do intervalo de bytes (que exclui o valor da assinatura — ISO 32000-2 §12.8.1), não valida a cadeia de certificados nem verifica a revogação.
Superfície da API
Seção intitulada “Superfície da API”Chame new Inspector() e, em seguida, ->inspect(string $pdfData, InspectConfig $config). Use InspectConfig::quick() para o fallback PHP offline. InspectDepth::Standard/Full exigem o sidecar Spectrum e falham de forma fechada (INSPECT-SIDECAR-001) quando ele está ausente. O resultado é um objeto de valor InspectResult. Para este fluxo de trabalho, use $hasSigned para indicar a presença da assinatura, além de $isEncrypted e $pdfVersion.
Exemplo de código — Início rápido
Seção intitulada “Exemplo de código — Início rápido”<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Inspect\InspectConfig;use NextPDF\Inspect\Inspector;
$pdfData = file_get_contents(__DIR__ . '/incoming.pdf');if ($pdfData === false || $pdfData === '') { fwrite(STDERR, "Cannot read incoming.pdf\n"); exit(1);}
$result = (new Inspector())->inspect($pdfData, InspectConfig::quick());
// hasSigned reports the PRESENCE of a signature dictionary.// It does NOT mean the signature verifies.echo $result->hasSigned ? "A signature is present — NOT verified.\n" : "No signature found.\n";Exemplo de código — Produção
Seção intitulada “Exemplo de código — Produção”Este programa autocontido roda no harness do cookbook. Ele espelha examples/37-inspect-existing-signature.php. Ele inspeciona uma amostra do corpus conhecida por estar assinada e um documento sem assinatura recém-criado, para que você veja as duas ramificações do flag de presença. Em seguida, encaminha o veredito para a próxima etapa. A presença é uma entrada de roteamento, nunca um veredito de confiança. O arquivo é passado a um verificador criptográfico (Pro ou externo). Ele não é tratado como confiável aqui.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;use NextPDF\Inspect\InspectConfig;use NextPDF\Inspect\Inspector;
$inspector = new Inspector();
// --- A known-signed input ---// The repository corpus carries synthetic PAdES samples. In your// application this is simply the incoming PDF you received.$signedPath = __DIR__ . '/tests/Corpus/pades/pades-b-b-bytepattern-synthetic.pdf';if (is_file($signedPath)) { $signed = (string) file_get_contents($signedPath); $r = $inspector->inspect($signed, InspectConfig::quick());
echo "Signed sample:\n"; echo ' Signature present : ' . ($r->hasSigned ? 'yes' : 'no') . "\n"; echo ' Encrypted : ' . ($r->isEncrypted ? 'yes' : 'no') . "\n"; echo ' PDF version : ' . ($r->pdfVersion ?? 'unknown') . "\n"; echo " Verdict : presence detected — NOT verified.\n";
if ($r->hasSigned) { // Presence detected. This is routing input, not a trust verdict. // Hand the file to a cryptographic verifier (Pro or external) // before relying on it. (Pseudo-queue shown; wire your own.) // $verifierQueue->enqueue($signed); echo " Next step : run a cryptographic verifier before trusting it.\n"; }} else { echo "Signed corpus sample absent; skipping the signed branch.\n";}
// --- A known-unsigned input ---$unsigned = Document::createStandalone();$unsigned->setTitle('Unsigned sample');$unsigned->addPage();$unsigned->setFont('helvetica', '', 12);$unsigned->cell(0, 10, 'This document carries no signature.', newLine: true);$unsignedBytes = $unsigned->getPdfData();
$ru = $inspector->inspect($unsignedBytes, InspectConfig::quick());echo "Unsigned sample:\n";echo ' Signature present : ' . ($ru->hasSigned ? 'yes' : 'no') . "\n";
// The harness sets NEXTPDF_COOKBOOK_OUTPUT and runs this script under the// semantic profile; emit the unsigned document to the side-channel.$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');file_put_contents($out !== false && $out !== '' ? $out : __DIR__ . '/inspected.pdf', $unsignedBytes);STDOUT esperado (a ramificação assinada é pulada se a amostra do corpus estiver ausente):
Signed sample: Signature present : yes Encrypted : no PDF version : <version> Verdict : presence detected — NOT verified. Next step : run a cryptographic verifier before trusting it.Unsigned sample: Signature present : noCasos extremos e armadilhas
Seção intitulada “Casos extremos e armadilhas”- Presença não é validade.
$hasSignedindica que existe um dicionário de assinatura. Ele não verifica a estrutura do CMS, o digest do intervalo de bytes, o certificado de assinatura, a cadeia ou a revogação. Um arquivo adulterado ainda pode resultar emhasSigned = true. Nunca trate a presença como prova de integridade ou autoria. - O que a verificação completa exige. Uma decisão completa recalcula o digest do intervalo de bytes (ISO 32000-2 §12.8.1), valida o CMS SignedData, constrói e verifica o caminho X.509 até uma âncora confiável e checa a revogação por meio do Online Certificate Status Protocol (OCSP) ou de uma certificate revocation list (CRL). Um carimbo de tempo de assinatura, quando presente, também é verificado contra o próprio imprint sobre os octetos do valor da assinatura (ETSI EN 319 122-1 §5.3). Essas operações são executadas por trás dos contratos de assinatura. As implementações de produção são fornecidas nas edições Pro e Enterprise. Um validador externo é o outro caminho com suporte.
- Profundidade da inspeção.
InspectConfig::quick()é a única profundidade que roda sem o sidecar Spectrum.Standard/FulllançamINSPECT-SIDECAR-001quando o sidecar está indisponível. - Entrada vazia. Uma string vazia lança uma exceção de inspeção com “PDF data must not be empty”. Proteja a leitura.
- Várias assinaturas / carimbos de tempo. O flag de presença não conta as assinaturas nem distingue uma assinatura de aprovação de um carimbo de tempo de documento (que também é transportado em
unsignedAttrsconforme a RFC 5652 §5.3). Use um verificador dedicado quando a contagem ou o veredito por assinatura importar.
Desempenho
Seção intitulada “Desempenho”O fallback Quick realiza uma varredura limitada nos bytes do documento. Ele não analisa o grafo de objetos completo. Use-o para triar rapidamente arquivos recebidos antes de encaminhá-los a um verificador mais pesado.
Notas de segurança
Seção intitulada “Notas de segurança”O inspetor é uma ferramenta de triagem, não um limite de confiança. Um hasSigned positivo nunca deve, por si só, embasar uma decisão de confiança.
Residência de dados e mitigações de PII
Seção intitulada “Residência de dados e mitigações de PII”A inspeção é executada inteiramente no processo. Nenhum byte do documento sai do host. O fallback Quick lê apenas marcadores estruturais, não o texto do documento; por isso, não extrai nem transmite informações de identificação pessoal (PII).
Telemetria segura e limpeza de logs
Seção intitulada “Telemetria segura e limpeza de logs”Inspector aceita um logger PSR-3 opcional. Ele registra o caminho escolhido (“Spectrum unavailable, using PHP fallback”), não o conteúdo do documento. Não registre os bytes do PDF inspecionado nem o InspectResult inteiro se o documento for sensível.
Modelo de ameaças
Seção intitulada “Modelo de ameaças”Considerados: um arquivo adulterado que apresenta um dicionário de assinatura sintaticamente válido (o inspetor informa a presença; ele explicitamente não afirma a integridade) e um arquivo sem assinatura (corretamente informado como ausente). Não se afirma: que qualquer assinatura detectada seja criptograficamente válida, confiável ou não revogada — isso é trabalho do verificador.
Comportamento no modo FIPS
Seção intitulada “Comportamento no modo FIPS”O fallback Quick não realiza nenhuma criptografia; portanto, o modo Federal Information Processing Standards (FIPS) não é relevante para esta receita. A verificação criptográfica (Premium/externa) é onde a cadeia de provedores FIPS importa.
Conformidade
Seção intitulada “Conformidade”| Declaração | Especificação | Cláusula | reference_id |
|---|---|---|---|
| O valor de um campo de assinatura é um dicionário de assinatura. | ISO 32000-2 | §12.7.4 | |
Contents contém CMS SignedData em DER; um Contents de carimbo de tempo de documento contém um TimeStampToken. | ISO 32000-2 | §12.8.1 | |
| A verificação recalcula o digest sobre o intervalo de bytes, excluindo o valor da assinatura. | ISO 32000-2 | §12.8.1 | |
| Um imprint de carimbo de tempo de assinatura é feito sobre os octetos do valor da assinatura do SignerInfo. | ETSI EN 319 122-1 | §5.3 | |
| Um carimbo de tempo é transportado em unsignedAttrs do SignerInfo. | RFC 5652 | §5.3 |
Esta receita detecta uma assinatura. Ela não afirma que qualquer assinatura seja válida, confiável ou não revogada. Essa decisão cabe a um verificador criptográfico.
Contexto comercial
Seção intitulada “Contexto comercial”A verificação criptográfica de CMS, a validação do caminho X.509 e a checagem de revogação por OCSP/CRL são fornecidas por trás dos contratos de assinatura nas edições Pro e Enterprise. O inspetor do Core cobre apenas a detecção de presença.