Aller au contenu

Comment les signatures sont intégrées dans un PDF

Spec: ETSI EN 319 142-1 Spec: RFC 5652 Evidence: Standard-backed

Une signature PDF n’enveloppe pas le fichier. Elle est intégrée à l’intérieur de celui-ci : un dictionnaire qui nomme la signature et un condensé calculé sur une plage d’octets déclarée, qui omet délibérément la valeur de la signature elle-même. Cette page explique ce mécanisme et, tout aussi important, ce qu’il ne garantit pas.

« Le document est signé » est une affirmation qui sert de base à une action. On y rattache un paiement, une approbation, une obligation légale. Si tu ne sais pas précisément quels octets une signature couvre, tu ne peux pas dire ce qu’un résultat valide prouve réellement. Un PDF peut porter une signature parfaitement valide et pourtant montrer à un lecteur un contenu que le signataire n’a jamais vu, parce que ce contenu a été ajouté après la signature, dans une zone que la signature n’a jamais couverte. Savoir où commence et où s’arrête l’autorité de la signature fait toute la différence entre une décision défendable et une décision fondée sur l’espoir.

  • Une signature PDF réside dans un dictionnaire de signature et un champ de signature à l’intérieur du document, pas sous la forme d’une enveloppe externe.
  • Les octets signés sont déclarés par le tableau ByteRange : deux segments (offset, length) qui, ensemble, couvrent tout le fichier sauf la valeur hexadécimale de la signature contenue dans l’entrée Contents.
  • Le condensé de ces deux segments concaténés est ce que la signature cryptographique protège réellement.
  • Tout ce qui est ajouté plus tard dans une nouvelle révision se trouve en dehors de la plage d’octets d’origine. La signature d’origine reste valide ; elle ne revendique rien sur les nouveaux octets.
  • Une signature d’approbation et une signature de certification diffèrent par leur portée : la certification (DocMDP) restreint les modifications ultérieures autorisées ; l’approbation, non.

NextPDF construit la signature comme le format l’exige, dans un ordre fixe, afin que la plage d’octets soit exacte plutôt qu’approximative.

Lorsque le moteur écrit une signature, il réserve d’abord un emplacement de taille fixe pour la valeur Contents et écrit un espace réservé de largeur fixe pour ByteRange. Il attend que le document complet soit écrit — table de références croisées et marqueur de fin de fichier inclus. Ce n’est qu’à ce moment-là qu’il calcule les deux décalages réels, les réécrit dans l’espace réservé sans déplacer le moindre octet, hache les deux segments, puis place l’objet CMS obtenu dans l’emplacement réservé. L’espace réservé est complété par des zéros jusqu’à une longueur constante, précisément pour que l’insertion des valeurs réelles ne puisse pas déplacer les octets mêmes que l’on hache. C’est le seul ordre qui produit une signature cohérente avec elle-même. Le moteur traite toute défaillance de cette séquence comme une erreur bloquante, jamais comme un repli silencieux.

Dans le profil PDF 2.0, l’objet de signature lui-même est une structure CMS SignedData détachée. Le dictionnaire PDF indique et comment ; l’objet CMS porte le qui et la preuve cryptographique.

  1. Step 1 of 4: ISO 32000-2 §12.8.1 — ByteRange digest & signature dictionary
  2. Step 2 of 4: ISO 32000-2 §12.8.3.3 — ETSI.CAdES.detached SubFilter
  3. Step 3 of 4: ETSI EN 319 142-1 PAdES baseline profile
  4. Step 4 of 4: RFC 5652 CMS SignedData in Contents
Où une signature PDF est définie, du format de conteneur jusqu’à l’objet cryptographique : ISO 32000-2 spécifie le dictionnaire et le mécanisme du byte-range, ETSI EN 319 142-1 le profile pour PAdES, et RFC 5652 définit l’objet CMS SignedData placé dans Contents.

Evidence: Standard-backed Le mécanisme est défini par Spec: ISO 32000-2, §12.8.1 . Un condensé de byte-range est calculé sur une plage d’octets indiquée par l’entrée ByteRange. Cette plage doit couvrir le fichier entier, y compris le dictionnaire de signature mais à l’exclusion de la valeur de la signature — l’entrée Contents. ByteRange est un tableau de paires d’entiers — décalage de départ et longueur. Des plages discontinues sont utilisées précisément pour que le condensé puisse omettre la valeur de la signature elle-même.

Pour le profil PDF 2.0, Spec: ISO 32000-2, §12.8.3.3 précise que, lorsque le SubFilter est ETSI.CAdES.detached, la valeur Contents est un objet CMS SignedData encodé en DER, c’est-à-dire la même structure que définit Spec: RFC 5652 . Le profil PAdES de cet objet est celui que décrit Spec: ETSI EN 319 142-1 .

