HotPDF verifiserer digitale signaturer i innlastede PDF-dokumenter gjennom tre THotPDF-metoder: GetLoadedSignatureInfo, VerifyLoadedSignature og VerifyLoadedSignatureEx, introdusert i v2.259.0. Komponenten hasher /ByteRange-segmentene i originalfilen på nytt, kontrollerer CMS-attributtet messageDigest og kjører en RSA PKCS#1 v1.5-verifisering mot det innebygde signerersertifikatet, og returnerer svValid når dokumentbytene er intakte
Situasjonen er hverdagslig, men det som står på spill, er det ikke. En motpart returnerer en signert kontrakt, arbeidsflyten din må arkivere den, og noen stiller det eneste spørsmålet som betyr noe: er dette dokumentet vi sendte, byte for byte, signert av sertifikatet det utgir seg for? Å svare på det i kode er verifiseringssiden av signaturhistorien; signeringssiden, det å bygge og bygge inn PAdES-signaturer i utgangspunktet, er dekket i søsterartikkelen om å lage PAdES-digitale signaturer med HotPDF. Denne artikkelen handler om den andre retningen: en PDF ankommer ferdig signert, og du vil ha en programmatisk dom i stedet for et skjermbilde av Acrobats grønne haketegn
Hvordan beviser en signert PDF at den ikke er manipulert?
En PDF-signatur beskytter bestemte byteområder i filen, ikke en abstrakt forestilling om «dokumentet». ISO 32000-1 §12.8 definerer mekanismen: signaturfeltet bærer en dictionary hvis /Contents-oppføring holder en CMS SignedData-container (RFC 5652), og hvis /ByteRange-array navngir nøyaktig de filområdene signaturen dekker, jf. §12.8.1. Arrayet er en liste over offset- og lengdepar, i praksis to segmenter: alt før /Contents-heksstrengen, og alt etter den. Signaturverdien kan ikke dekke seg selv, så filen hashes rundt det hullet
Den utformingen har en konsekvens som former hele API-et: verifiseringen må hashe de opprinnelige serialiserte bytene, nøyaktig slik de ligger på disk. En parset objektmodell er ubrukelig til dette, fordi reserialisering av selv et uendret dokument gir andre byte. HotPDF verifiserer derfor mot kildefilen dokumentet ble lastet fra, eller mot en TStream med rå byte som du oppgir, aldri mot representasjonen i minnet
Lese signaturmetadata før du verifiserer noe
GetLoadedSignatureInfo parser signatur-dictionaryen og dens CMS-container uten å røre en eneste dokumentbyte, noe som gjør den til det riktige første kallet når du bare skal vise hvem som signerte og når. Signaturfelt indekseres fra 0 i skjemafeltrekkefølge, og GetLoadedSignatureFieldCount forteller deg hvor mange som finnes. Den returnerte THPDFSignatureInfo-recorden bærer feltnavnet, /SubFilter, signeringssertifikatets common name, subject- og issuer-distinguished names, serienummer, gyldighetsdatoer, signeringstidspunktet (fra den signerte attributten når den finnes, ellers dictionaryens /M-oppføring), navnet på digest-algoritmen, samt /Reason-, /Location- og /ContactInfo-strengene. Status-medlemmet forblir svNotVerified, en ærlig etikett for «parset, ikke kontrollert»
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;
Kjøre den kryptografiske kontrollen
VerifyLoadedSignatureEx utfører hele verifiseringen for et fillastet dokument og leverer den utfylte info-recorden tilbake i ett kall: den åpner kildefilen på nytt, hasher /ByteRange-segmentene med SignerInfo-digestalgoritmen, sammenligner resultatet med den signerte attributten messageDigest (RFC 5652 §5.4), og RSA-verifiserer deretter signaturen over DER SET-reenkodingen av de signerte attributtene. Når en signatur ikke bærer signerte attributter, kjører RSA-kontrollen direkte over dokumenthashen i stedet. Støttede signaturer er RSA PKCS#1 v1.5 med SHA-1-, SHA-256-, SHA-384- eller SHA-512-digester, noe som dekker subfiltrene adbe.pkcs7.detached og ETSI.CAdES.detached som toneangivende signeringsverktøy produserer
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;
To implementasjonsdetaljer er verdt å kjenne til, fordi de forklarer feil som ser mystiske ut utenfra. For det første er kontrollen av signerte attributter kresen på koding: inne i filen er attributtene tagget [0] IMPLICIT, men signaturen ble beregnet over deres DER SET OF-form, så verifisereren tagger om før den hasher, akkurat slik RFC 5652 §5.4 krever. En hjemmesnekret verifiserer som hasher bytene slik de står i filen, vil avvise ethvert korrekt signert dokument. For det andre er /Contents konvensjonelt nullpolstret opp til et reservert bytebudsjett, så verifisereren trunkerer DER-blobben til den faktiske lengden på sin ytre SEQUENCE før parsingen; etterfølgende nuller som ser ut som søppel, er normalt, ikke korrupsjon. Den samme familien av ASN.1-parsefarer, på sertifikatimportsiden, er temaet for artikkelen om PKCS#12- og ASN.1-sikkerhetsherding i HotPDF
Hva garanterer egentlig en gyldig signatur?
svValid betyr nøyaktig dette: bytene som /ByteRange navngir, hasher til verdien signereren signerte, og signaturen verifiserer under den offentlige nøkkelen til sertifikatet som er innebygd i CMS-containeren. Det er byteintegritet pluss nøkkelbinding, og ikke noe mer. Sertifikatkjede- og tillitsvalidering er uttrykkelig utenfor virkeområdet til HotPDFs verifiserer: den går ikke kjeden opp til en rot, sjekker ikke tilbakekalling og slår ikke opp i noe tillitslager. Et selvsignert sertifikat fra en angriper som signerte et modifisert dokument på nytt, vil verifisere som svValid, fordi matematikken er internt konsistent. Hvorvidt signereren er den vedkommende utgir seg for, og hvorvidt noen bør stole på vedkommende, er en policybeslutning som hører hjemme i et eget lag, enten det er organisasjonens sertifikathviteliste, Windows-sertifikatlageret eller en valideringsmyndighet
CoversWholeDocument-flagget vokter et mer subtilt gap. En signatur dekker aldri annet enn sin /ByteRange, og PDF-ens mekanisme for inkrementelle oppdateringer tillater å føye til innhold etter en signatur uten å ugyldiggjøre den, noe som er tilsiktet og er slik arbeidsflyter med flere signaturer fungerer. Flagget beregnes under verifiseringen og er sant bare når de to segmentene pluss /Contents-gapet spenner over hele filen. Når svValid kommer med CoversWholeDocument lik usann, er den signerte revisjonen intakt, men filen inneholder senere tillegg, og hva de tilleggene endret, er noe arbeidsflyten din bør avgjøre om den vil tolerere
Strømlastede og krypterte dokumenter trenger sine egne kildebyte
De parameterløse VerifyLoadedSignature og VerifyLoadedSignatureEx avhenger av at komponenten husker hvilken fil dokumentet kom fra. Last dokumentet fra en strøm, og det finnes ingen filnavn å åpne på nytt; det samme gjelder etter passordomlastingsstien som brukes for krypterte dokumenter, arbeidsflyten som er beskrevet i artikkelen om AES-256-PDF-kryptering med HotPDF. I begge tilfeller returnerer de filbaserte overbelastningene svSourceUnavailable i stedet for å gjette. Løsningen er TStream-overbelastningen, som lar deg levere de opprinnelige rå bytene fra hvor du enn tok vare på dem, en fil du fortsatt har, en minnebuffer, en databaseblob
var
Src: TFileStream;
Status: THPDFSignatureVerifyStatus;
Info: THPDFSignatureInfo;
begin
// Strømlastet dokument: komponenten holder ikke på noe
// kildefilnavn, så oppgi originalbytene selv.
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;
Rapportere det du ikke kan verifisere
En verifiserer som bare kjenner «gyldig» og «ugyldig», vil feilrapportere dokumenter den rett og slett ikke forstår, så statusenumerasjonen skiller de tilfellene brukergrensesnittet ditt bør holde fra hverandre. svDigestMismatch betyr at dokumentbytene ble endret etter signering, det klassiske manipulasjonssignalet. svSignatureInvalid betyr at bytene hasher korrekt, men at RSA-kontrollen mislyktes, noe som peker mot en korrupt eller forfalsket signaturverdi. svUnsupportedAlgorithm er det ærlige svaret for ECDSA-nøkler og ukjente digester: signaturen kan være helt i orden, HotPDF kan bare ikke kontrollere den, og å rapportere det som «ugyldig» ville sverte et friskt dokument. svMalformed flagger en CMS-container som ikke lot seg parse i det hele tatt. For portvaktkontroller returnerer VerifyAllLoadedSignatures sant bare når minst ett signaturfelt finnes og hvert eneste av dem verifiserer som svValid, en praktisk enkelt boolsk verdi for en arkivinntaksrørledning som avviser alt mindre
Signaturverifisering, PAdES-signering, AES-256-kryptering og API-et for redigering av innlastede dokumenter følger alle med i det samme native VCL-biblioteket for Delphi og C++Builder, uten eksterne DLL-avhengigheter; den fullstendige funksjonslisten og støttede IDE-versjoner finner du på produktsiden for HotPDF Delphi Component