HotPDF verifieert digitale handtekeningen in geladen PDF-documenten via drie THotPDF-methoden: GetLoadedSignatureInfo, VerifyLoadedSignature en VerifyLoadedSignatureEx, geïntroduceerd in v2.259.0. Het component berekent de hashes van de /ByteRange-segmenten van het originele bestand opnieuw, controleert het CMS-attribuut messageDigest en voert een RSA PKCS#1 v1.5-verificatie uit tegen het ingebedde certificaat van de ondertekenaar. Het retourneert svValid wanneer de bytes van het document intact zijn
Het scenario is alledaags en de belangen zijn dat niet. Een tegenpartij stuurt een ondertekend contract terug, uw workflow moet het archiveren, en iemand stelt de enige vraag die er toe doet: is dit het document dat we hebben verzonden, byte voor byte, ondertekend met het certificaat dat het claimt? Dat in code beantwoorden is de verificatiezijde van het handtekeningenverhaal; de ondertekeningszijde, het bouwen en insluiten van PAdES-handtekeningen in de eerste plaats, wordt behandeld in het bijbehorende artikel over het maken van PAdES digitale handtekeningen met HotPDF. Dit artikel gaat over de andere richting: er komt een al ondertekende PDF binnen en u wilt een programmatisch oordeel in plaats van een screenshot van het groene vinkje van Acrobat
Hoe bewijst een ondertekende PDF dat er niet mee is geknoeid?
Een PDF-handtekening beschermt specifieke bytebereiken van het bestand, niet een abstract idee van "het document". ISO 32000-1 §12.8 definieert het mechanisme: het handtekeningformulierveld bevat een woordenboek waarvan de vermelding /Contents een CMS SignedData-container (RFC 5652) bevat, en waarvan de array /ByteRange de exacte bestandsregio's noemt die de handtekening dekt, volgens §12.8.1. De array is a lijst van offset- en lengteparen, in de praktijk twee segmenten: alles voor de hex-string /Contents, en alles erna. De handtekeningwaarde kan zichzelf niet dekken, dus wordt het bestand rond dat gat gehasht
Dat ontwerp heeft een gevolg dat de hele API vormgeeft: verificatie moet de originele geserialiseerde bytes hashen, exact zoals ze op schijf staan. Een geparst objectmodel is hiervoor onbruikbaar, omdat het opnieuw serialiseren van zelfs een ongewijzigd document andere bytes produceert. HotPDF verifieert daarom tegen het bronbestand van waaruit het document is geladen, of tegen een door u aangeleverde TStream van ruwe bytes, nooit tegen de weergave in het geheugen
Metagegevens van handtekeningen lezen alvorens iets te verifiëren
GetLoadedSignatureInfo parseert het handtekeningwoordenboek en de bijbehorende CMS-container zonder één enkele byte van het document aan te raken, wat dit de juiste eerste aanroep maakt wanneer u alleen wilt weergeven wie er heeft ondertekend en wanneer. Handtekeningvelden zijn geïndexeerd vanaf 0 in de volgorde van formuliervelden, en GetLoadedSignatureFieldCount vertelt u hoeveel er bestaan. Het geretourneerde record THPDFSignatureInfo bevat de veldnaam, /SubFilter, the algemene naam van het certificaat van de ondertekenaar, subject en issuer distinguished names, serienummer, geldigheidsdatums, het tijdstip van ondertekening (van het ondertekende attribuut indien aanwezig, anders het /M-item van het woordenboek), de naam van het digest-algoritme, en de strings /Reason, /Location en /ContactInfo. Het lid Status blijft svNotVerified, een eerlijk label voor "geparst, niet gecontroleerd"
var
Pdf: THotPDF;
Info: THPDFSignatureInfo;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('signed-contract.pdf');
for I := 0 to Pdf.GetLoadedSignatureFieldCount - 1 do
begin
Info := Pdf.GetLoadedSignatureInfo(I);
Writeln('Field: ', Info.FieldName);
Writeln('Signer: ', Info.SignerName);
Writeln('Issuer: ', Info.IssuerDN);
Writeln('Algorithm: ', Info.HashAlgorithm);
Writeln('SubFilter: ', Info.SubFilter);
end;
finally
Pdf.Free;
end;
end;
De cryptografische controle uitvoeren
VerifyLoadedSignatureEx voert de volledige verificatie uit voor een uit een bestand geladen document en geeft in één aanroep het ingevulde informatierecord terug: het heropent het bronbestand, hasht de /ByteRange-segmenten met het SignerInfo digest-algoritme, vergelijkt het resultaat met het ondertekende attribuut messageDigest (RFC 5652 §5.4) en verifieert vervolgens via RSA de handtekening over de DER SET-hercodering van de ondertekende attributen. Wanneer een handtekening geen ondertekende attributen bevat, wordt de RSA-controle in plaats daarvan rechtstreeks over de documenthash uitgevoerd. Ondersteunde handtekeningen zijn RSA PKCS#1 v1.5 met SHA-1, SHA-256, SHA-384 of SHA-512 digests, wat de subfilters adbe.pkcs7.detached en ETSI.CAdES.detached dekt die door gangbare ondertekeningstools worden geproduceerd
var
Status: THPDFSignatureVerifyStatus;
Info: THPDFSignatureInfo;
begin
Status := Pdf.VerifyLoadedSignatureEx(0, Info);
case Status of
svValid:
if Info.CoversWholeDocument then
Writeln('Valid; signature covers the whole file')
else
Writeln('Valid; file was extended after signing');
svDigestMismatch:
Writeln('Document bytes changed after signing');
svSignatureInvalid:
Writeln('RSA check failed over signed attributes');
svUnsupportedAlgorithm:
Writeln('Non-RSA key or unknown digest algorithm');
svMalformed:
Writeln('CMS container could not be parsed');
svSourceUnavailable:
Writeln('No source bytes; use the TStream overload');
end;
end;
Twee implementatiedetails zijn de moeite waard om te weten omdat ze fouten verklaren die er van buitenaf mysterieus uitzien. Ten eerste is de controle van de ondertekende attributen kieskeurig wat betreft codering: in het bestand zijn de attributen gelabeld als [0] IMPLICIT, maar de handtekening is berekend over hun DER SET OF-vorm, dus de verifieerder labelt opnieuw alvorens te hashen, exact zoals RFC 5652 §5.4 verwerkt. Een zelfgebouwde verifieerder die de bytes hasht zoals ze in het bestand verschijnen, zal elk correct ondertekend document weigeren. Ten tweede is /Contents conventioneel met nullen opgevuld tot een gereserveerd bytebudget, dus de verifieerder kapt de DER-blob af tot de werkelijke lengte van de buitenste SEQUENCE alvorens te parsen; vuil uitziende naloopnullen zijn normaal, geen corruptie. Dezelfde familie van ASN.1-parserifserisico's, aan de certificaatimportzijde, is het onderwerp van the article on PKCS#12 and ASN.1 security hardening in HotPDF
What does a valid signature actually guarantee?
svValid betekent exact dit: de bytes die door /ByteRange worden genoemd, hashen naar de waarde die de ondertekenaar heeft ondertekend, en de handtekening is geldig onder de publieke sleutel van het certificaat dat is ingebed in de CMS-container. Dat is byte-integriteit plus sleutelbinding, en niets meer. Certificaatketen- en vertrouwen-validatie vallen expliciet buiten het bereik van de verifieerder van HotPDF: deze doorloopt de keten niet naar een root-certificaat, controleert geen intrekkingen en raadpleegt geen trust store. Een zelfondertekend certificaat van een aanvaller die een gewijzigd document opnieuw heeft ondertekend, zal als svValid verifiëren, omdat de wiskunde intern consistent is. Of de ondertekenaar is wie hij beweert te zijn, en of iemand hem moet vertrouwen, is een beleidsbeslissing die thuishoort in een afzonderlijke laag, of dat nu de whitelist van uw organisatie is, de Windows-certificaatstore of een validatieautoriteit
De vlag CoversWholeDocument bewaakt een subtieler gat. Een handtekening dekt altijd alleen de bijbehorende /ByteRange, en het incrementele updatemechanisme van PDF staat het toevoegen van inhoud na een handtekening toe zonder deze ongeldig te maken. Dit is inherent aan het ontwerp en is de manier waarop workflows met meerdere handtekeningen functioneren. De vlag wordt berekend tijdens de verificatie en is alleen waar wanneer de twee segmenten plus het /Contents-gat het gehele bestand beslaan. Wanneer svValid binnenkomt met CoversWholeDocument op onwaar (false), is de ondertekende revisie intact maar bevat het bestand latere toevoegingen, en of u die toevoegingen tolereert is iets wat uw workflow moet beslissen
Uit streams geladen en gecodeerde documenten hebben hun eigen bronbytes nodig
De parameterloze VerifyLoadedSignature en VerifyLoadedSignatureEx zijn ervan afhankelijk dat de component onthoudt uit welk bestand het document afkomstig is. Laad het document uit een stream en er is geen bestandsnaam om opnieuw te openen; hetzelfde geldt na het wachtwoord-herlaadpad dat wordt gebruikt voor gecodeerde documenten, de workflow die wordt beschreven in het artikel over AES-256 PDF-versleuteling met HotPDF. In beide gevallen retourneren de bestands-ondersteunde overloads svSourceUnavailable in plaats van te gokken. De oplossing is de TStream-overload, waarmee u de originele ruwe bytes kunt overhandigen van waar u ze ook bewaart: een bestand dat u nog heeft, een geheugenbuffer, of een databaseblob
var
Src: TFileStream;
Status: THPDFSignatureVerifyStatus;
Info: THPDFSignatureInfo;
begin
// Stream-loaded document: the component holds no source
// file name, so supply the original bytes yourself.
Src := TFileStream.Create('signed-contract.pdf',
fmOpenRead or fmShareDenyWrite);
try
Status := Pdf.VerifyLoadedSignature(0, Src, Info);
if Status <> svValid then
Writeln('Verification failed: ', Ord(Status));
finally
Src.Free;
end;
end;
Rapporteren wat u niet kunt verifiëren
Een verifieerder die alleen "geldig" en "ongeldig" kent, zal documenten die hij simpelweg niet begrijpt verkeerd rapporteren, dus de status-enumeratie scheidt de gevallen die uw UI zou moeten onderscheiden. svDigestMismatch betekent dat de documentbytes na ondertekening zijn gewijzigd, het klassieke signaal van knoeien. svSignatureInvalid betekent dat de bytes correct hashen maar de RSA-controle is mislukt, wat wijst op een beschadigde of vervalste handtekeningwaarde. svUnsupportedAlgorithm is het eerlijke antwoord voor ECDSA-sleutels en niet-herkende digests: de handtekening kan prima zijn, HotPDF kan deze simpelweg niet controleren, en dat rapporteren als "ongeldig" zou een gezond document onrecht aandoen. svMalformed markeert een CMS-container dat helemaal niet kon worden geparst. Voor poortcontroles retourneert VerifyAllLoadedSignatures alleen true wanneer er ten minste één handtekeningveld bestaat en elk daarvan als svValid verifieert, een handige enkele boolean voor een archiefinname-pijplijn die met niets minder genoegen neemt
Handtekeningverificatie, PAdES-ondertekening, AES-256 versleuteling en de API voor het bewerken van geladen documenten worden allemaal geleverd in dezelfde native VCL-bibliotheek voor Delphi en C++Builder, zonder externe DLL-afhankelijkheden. De volledige lijst met functies en ondersteunde IDE-versies is te vinden op de productpagina van HotPDF Component