Teknisk artikel

Verificer digitale PDF-signaturer i Delphi med HotPDF

HotPDF verificerer digitale signaturer i indlæste PDF-dokumenter via tre THotPDF-metoder: GetLoadedSignatureInfo, VerifyLoadedSignature og VerifyLoadedSignatureEx, introduceret i v2.259.0. Komponenten genberegner hash-værdien for den oprindelige fils /ByteRange-segmenter, kontrollerer CMS-attributten messageDigest og kører en RSA PKCS#1 v1.5-verifikation mod det integrerede underskrivercertifikat, og returnerer svValid, når dokumentets bytes er intakte

Scenariet er almindeligt, men indsatsen er høj. En modpart returnerer en signeret kontrakt, dit arbejdsflow skal arkivere den, og nogen stiller det eneste spørgsmål, der betyder noget: Er dette det dokument, vi sendte, byte for byte, signeret med det certifikat, det hævder? At besvare dette i kode er verifikationssiden af signaturhistorien; signeringssiden, altså hvordan man overhovedet bygger og integrerer PAdES-signaturer, dækkes i ledsagerartiklen om oprettelse af digitale PAdES-signaturer med HotPDF. Denne artikel handler om den anden retning: En PDF ankommer allerede signeret, og du ønsker en programmatisk afgørelse frem for et skærmbillede af Acrobats grønne flueben

Hvordan beviser en signeret PDF, at der ikke er manipuleret med den?

En PDF-signatur beskytter specifikke byte-områder i filen, ikke en abstrakt forestilling om "dokumentet." ISO 32000-1 §12.8 definerer mekanismen: Signaturformularfeltet indeholder en ordbog, hvis /Contents-post indeholder en CMS SignedData-beholder (RFC 5652), og hvis /ByteRange-array navngiver de præcise filområder, signaturen dækker, i henhold til §12.8.1. Arrayet is en liste over par af offset og længde, i praksis to segmenter: Alt før /Contents-hexstrengen og alt efter den. Signaturværdien kan ikke dække sig selv, så filen hashes udenom dette hul

Dette design har en konsekvens, som præger hele API'en: Verifikationen skal hashe de oprindelige serialiserede bytes, nøjagtigt som de ligger på disken. En fortolket objektmodel er ubrugelig til dette, fordi en re-serialisering af selv et uændret dokument producerer andre bytes. HotPDF verificerer derfor mod den kildefil, som dokumentet blev indlæst fra, eller mod en TStream med rå bytes, som du leverer, og aldrig mod dets repræsentation i hukommelsen

Læsning af signatur-metadata før verifikation

GetLoadedSignatureInfo fortolker signaturordbogen og dens CMS-beholder uden at røre ved en eneste byte i dokumentet, hvilket gør det til det rette første kald, når du blot skal vise, hvem der har signeret og hvornår. Signaturfelter indekseres fra 0 i formularfelt-rækkefølge, og GetLoadedSignatureFieldCount fortæller, hvor mange der findes. Den returnerede THPDFSignatureInfo-post indeholder feltnavnet, /SubFilter, underskrivercertifikatets kaldenavn (common name), subject og issuer distinguished names, serienummer, gyldighedsdatoer, signeringstidspunktet (fra den signerede attribut, hvis den findes, ellers ordbogens /M-post), hash-algoritmens navn og strengene for /Reason, /Location og /ContactInfo. Dens Status-medlem forbliver svNotVerified, en ærlig betegnelse for "fortolket, ikke kontrolleret"

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;

Kørsel af den kryptografiske kontrol

VerifyLoadedSignatureEx udfører den fulde verifikation for et fil-indlæst dokument og returnerer den udfyldte info-post i ét kald: Den genåbner kildefilen, hashes /ByteRange-segmenterne med SignerInfo-hash-algoritmen, sammenligner resultatet med den signerede attribut messageDigest (RFC 5652 §5.4), og RSA-verificerer derefter signaturen over DER SET-genkodningen af de signerede attributter. Når en signatur ikke indeholder nogen signerede attributter, kører RSA-kontrollen i stedet direkte over dokumentets hash. Understøttede signaturer er RSA PKCS#1 v1.5 med SHA-1-, SHA-256-, SHA-384- eller SHA-512-resuméer, hvilket dækker subfiltrene adbe.pkcs7.detached og ETSI.CAdES.detached, som produceres af gængse signeringsværktøjer

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 implementeringsdetalser er værd at kende, fordi de forklarer fejl, der udefra kan se mystiske ud. For det første er kontrollen af de signerede attributter følsom over for kodning: Inde i filen er attributterne markeret med [0] IMPLICIT, men signaturen blev beregnet over deres DER SET OF-form, så verifikatoren ommærker før hashing, præcis som RFC 5652 §5.4 foreskriver. En hjemmelavet verifikator, der hashes de bytes, som de optræder i filen, vil afvise ethvert korrekt signeret dokument. For det andet er /Contents konventionelt udfyldt med nuller til et reserveret byte-budget, så verifikatoren afskærer DER-blobben til den faktiske længde af dens ydre SEQUENCE før fortolkning; uordentlige, efterfølgende nuller er normale og ikke tegn på korruption. Den samme familie af ASN.1-fortolkningsrisici, på certifikatimport-siden, er emnet for artiklen om PKCS#12- og ASN.1-sikkerhedshærdning i HotPDF

