Un PDF signé qui a changé après la signature n est pas automatiquement corrompu. La norme ISO 32000-1 autorise les mises à jour incrémentielles par-dessus une signature, et seules certaines d entre elles enfreignent la politique fixée par le signataire. HotPDF Component pour Delphi et C++Builder répond à cette question avec AnalyzeLoadedSignatureRevisions, qui classe chaque révision post-signature et l évalue par rapport à DocMDP et FieldMDP. Le scénario est familier à quiconque livre des logiciels de contrat : votre client signe un contrat d achat, l envoie, et le récupère avec une page d annexe attachée. Le lecteur affiche une barre jaune indiquant que la signature est intacte mais que le document a été modifié depuis sa signature, et personne dans la salle ne peut dire s il s agit d un flux de travail de contresignature normal ou de quelqu un modifiant discrètement un contrat signé
Qu est-ce qui compte comme un changement légal après signature ?
Un changement est légal quand sa catégorie sémantique tombe dans la permission que la signature de certification a déclarée. La norme ISO 32000-1 §12.8.2.2 définit la transformation DocMDP avec une valeur /P de 1, 2 ou 3 : 1 n autorise aucun changement, 2 autorise le remplissage de formulaire et la signature, 3 autorise le remplissage de formulaire, la signature et les annotations. HotPDF expose cela sous forme de valeurs THPDFDocMDPPermission dmpNoChanges, dmpFormFillAndSign et dmpFormFillSignAndAnnotate, avec dmpNone réservée aux résultats d inspection ne portant aucune transformation DocMDP
Les catégories sont ordonnées, et cet ordre est le moteur de tout le contrôle. THPDFRevisionModificationLevel comprend rmlNone, rmlLongTermValidation, rmlFormFillAndSign, rmlAnnotations, rmlOther, délibérément disposés pour qu un ordinal plus grand ne soit jamais moins restrictif. Un document entier se réduit au niveau maximal observé sur toutes les révisions après la signature, et la comparaison DocMDP devient un simple test d entier. Une nuance compte tôt : au niveau dmpNoChanges, l analyse accepte quand même rmlLongTermValidation. Ajouter du matériel de validation DSS et VRI ou un horodatage de document à un fichier certifié relève de la maintenance de la signature, pas d une modification du document, et le traiter comme une violation casserait tout flux de travail d archivage à long terme existant
Comment HotPDF reconstruit-il la chaîne de révisions ?
De façon structurelle, pas heuristique. Selon la norme ISO 32000-1 §7.5.6, une mise à jour incrémentielle ajoute une nouvelle section de références croisées dont /Prev pointe vers la précédente, donc HotPDF lit startxref depuis la fin, analyse la section à cet endroit, suit /Prev vers l arrière et répète, renvoyant les sections de la plus ancienne à la plus récente. Deux limites de sécurité se trouvent dans cette boucle et toutes deux méritent d être connues lors du triage d un fichier qui échoue : un /Prev pointant vers un décalage déjà visité termine le parcours avec un diagnostic de cycle explicite plutôt que de tourner indéfiniment, et une chaîne de plus de mille révisions est rejetée purement et simplement. Les deux apparaissent dans Analysis.Issue avec la fonction renvoyant False, et aucun des deux ne devrait être masqué, car un /Prev cyclique est un fichier malformé ou hostile plutôt qu inhabituel
Quatre formes historiques apparaissent dans des documents réels et toutes les quatre sont gérées : les tables xref traditionnelles analysées ligne par ligne, les flux de références croisées décompressés et décodés via leurs champs /W et /Index, les fichiers à référence hybride dont le trailer traditionnel porte une clé /XRefStm qui est analysée et fusionnée dans la même révision (le cas des producteurs Office, couvert dans l article sur les flux de références croisées hybrides), et les objets vivant à l intérieur d un conteneur ObjStm, ce qui compte car une mise à jour moderne place généralement le dictionnaire modifié dans un flux compressé plutôt que de l écrire directement, comme décrit dans l article sur les flux d objets et les mises à jour incrémentielles. La signature ancre la division : /ByteRange[2] + /ByteRange[3] devient SignedRevisionLength, et chaque section à ce décalage ou au-delà est post-signature. Savoir si la plage d octets se hache toujours correctement est une question distincte, à laquelle répond VerifyLoadedSignature, couverte dans l article sur la vérification des signatures numériques PDF
Comment chaque objet modifié est-il classé
La classification s exécute par objet, puis se propage le long des références. Pour chaque numéro d objet qu une section post-signature touche, HotPDF lit le nouveau corps et le corps tel qu il se présentait dans l instantané signé ; un corps identique est rmlNone, car les producteurs réécrivent effectivement des objets sans les modifier. Les reconnaisseurs sont volontairement étroits. Un objet /Type /DocTimeStamp, ou dont le /SubFilter est ETSI.RFC3161, est rmlLongTermValidation, tout comme tout ce qui est accessible depuis l arbre /DSS du catalogue ; un dictionnaire /Type /Sig est rmlFormFillAndSign. Pour les conteneurs, le test porte sur quelles clés ont bougé, pas sur ce qu est l objet : le catalogue ne peut que gagner ou modifier /DSS, /Extensions ou /AcroForm ; le dictionnaire AcroForm uniquement /Fields, /SigFlags, /NeedAppearances, /DR, /DA ou /Q ; une page uniquement /Annots ; un champ ou un widget uniquement /V, /AP, /AS ou /M. Tout ce qui sort de ces ensembles retombe à rmlOther, ce qui est exactement comment la page d annexe ajoutée est capturée : ajouter une page réorganise l arbre de pages de façons qu aucune liste blanche ne couvre, et aucune quantité de remplissage de formulaire légitime ne lui ressemble
Puis les niveaux se propagent, chaque conteneur héritant du niveau maximal des enfants modifiés qu il référence, itéré jusqu à ce que l affectation se stabilise. C est ce qui fait fonctionner les flux d apparence. Un champ de texte rempli réécrit /V et pointe vers un nouveau flux /AP, et ce flux à lui seul est un blob anonyme d opérateurs de contenu sans type reconnaissable ; parce que le champ qui le possède est rmlFormFillAndSign, le flux hérite du même niveau au lieu de retomber à rmlOther. La même propagation porte le contexte DSS sur les flux de certificat et de révocation qui seraient autrement inclassables
Pourquoi un objet illisible compte-t-il comme une violation ?
Parce que l alternative est un validateur mis en échec en écrivant quelque chose qu il ne comprend pas. Trois situations aboutissent à rmlOther sans appel dans HotPDF : un objet dont le corps n a pas pu être lu depuis la révision, un objet que la révision marque comme libéré, et un objet ne correspondant à aucun des reconnaisseurs ci-dessus. Chacun enregistre un diagnostic spécifique dans le champ Issue de la révision, de sorte qu un opérateur puisse voir quel numéro d objet a produit le verdict
La libération est la plus tranchante des trois. Une révision post-signature qui marque un objet précédemment défini comme libre a supprimé du contenu d un document signé, et aucun niveau de permission sous le §12.8.2.2 ne l autorise ; les numéros d objet atterrissent dans FreedObjectNumbers et la révision est élevée à rmlOther. Les objets illisibles suivent la même logique pour une raison différente. Un validateur qui ne peut pas analyser un objet n a aucune base pour le déclarer inoffensif, et la réponse honnête à cela n est pas le silence. Signaler une construction inhabituelle mais bénigne comme une violation coûte une révision humaine ; l erreur inverse livre un contrat signé avec une modification passée inaperçue à l intérieur
Lire le verdict en Delphi
L appel est court. Chargez le document, choisissez un index de signature, lisez l enregistrement ; la surcharge sans paramètre rouvre le fichier depuis lequel le document a été chargé, et la surcharge TStream prend des octets fournis par l appelant et restaure la position du flux avant de revenir. PolicyCompliant est le booléen unique que la plupart des appelants veulent, combinant trois décisions indépendantes : la validité structurelle des dictionnaires de permission, DocMDPCompliant, et FieldMDPCompliant. Gardez les composants visibles dans votre interface plutôt que de les réduire, et notez qu un document sans transformation DocMDP laisse DocMDPCompliant à True, puisqu une signature d approbation ordinaire ne déclare aucune politique à violer et le ModificationLevel agrégé est alors descriptif plutôt qu un verdict
var
Pdf: THotPDF;
Analysis: THPDFSignatureRevisionAnalysis;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('contract-countersigned.pdf') > 0 then
begin
if Pdf.AnalyzeLoadedSignatureRevisions(0, Analysis) then
begin
if Analysis.PolicyCompliant then
Writeln('Post-signature changes stay inside the signing policy')
else
Writeln('Policy violation: ', string(Analysis.Issue));
end
else
Writeln('Analysis could not run: ', string(Analysis.Issue));
end;
finally
Pdf.Free;
end;
end;
Pour le triage, vous voulez généralement le détail par révision plutôt que le résumé, car il indique à quel moment de l historique du document les choses ont mal tourné. Chaque entrée de Analysis.Revisions porte son index dans la chaîne, le décalage de références croisées où elle a été écrite, son propre niveau de modification, et les numéros d objet impliqués
const
LevelNames: array[THPDFRevisionModificationLevel] of string =
('none', 'long-term validation', 'form fill and sign',
'annotations', 'other');
var
I: Integer;
begin
Writeln(Format('%d revisions in chain, signature sits at index %d',
[Analysis.TotalRevisionCount, Analysis.SignedRevisionIndex]));
for I := 0 to High(Analysis.Revisions) do
Writeln(Format(' rev %d at offset %d: %s (%d changed, %d freed) %s',
[Analysis.Revisions[I].RevisionIndex,
Analysis.Revisions[I].XRefOffset,
LevelNames[Analysis.Revisions[I].ModificationLevel],
Length(Analysis.Revisions[I].ChangedObjectNumbers),
Length(Analysis.Revisions[I].FreedObjectNumbers),
string(Analysis.Revisions[I].Issue)]));
end;
FieldMDP est jugé séparément, et c est délibéré
Un document peut satisfaire DocMDP et rester illégitime, raison pour laquelle FieldMDPCompliant est un booléen distinct plutôt que replié dans la comparaison de niveau. La norme ISO 32000-1 §12.8.2.4 définit la transformation FieldMDP, et le §12.7.5.5 l entrée /SigFieldLock associée, pour geler des champs de formulaire nommés au moment de la signature même là où le document dans son ensemble autorise encore le remplissage de formulaire. Remplir un champ est une action de niveau 2 ; remplir un champ que le signataire a verrouillé est une violation quel que soit le niveau. HotPDF lit la portée dans THPDFFieldLockAction comme flaAll, flaInclude ou flaExclude, avec flaNone pour les résultats ne portant aucune politique de verrouillage, et les noms dans Permissions.FieldNames : flaAll verrouille tout, flaInclude verrouille les noms listés, flaExclude verrouille tout sauf eux. Un détail compte à la lecture des résultats, à savoir que seuls les champs déjà présents dans l instantané signé sont rapportés dans ChangedFieldNames, car un champ créé entièrement après la signature n a aucun état signé à contredire et est capturé par le chemin DocMDP à la place
var
Source: TFileStream;
Analysis: THPDFSignatureRevisionAnalysis;
I: Integer;
begin
Source := TFileStream.Create('contract.pdf', fmOpenRead or fmShareDenyWrite);
try
if Pdf.AnalyzeLoadedSignatureRevisions(0, Source, Analysis) then
if Analysis.Permissions.HasFieldMDP and (not Analysis.FieldMDPCompliant) then
for I := 0 to High(Analysis.ChangedFieldNames) do
Writeln('modified after locking: ',
string(Analysis.ChangedFieldNames[I]));
finally
Source.Free; // stream position was restored before the call returned
end;
end;
Ce que cette analyse ne vous dira pas
Elle ne vérifie pas une signature. AnalyzeLoadedSignatureRevisions raisonne sur la structure et les permissions ; savoir si la plage d octets signée se hache toujours à la valeur dans le blob CMS, et si le certificat du signataire chaîne vers quelque chose en qui vous avez confiance, relève de VerifyLoadedSignature et VerifyLoadedSignatureWithTrust. Un fichier peut être parfaitement conforme à la politique et cryptographiquement sans valeur, donc les deux contrôles doivent siéger côte à côte dans toute vraie porte d acceptation. Elle ne lit pas non plus l intention à l intérieur des flux de contenu : une page dont le flux de contenu a été remplacé intégralement est capturée comme un changement hors liste blanche, mais l analyse ne vous dira pas que le remplacement a échangé un montant de paiement. Un verdict rmlOther signifie qu un humain devrait regarder, pas qu une fraude a eu lieu, et un verdict conforme signifie que le changement correspond à une catégorie autorisée, pas que le changement était souhaité. Quand tout ce dont vous avez besoin est ce que le signataire a déclaré, sans le parcours des révisions, GetLoadedSignaturePermissions renvoie les dictionnaires de politique à eux seuls
Tout ce qui est décrit ici s exécute nativement en Delphi et C++Builder sans aucun service de signature externe dans la boucle, ce qui rend pratique de l exécuter sur chaque document entrant plutôt que seulement ceux que quelqu un soupçonnait déjà. L API complète de signature et de révision, y compris les méthodes de permission et de vérification avec lesquelles elle s associe, fait partie de HotPDF Component pour Delphi et C++Builder