Esaminare una firma esistente e comprenderne il perimetro di attendibilità
In breve
Sezione intitolata “In breve”Questa ricetta mostra come usare l’inspector di Core per rilevare se un PDF contiene un dizionario di firma. L’inspector viene eseguito offline e non usa il sidecar Spectrum. La ricetta rende inoltre esplicito il confine: rilevare una firma non equivale a verificarla. La verifica crittografica, la convalida del percorso di attendibilità e il controllo della revoca sono funzionalità Premium o esterne.
Installazione
Sezione intitolata “Installazione”composer require nextpdf/core:^3Panoramica concettuale
Sezione intitolata “Panoramica concettuale”In un PDF, una firma è un campo firma il cui valore è un dizionario di firma (ISO 32000-2 §12.7.4). La voce Contents del dizionario contiene dati CMS SignedData codificati in DER (ISO 32000-2 §12.8.1). Il fallback Quick di Inspector rileva la presenza di tale struttura analizzando i marcatori di firma. Non analizza il CMS, non ricalcola il digest dell’intervallo di byte (che esclude il valore della firma — ISO 32000-2 §12.8.1), non convalida la catena di certificati e non controlla la revoca.
Superficie API
Sezione intitolata “Superficie API”Istanziare new Inspector(), quindi invocare ->inspect(string $pdfData, InspectConfig $config). Usare InspectConfig::quick() per il fallback PHP offline. InspectDepth::Standard/Full richiedono il sidecar Spectrum e falliscono in modo sicuro (INSPECT-SIDECAR-001) quando è assente. Il risultato è un oggetto valore InspectResult. I campi rilevanti in questo caso sono $hasSigned (presenza della firma), $isEncrypted e $pdfVersion.
Esempio di codice — Avvio rapido
Sezione intitolata “Esempio di codice — Avvio rapido”<?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";Esempio di codice — Produzione
Sezione intitolata “Esempio di codice — Produzione”Questo è il programma autonomo eseguibile dall’harness. Corrisponde a examples/37-inspect-existing-signature.php. Il programma esamina un campione del corpus noto come firmato e un documento non firmato appena creato, rendendo osservabili entrambi i rami del flag di presenza. Quindi emette l’esito. La presenza è un input di instradamento, mai un esito di attendibilità. Il file va passato a un verificatore crittografico (Pro o esterno). In questa ricetta non è considerato attendibile.
<?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 previsto (il ramo relativo al documento firmato viene saltato se il campione del corpus è assente):
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 : noCasi limite e insidie
Sezione intitolata “Casi limite e insidie”- La presenza non equivale a validità.
$hasSignedsegnala che esiste un dizionario di firma. Non controlla la struttura CMS, il digest dell’intervallo di byte, il certificato di firma, la catena o la revoca. Un file manomesso può comunque segnalarehasSigned = true. Non considerare mai la presenza come prova di integrità o paternità. - Cosa richiede la verifica completa. Una verifica completa ricalcola il digest dell’intervallo di byte (ISO 32000-2 §12.8.1), convalida il CMS SignedData, costruisce e verifica il percorso X.509 fino a un’ancora attendibile e controlla la revoca tramite OCSP o CRL. Una marca temporale di firma, quando presente, viene a sua volta verificata rispetto alla propria impronta sugli ottetti del valore della firma (ETSI EN 319 122-1 §5.3). Queste operazioni sono disponibili tramite i contratti di firma. Le implementazioni di produzione sono fornite in Pro ed Enterprise. Un validatore esterno è l’altro percorso supportato.
- Profondità di esame.
InspectConfig::quick()è l’unica profondità che viene eseguita senza il sidecar Spectrum.Standard/FullgeneranoINSPECT-SIDECAR-001quando il sidecar non è disponibile. - Input vuoto. Una stringa vuota genera un’eccezione di inspect con “PDF data must not be empty”. Proteggere la lettura del file.
- Firme / marche temporali multiple. Il flag di presenza non conta le firme né distingue una firma di approvazione da una marca temporale del documento (anch’essa contenuta in
unsignedAttrssecondo RFC 5652 §5.3). Usare un verificatore dedicato quando il conteggio o l’esito di ogni singola firma sono rilevanti.
Prestazioni
Sezione intitolata “Prestazioni”Il fallback Quick esegue una scansione limitata dei byte del documento. Non analizza l’intero grafo degli oggetti. È adatto per un triage rapido dei file in arrivo prima di instradarli a un verificatore più oneroso.
Note sulla sicurezza
Sezione intitolata “Note sulla sicurezza”L’inspector è uno strumento di triage, non un perimetro di attendibilità. Un hasSigned positivo non deve mai, da solo, determinare una decisione di attendibilità.
Residenza dei dati e mitigazioni delle PII
Sezione intitolata “Residenza dei dati e mitigazioni delle PII”L’esame avviene interamente in-process. Nessun byte del documento lascia l’host. Il fallback Quick legge solo i marcatori strutturali, non il testo del documento; quindi non viene estratta né trasmessa alcuna PII.
Telemetria sicura e pulizia dei log
Sezione intitolata “Telemetria sicura e pulizia dei log”Inspector accetta un logger PSR-3 facoltativo. Registra nel log il percorso scelto (“Spectrum unavailable, using PHP fallback”), non il contenuto del documento. Non registrare i byte del PDF esaminato né il InspectResult integrale se il documento è sensibile.
Modello delle minacce
Sezione intitolata “Modello delle minacce”Casi considerati: un file manomesso che presenta un dizionario di firma sintatticamente valido (l’inspector segnala la presenza; non dichiara l’integrità) e un file privo di firma (correttamente segnalato come assente). Non viene asserito: che una firma rilevata sia crittograficamente valida, attendibile o non revocata — questi sono compiti del verificatore.
Comportamento in modalità FIPS
Sezione intitolata “Comportamento in modalità FIPS”Il fallback Quick non esegue alcuna operazione crittografica, quindi la modalità FIPS non è rilevante per questa ricetta. La catena di provider FIPS diventa rilevante nella verifica crittografica (Premium/esterna).
Conformità
Sezione intitolata “Conformità”| Asserzione | Specifica | Clausola | reference_id |
|---|---|---|---|
| Il valore di un campo firma è un dizionario di firma. | ISO 32000-2 | §12.7.4 | |
Contents contiene dati DER CMS SignedData; un Contents di marca temporale del documento contiene un TimeStampToken. | ISO 32000-2 | §12.8.1 | |
| La verifica ricalcola il digest sull’intervallo di byte, escludendo il valore della firma. | ISO 32000-2 | §12.8.1 | |
| L’impronta di una marca temporale di firma è sugli ottetti del valore della firma di SignerInfo. | ETSI EN 319 122-1 | §5.3 | |
| Una marca temporale è contenuta negli unsignedAttrs di SignerInfo. | RFC 5652 | §5.3 |
Questa ricetta rileva una firma. Non asserisce che una firma sia valida, attendibile o non revocata. Tale decisione spetta a un verificatore crittografico.
Contesto commerciale
Sezione intitolata “Contesto commerciale”La verifica crittografica CMS, la convalida del percorso X.509 e il controllo della revoca tramite OCSP/CRL sono disponibili tramite i contratti di firma nelle edizioni Pro ed Enterprise. L’inspector di Core copre solo il rilevamento della presenza.