Ga naar inhoud

Een bestaande handtekening inspecteren en de vertrouwensgrens begrijpen

Gebruik de Core-inspector om te detecteren of een PDF een signature dictionary bevat. De inspector draait offline en gebruikt de Spectrum-sidecar niet. Dit recipe maakt ook de vertrouwensgrens duidelijk: het detecteren van een handtekening is niet hetzelfde als die handtekening verifiëren. Cryptografische verificatie, validatie van het vertrouwenspad en intrekkingscontrole horen bij Premium of bij een externe oplossing.

Terminal window
composer require nextpdf/core:^3

Een PDF-handtekening is een signature field waarvan de waarde een signature dictionary is (ISO 32000-2 §12.7.4). De Contents-entry van de dictionary bevat DER-gecodeerde Cryptographic Message Syntax (CMS) SignedData (ISO 32000-2 §12.8.1). De Quick-fallback van de Inspector detecteert de aanwezigheid van die structuur door te scannen op handtekeningmarkeringen. De fallback parseert CMS niet, berekent de byte-range-digest niet opnieuw (waarbij de handtekeningwaarde wordt uitgesloten — ISO 32000-2 §12.8.1), valideert de certificaatketen niet en controleert geen intrekkingen.

Roep new Inspector() aan en daarna ->inspect(string $pdfData, InspectConfig $config). Gebruik InspectConfig::quick() voor de offline PHP-fallback. InspectDepth::Standard/Full vereisen de Spectrum-sidecar en falen veilig (fail closed) (INSPECT-SIDECAR-001) als die ontbreekt. Het resultaat is een InspectResult-value object. Gebruik in deze workflow $hasSigned voor de aanwezigheid van een handtekening, samen met $isEncrypted en $pdfVersion.

<?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";

Dit zelfstandige programma draait in de cookbook-harness. Het weerspiegelt examples/37-inspect-existing-signature.php. Het inspecteert een corpusvoorbeeld waarvan bekend is dat het is ondertekend en een nieuw opgebouwd niet-ondertekend document, zodat je beide vertakkingen van de aanwezigheidsvlag kunt zien. Daarna routeert het de uitkomst verder. Aanwezigheid is routinginvoer, nooit een vertrouwensoordeel. Het bestand wordt doorgegeven aan een cryptografische verificateur (Pro of extern). Hier wordt het niet vertrouwd.

<?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);

Verwachte STDOUT (de ondertekende vertakking wordt overgeslagen als het corpusvoorbeeld afwezig is):

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 : no
  • Aanwezigheid is geen geldigheid. $hasSigned meldt dat er een signature dictionary bestaat. De vlag controleert niet de CMS-structuur, de byte-range-digest, het ondertekeningscertificaat, de keten of intrekkingen. Een gemanipuleerd bestand kan nog steeds hasSigned = true opleveren. Beschouw aanwezigheid nooit als bewijs van integriteit of auteurschap.
  • Wat volledige verificatie vereist. Een volledig oordeel berekent de byte-range-digest opnieuw (ISO 32000-2 §12.8.1), valideert de CMS SignedData, bouwt en controleert het X.509-pad naar een vertrouwd anker en controleert intrekkingen via het Online Certificate Status Protocol (OCSP) of een certificate revocation list (CRL). Een handtekeningtijdstempel wordt, als die aanwezig is, zelf geverifieerd aan de hand van zijn eigen imprint over de octetten van de handtekeningwaarde (ETSI EN 319 122-1 §5.3). Deze bewerkingen vallen onder de signing contracts. Productie-implementaties worden geleverd in Pro en Enterprise. Een externe validator is het andere ondersteunde pad.
  • Inspectiediepte. InspectConfig::quick() is de enige inspectiediepte die zonder de Spectrum-sidecar draait. Standard/Full werpen INSPECT-SIDECAR-001 wanneer de sidecar niet beschikbaar is.
  • Lege invoer. Een lege string werpt een inspect-uitzondering met “PDF data must not be empty”. Vang dit bij het inlezen af.
  • Meerdere handtekeningen / tijdstempels. De aanwezigheidsvlag telt geen handtekeningen en maakt geen onderscheid tussen een goedkeuringshandtekening en een documenttijdstempel (dat volgens RFC 5652 §5.3 ook wordt meegevoerd in unsignedAttrs). Gebruik een speciale verificateur wanneer het aantal of het oordeel per handtekening van belang is.

De Quick-fallback voert een begrensde scan uit over de documentbytes. De fallback parseert niet de volledige objectgraaf. Gebruik die voor snelle triage van binnenkomende bestanden voordat je ze doorstuurt naar een zwaardere verificateur.

De inspector is een triagetool, geen vertrouwensgrens. Een positieve hasSigned mag nooit op zichzelf een vertrouwensbeslissing bepalen.

De inspectie draait volledig in-process. Geen documentbytes verlaten de host. De Quick-fallback leest alleen structurele markeringen, geen documenttekst, en extraheert of verzendt dus geen persoonlijk identificeerbare informatie (PII).

Inspector accepteert een optionele PSR-3-logger. Die logt het gekozen pad (“Spectrum unavailable, using PHP fallback”), niet de documentinhoud. Log de geïnspecteerde PDF-bytes of de InspectResult niet letterlijk als het document gevoelig is.

Beschouwd: een gemanipuleerd bestand dat een syntactisch geldige signature dictionary presenteert (de inspector meldt aanwezigheid; hij bevestigt expliciet geen integriteit), en een bestand zonder handtekening (correct als afwezig gemeld). Niet bevestigd wordt dat een gedetecteerde handtekening cryptografisch geldig, vertrouwd of niet-ingetrokken is — dat is de taak van de verificateur.

De Quick-fallback voert geen cryptografie uit, dus de Federal Information Processing Standards (FIPS)-modus is niet relevant voor dit recipe. Bij cryptografische verificatie (Premium/extern) is de FIPS-providerketen wel van belang.

BeweringSpecificatieClausulereference_id
De waarde van een signature field is een signature dictionary.ISO 32000-2§12.7.4
Contents bevat DER CMS SignedData; een Contents van een documenttijdstempel bevat een TimeStampToken.ISO 32000-2§12.8.1
Verificatie berekent de digest opnieuw over de byte range, met uitsluiting van de handtekeningwaarde.ISO 32000-2§12.8.1
Een imprint van een handtekeningtijdstempel ligt over de octetten van de handtekeningwaarde van de SignerInfo.ETSI EN 319 122-1§5.3
Een tijdstempel wordt meegevoerd in SignerInfo unsignedAttrs.RFC 5652§5.3

Dit recipe detecteert een handtekening. Het bevestigt niet dat die handtekening geldig, vertrouwd of niet-ingetrokken is. Een cryptografische verificateur neemt die beslissing.

Cryptografische CMS-verificatie, X.509-padvalidatie en OCSP/CRL-intrekkingscontrole worden geleverd achter de signing contracts in de Pro- en Enterprise-edities. De Core-inspector dekt alleen aanwezigheidsdetectie.