Hvad garanterer en gyldig signatur reelt?

svValid betyder præcis dette: De bytes, der navngives af /ByteRange, hashes til den værdi, som underskriveren signerede, og signaturen verificeres under den offentlige nøgle for det certifikat, der er indlejret i CMS-beholderen. Det er byte-integritet plus nøglebinding, og intet andet. Certifikatkæde- og tillidsvalidering er eksplicit uden for rammerne af HotPDFs verifikator: Den gennemgår ikke kæden til en rod, kontrollerer ikke tilbagekaldelse eller konsulterer nogen tillidsserver. Et selvsigneret certifikat fra en angriber, der har re-signeret et ændret dokument, vil verificere som svValid, fordi matematikken internt er konsistent. Hvorvidt underskriveren er den, vedkommende udgiver sig for at være, og om man skal stole på dem, er en politikbeslutning, der hører hjemme i et separat lag, uanset om det er din organisations whitelist for certifikater, Windows' certifikatlager eller en valideringsmyndighed

Flaget CoversWholeDocument beskytter mod et mere subtilt hul. En signatur dækker altid kun sit /ByteRange, og PDF-filens inkrementelle opdateringsmekanisme tillader at tilføje indhold efter en signatur uden at ugyldiggøre den, hvilket er tilsigtet og er måden, hvorpå arbejdsgange med flere signaturer fungerer. Flaget beregnes under verifikationen og er kun sandt (true), når de to segmenter plus /Contents-hullet spænder over hele filen. Når svValid ankommer med CoversWholeDocument som falsk (false), er den signerede revision intakt, men filen indeholder senere tilføjelser, og hvad disse tilføjelser ændrede, er noget, dit arbejdsflow bør beslutte, om det vil acceptere

Strøm-indlæste og krypterede dokumenter har brug for deres egne kilde-bytes

De parameterløse VerifyLoadedSignature og VerifyLoadedSignatureEx afhænger af, at komponenten husker, hvilken fil dokumentet kom fra. Hvis du indlæser dokumentet fra en strøm, er der intet filnavn at genåbne; det samme gælder efter genindlæsningsstien med adgangskode, som bruges til krypterede dokumenter, hvilket er det arbejdsflow, der er beskrevet i artiklen om AES-256 PDF-kryptering med HotPDF. I begge tilfælde returnerer de fil-baserede overloads svSourceUnavailable frem for at gætte. Løsningen er TStream-overloaden, som lader dig overdrage de originale rå bytes fra det sted, hvor du opbevarede dem — en fil, du still har, en hukommelsesbuffer eller en database-blob

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;

Rapportering af det, du ikke kan verificere

En verifikator, der kun kender "gyldig" og "ugyldig", vil fejlrapportere dokumenter, som den blot ikke forstår, så status-enumeration adskiller de tilfælde, som din brugergrænseflade bør skelne imellem. svDigestMismatch betyder, at dokumentets bytes er ændret efter signering, hvilket er det klassiske tegn på manipulation. svSignatureInvalid betyder, at bytes hashes korrekt, men RSA-kontrollen fejlede, hvilket peger på en beskadiget eller forfalsket signaturværdi. svUnsupportedAlgorithm is det ærlige svar for ECDSA-nøgler og ukendte resuméer (digests): Signaturen kan være aldeles god, men HotPDF kan simpelthen ikke kontrollere den, og at rapportere det som "ugyldig" ville miskreditere et sundt dokument. svMalformed markerer en CMS-beholder, der slet ikke kunne fortolkes. For overordnede kontroller returnerer VerifyAllLoadedSignatures kun true, når der findes mindst ét signaturfelt, og hver enkelt af dem verificeres som svValid, en praktisk enkelt sandhedsværdi til en pipeline for dokumentmodtagelse, der afviser alt andet

Signaturverifikation, PAdES-signering, AES-256-kryptering og API'en til redigering af indlæste dokumenter leveres alle i det samme indfødte VCL-bibliotek til Delphi og C++Builder uden eksterne DLL-afhængigheder; den fulde liste over funktioner og understøttede IDE-versioner findes på produktsiden for HotPDF Component