La portée varie d’une signature à l’autre. Spec: ISO 32000-2, §12.7.4.5 définit l’autorisation MDP : une valeur de 0 fait de la signature une signature d’approbation, tandis que les valeurs 13 en font une signature de certification qui restreint les modifications ultérieures maintenant le document conforme. Même mécanisme de byte-range ; promesse différente quant à l’avenir.

Le moteur de NextPDF implémente exactement ce fonctionnement : un espace réservé ByteRange de largeur fixe, le condensé concaténé en deux segments et un objet CMS détaché dans un emplacement Contents réservé, finalisé seulement une fois le fichier complet.

Tu construis rarement un ByteRange à la main. L’exemple vise à montrer la forme du résultat afin qu’elle soit reconnaissable lorsque tu inspectes un fichier signé.

<?php
declare(strict_types=1);
use NextPDF\Security\Signature\ByteRangeCalculator;
// Offsets the engine knows only after the whole PDF is written:
// $contentsStart — byte just before the '<' of the hex signature
// $contentsEnd — byte just after the '>' that closes it
// $fileLength — total file size in bytes
$range = ByteRangeCalculator::calculate(
contentsStart: $contentsStart,
contentsEnd: $contentsEnd,
fileLength: $fileLength,
);
// $range === [0, $contentsStart, $contentsEnd, $fileLength - $contentsEnd]
// Segment 1: file start → just before the signature value
// Segment 2: just after the signature value → end of file
// The signature value itself is the gap. It is never hashed.
$signedMessage = ByteRangeCalculator::extractSignedData($pdfBytes, $range);
// $signedMessage is segment 1 concatenated with segment 2 — exactly the
// bytes the cryptographic digest is computed over.

L’espace entre les deux segments est la valeur de la signature. Elle ne peut pas faire partie de son propre condensé ; c’est pourquoi la plage comporte deux morceaux, et non un seul.

Le piège est de croire qu’une signature valide signifie que tout le fichier que tu regardes est ce qui a été signé. Ce n’est pas le cas. Cela signifie que les octets situés à l’intérieur de la plage déclarée sont intacts. Une révision ultérieure peut légitimement ajouter du contenu — une seconde signature, des données de formulaire, du matériel de validation — en dehors de cette plage. La première signature reste valide, et elle ne dit rien sur cet ajout. Un visualiseur correct t’indique qu’une signature couvre « le document tel qu’il existait au moment de la signature », et non « chaque octet à l’écran ». Confondre les deux, c’est ainsi qu’un document signé acquiert un contenu non signé qui semble signé.

Cette page explique la structure, pas la confiance. Un ByteRange correctement formé et un objet CMS t’indiquent que les octets sont intacts et quelle clé a signé ces octets. Ils ne te disent pas, à eux seuls, si cette clé appartient à celui que tu crois, si son certificat était valide au moment de la signature, ou s’il a été ensuite révoqué. C’est le rôle du chemin de certification et de la révocation, traité dans Valider correctement une signature. Cette page ne traite pas non plus du moment où la signature a eu lieu, attesté par une autorité indépendante. Une heure de signature auto-déclarée n’est pas une heure de confiance — voir Horodatages et heure de confiance. NextPDF construit la structure décrite ici ; les certificats, les ancres de confiance et l’autorité d’horodatage sont fournis par ton déploiement, et non par le moteur.

Selon l’édition, le moteur livre la capacité de construire cette structure :

PAdES signature structure (byte range, signature dictionary, detached CMS) — edition availability
Edition Availability
Core

PAdES B-B : le dictionnaire de signature, le ByteRange de largeur fixe et l’objet CMS SignedData détaché décrit sur cette page.

Pro

Ajoute PAdES B-T — un horodatage de confiance sur la valeur de la signature — sur la même structure.

Enterprise

Ajoute les profils à long terme (B-LT, B-LTA) : matériel de validation intégré et horodatages de document superposés sur la même base de byte-range.

  • Dictionnaire de signature — le dictionnaire PDF qui nomme le gestionnaire de signature, le SubFilter, le ByteRange et la valeur Contents.
  • ByteRange — un tableau de paires d’entiers (offset, length) déclarant les octets exacts couverts par le condensé de la signature.
  • Contents — l’entrée hexadécimale contenant la valeur de la signature (pour PDF 2.0, un objet CMS SignedData détaché) ; elle est exclue de son propre condensé.
  • CMS SignedData — structure de Cryptographic Message Syntax (RFC 5652) portant le certificat du signataire et les octets de la signature.
  • PAdES — PDF Advanced Electronic Signatures : le profil ETSI des signatures CMS pour PDF, défini dans la série ETSI EN 319 142.
  • Signature d’approbation — une signature avec l’autorisation MDP 0 ; elle atteste le contenu sans restreindre les modifications ultérieures.
  • Signature de certification — une signature avec une autorisation DocMDP (MDP 13) qui limite les modifications ultérieures maintenant le document conforme.