Inspecter une signature existante et comprendre la limite de confiance
Cette recette utilise l’inspecteur du Core pour détecter si un PDF porte un dictionnaire de signature. L’inspecteur s’exécute hors ligne et n’utilise pas le sidecar Spectrum. Elle trace aussi clairement la limite : détecter une signature n’est pas la même chose que la vérifier. La vérification cryptographique, la validation du chemin de confiance et le contrôle de révocation relèvent de Premium ou d’un outil externe.
Installation
Section intitulée « Installation »composer require nextpdf/core:^3Aperçu conceptuel
Section intitulée « Aperçu conceptuel »Dans un PDF, une signature est un champ de signature dont la valeur est un dictionnaire de signature (ISO 32000-2 §12.7.4). L’entrée Contents du dictionnaire contient des données CMS SignedData encodées en DER (ISO 32000-2 §12.8.1). Le mode de repli Quick de l’Inspector détecte la présence d’une telle structure en recherchant les marqueurs de signature. Il n’analyse pas le CMS, ne recalcule pas l’empreinte de la plage d’octets (qui exclut la valeur de la signature — ISO 32000-2 §12.8.1), ne valide pas la chaîne de certificats et ne contrôle pas la révocation.
Surface de l’API
Section intitulée « Surface de l’API »Appelle new Inspector(), puis ->inspect(string $pdfData, InspectConfig $config). Utilise InspectConfig::quick() pour le mode de repli PHP hors ligne. InspectDepth::Standard/Full exigent le sidecar Spectrum et échouent en mode fermé (INSPECT-SIDECAR-001) lorsqu’il est absent. Le résultat est un objet valeur InspectResult. Les champs pertinents ici sont $hasSigned (présence de la signature), $isEncrypted et $pdfVersion.
Exemple de code — Démarrage rapide
Section intitulée « Exemple de code — Démarrage rapide »<?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";Exemple de code — Production
Section intitulée « Exemple de code — Production »Voici le programme autonome que le harnais exécute. Il reflète examples/37-inspect-existing-signature.php. Le programme inspecte un échantillon du corpus connu pour être signé et un document non signé construit à la volée, de sorte que les deux branches de l’indicateur de présence soient observables. Il achemine ensuite ce verdict. La présence sert au routage, jamais de verdict de confiance. Le fichier est confié à un vérificateur cryptographique (Pro ou externe). Ici, il n’est pas considéré comme digne de confiance.
<?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);Sortie STDOUT attendue (la branche signée est ignorée si l’échantillon de corpus est absent) :
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 : noCas limites et pièges
Section intitulée « Cas limites et pièges »- La présence n’est pas la validité.
$hasSignedsignale qu’un dictionnaire de signature existe. Il ne vérifie pas la structure CMS, l’empreinte de la plage d’octets, le certificat de signature, la chaîne ni la révocation. Un fichier altéré peut tout de même renvoyerhasSigned = true. Ne considère jamais la présence comme une preuve d’intégrité ou de paternité. - Ce qu’exige une vérification complète. Une vérification complète recalcule l’empreinte de la plage d’octets (ISO 32000-2 §12.8.1), valide le CMS SignedData, construit le chemin X.509 jusqu’à une ancre de confiance et le contrôle, puis vérifie la révocation via OCSP ou CRL. Un horodatage de signature, lorsqu’il est présent, est lui-même vérifié par rapport à sa propre empreinte sur les octets de la valeur de la signature (ETSI EN 319 122-1 §5.3). Ces opérations passent par les contrats de signature. Les implémentations de production sont livrées dans Pro et Enterprise. L’autre option prise en charge consiste à utiliser un validateur externe.
- Profondeur d’inspection.
InspectConfig::quick()est le seul niveau de profondeur qui s’exécute sans le sidecar Spectrum.Standard/FulllèventINSPECT-SIDECAR-001lorsque le sidecar est indisponible. - Entrée vide. Une chaîne vide lève une exception d’inspection avec « PDF data must not be empty ». Protège la lecture en amont.
- Signatures et horodatages multiples. L’indicateur de présence ne compte pas les signatures et ne distingue pas une signature d’approbation d’un horodatage de document (qui est également porté dans
unsignedAttrsselon RFC 5652 §5.3). Utilise un vérificateur dédié lorsque le nombre ou le verdict par signature a de l’importance.
Performance
Section intitulée « Performance »Le mode de repli Quick effectue un balayage borné des octets du document. Il n’analyse pas le graphe complet des objets. Il convient au tri rapide des fichiers entrants avant de les acheminer vers un vérificateur plus lourd.
Notes de sécurité
Section intitulée « Notes de sécurité »L’inspecteur est un outil de tri, pas une limite de confiance. Un hasSigned positif ne doit jamais conditionner à lui seul une décision de confiance.
Résidence des données et mesures d’atténuation relatives aux données personnelles
Section intitulée « Résidence des données et mesures d’atténuation relatives aux données personnelles »L’inspection s’exécute entièrement dans le processus. Aucun octet du document ne quitte l’hôte. Le mode de repli Quick ne lit que les marqueurs structurels, pas le texte du document ; aucune donnée personnelle n’est donc extraite ni transmise.
Télémétrie sûre et assainissement des journaux
Section intitulée « Télémétrie sûre et assainissement des journaux »Inspector accepte un logger PSR-3 facultatif. Il journalise le chemin choisi (« Spectrum unavailable, using PHP fallback »), pas le contenu du document. Ne journalise pas les octets du PDF inspecté ni l’InspectResult tel quel si le document est sensible.
Modèle de menace
Section intitulée « Modèle de menace »Pris en compte : un fichier altéré présentant un dictionnaire de signature syntaxiquement valide (l’inspecteur signale la présence ; il n’affirme explicitement pas l’intégrité), et un fichier sans signature (correctement signalé comme absent). Non affirmé : qu’une signature détectée soit cryptographiquement valide, digne de confiance ou non révoquée — cela relève du vérificateur.
Comportement en mode FIPS
Section intitulée « Comportement en mode FIPS »Le mode de repli Quick n’effectue aucune cryptographie, de sorte que le mode FIPS n’est pas pertinent pour cette recette. La vérification cryptographique (Premium/externe) est l’endroit où la chaîne de fournisseurs FIPS compte.
Conformité
Section intitulée « Conformité »| Déclaration | Spécification | Clause | reference_id |
|---|---|---|---|
| La valeur d’un champ de signature est un dictionnaire de signature. | ISO 32000-2 | §12.7.4 | |
Contents contient des données CMS SignedData encodées en DER ; un Contents d’horodatage de document contient un TimeStampToken. | ISO 32000-2 | §12.8.1 | |
| La vérification recalcule l’empreinte sur la plage d’octets, en excluant la valeur de la signature. | ISO 32000-2 | §12.8.1 | |
| L’empreinte d’un horodatage de signature porte sur les octets de la valeur de la signature SignerInfo. | ETSI EN 319 122-1 | §5.3 | |
| Un horodatage est porté dans les unsignedAttrs de SignerInfo. | RFC 5652 | §5.3 |
Cette recette détecte une signature. Elle n’affirme pas qu’une signature soit valide, digne de confiance ou non révoquée. Cette décision revient à un vérificateur cryptographique.
Contexte commercial
Section intitulée « Contexte commercial »La vérification cryptographique CMS, la validation du chemin X.509 et le contrôle de révocation OCSP/CRL sont disponibles derrière les contrats de signature dans les éditions Pro et Enterprise. L’inspecteur du Core couvre uniquement la détection de présence.