Techninis straipsnis

PDF skaitmeninių parašų patvirtinimas Delphi aplinkoje su HotPDF

„HotPDF“ patvirtina skaitmeninius parašus įkeltuose PDF dokumentuose trimis THotPDF metodais: GetLoadedSignatureInfo, VerifyLoadedSignature ir VerifyLoadedSignatureEx, kurie buvo pristatyti versijoje v2.259.0. Komponentas iš naujo apskaičiuoja pradinio failo /ByteRange segmentų maišos reikšmes, patikrina CMS messageDigest atributą ir atlieka RSA PKCS#1 v1.5 patikrą su įterptu pasirašiusiojo sertifikatu, grąžindamas svValid, kai dokumento baitai yra nepaliesti

Šis scenarijus yra kasdieniškas, tačiau statymai dideli. Kita šalis grąžina pasirašytą sutartį, jūsų darbo srautui reikia ją archyvuoti, ir kas nors užduoda vienintelį svarbų klausimą: ar tai yra tas pats dokumentas, kurį išsiuntėme, baitas po baito, pasirašytas sertifikatu, kurį jis deklaruoja? Atsakymas į tai kode yra parašo patvirtinimo pusė; pasirašymo pusė – PAdES parašų kūrimas ir įterpimas – aptariama susijusiame straipsnyje apie PAdES skaitmeninių parašų kūrimą su HotPDF. Šis straipsnis yra apie kitą kryptį: gaunamas jau pasirašytas PDF failas ir jūs norite programinio nuosprendžio, o ne „Acrobat“ žalios varnelės ekrano kopijos

Kaip pasirašytas PDF įrodo, kad jis nebuvo suklastotas?

PDF parašas saugo konkrečius failo baitų diapazonus, o ne abstrakčią „dokumento“ sampratą. ISO 32000-1 §12.8 apibrėžia šį mechanizmą: parašo formos laukas turi žodyną, kurio įrašas /Contents saugo CMS SignedData konteinerį (RFC 5652), o jo masyvas /ByteRange nurodo tikslias failo sritis, kurias apima parašas pagal §12.8.1. Šis masyvas yra poslinkių ir ilgių porų sąrašas, praktiškai susidedantis iš dviejų segmentų: visko prieš šešioliktainę eilutę /Contents ir visko po jos. Parašo reikšmė negali apimti pati savęs, todėl failo maišos kodas skaičiuojamas aplink šią skylę

Šis dizainas turi pasekmę, kuri formuoja visą API: patvirtinimas turi apskaičiuoti pradinių serijozuotų baitų maišos reikšmę tiksliai taip, kaip jie guli diske. Išanalizuotas objektų modelis čia yra nenaudingas, nes net ir nepakeisto dokumento pakartotinis serijozavimas sukuria kitokius baitus. Todėl „HotPDF“ atlieka patikrą su šaltinio failu, iš kurio dokumentas buvo įkeltas, arba su jūsų pateiktu neapdorotų baitų srautu TStream, bet niekada su jo reprezentacija atmintyje

Parašo metaduomenų skaitymas prieš atliekant patikrą

GetLoadedSignatureInfo išanalizuoja parašo žodyną ir jo CMS konteinerį neliesdama nė vieno dokumento baito, todėl tai yra tinkamas pirmasis iškvietimas, kai jums tereikia parodyti, kas ir kada pasirašė. Parašų laukai yra indeksuojami nuo 0 formos laukų tvarka, o GetLoadedSignatureFieldCount praneša, kiek jų yra. Grąžintas THPDFSignatureInfo įrašas turi lauko pavadinimą, /SubFilter, pasirašiusiojo sertifikato bendrąjį pavadinimą, subjekto bei išdavėjo distinguished vardus (DN), serijos numerį, galiojimo datas, pasirašymo laiką (iš pasirašyto atributo, jei yra, kitu atveju – iš žodyno įrašo /M), maišos algoritmo pavadinimą ir eilutes /Reason, /Location bei /ContactInfo. Jo narys Status išlieka svNotVerified – sąžininga žyma, reiškianti „išanalizuota, nepatikrinta“

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;

Kriptografinės patikros vykdymas

VerifyLoadedSignatureEx atlieka pilną failo įkelto dokumento patikrą ir grąžina užpildytą informacijos įrašą vienu iškvietimu: iš naujo atidaro šaltinio failą, apskaičiuoja /ByteRange segmentų maišos reikšmę su „SignerInfo“ maišos algoritmu, palygina rezultatą su pasirašytu atributu messageDigest (RFC 5652 §5.4), o po to atlieka RSA parašo patikrą su pasirašytų atributų DER SET pakartotiniu kodavimu. Kai parašas neturi pasirašytų atributų, RSA patikra vykdoma tiesiogiai su dokumento maišos kodu. Palaikomi parašai yra RSA PKCS#1 v1.5 su SHA-1, SHA-256, SHA-384 arba SHA-512 maišomis, kas apima subfiltrus adbe.pkcs7.detached ir ETSI.CAdES.detached, sukurtus populiarių pasirašymo įrankių

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;

Verta žinoti dvi įgyvendinimo detales, nes jos paaiškina nesėkmes, kurios iš išorės atrodo paslaptingos. Pirma, pasirašytų atributų patikra yra jautri kodavimui: failo viduje atributai pažymėti kaip [0] IMPLICIT, tačiau parašas buvo apskaičiuotas pagal jų DER SET OF formą, todėl tikrintuvas prieš maišos skaičiavimą iš naujo uždeda žymą tiksliai taip, kaip reikalauja RFC 5652 §5.4. Pačių sukurtas tikrintuvas, kuris skaičiuoja maišos kodą iš baitų, esančių faile, atmes kiekvieną teisingai pasirašytą dokumentą. Antra, /Contents paprastai užpildomas nuliais iki rezervuoto baitų biudžeto, todėl tikrintuvas prieš analizavimą sutrumpina DER duomenis iki tikrojo jo išorinės SEQUENCE ilgio; atrodantys kaip šiukšlės papildomi nuliniai baitai pabaigoje yra normalus reiškinys, o ne sugadinimas. Ta pati ASN.1 analizavimo pavojų šeima, kalbant apie sertifikatų importą, yra straipsnio apie PKCS#12 ir ASN.1 saugumo stiprinimą HotPDF sistemoje tema

Ką iš tikrųjų garantuoja galiojantis parašas?

svValid reiškia būtent tai: baitų diapazonas, nurodytas /ByteRange, sutampa su maišos reikšme, kurią pasirašė pasirašantysis, o parašas pasitvirtina su viešuoju raktu iš sertifikato, esančio CMS konteineryje. Tai yra baitų vientisumas plius rakto susiejimas, ir nieko daugiau. Sertifikatų grandinės ir pasitikėjimo tikrinimas yra aiškiai už „HotPDF“ tikrintuvo ribų: jis neina grandine iki šaknies (root), netikrina atšaukimo ir nesikonsultuoja su jokiu pasitikėjimo store. Paties užpuoliko pasirašytas sertifikatas, kuris iš naujo pasirašė pakeistą dokumentą, bus patvirtintas kaip svValid, nes matematiškai viskas yra teisinga. Tai, ar pasirašęs asmuo yra tas, kuo dedasi, ir ar juo reikėtų pasitikėti, yra politinis sprendimas, priklausantis atskiram sluoksniui – jūsų organizacijos sertifikatų baltajam sąrašui, „Windows“ sertifikatų saugyklai ar patvirtinimo tarnybai

Vėliava CoversWholeDocument saugo nuo subtilesnės spragos. Parašas visada apima tik savo /ByteRange, o PDF prieaugio atnaujinimo mechanizmas leidžia pridėti turinį po parašo jo nepažeidžiant, kas yra sukurta specialiai kelių parašų darbo eigoms. Ši vėliava apskaičiuojama patikros metu ir yra teigiama tik tada, kai abu segmentai plius /Contents tarpas apima visą failą. Kai grąžinama svValid su neigiama CoversWholeDocument vėliava, pasirašyta versija yra nepaliesta, zodžiu, faile yra vėlesnių papildymų. Jūsų darbo eiga turėtų nuspręsti, ar toleruoti šiuos papildymus

Srautu įkeltiems ir šifruotiems dokumentams reikalingi jų pačių šaltinio baitai

Metodai be parametrų VerifyLoadedSignature ir VerifyLoadedSignatureEx priklauso nuo to, ar komponentas prisimena, iš kokio failo atsirado dokumentas. Įkelkite dokumentą iš srauto ir nebus failo pavadinimo, kurį būtų galima atidaryti iš naujo; tas pats galioja ir po slaptažodžio perkrovimo kelio, naudojamo šifruotiems dokumentams, t. y. darbo eigos, aprašytos straipsnyje apie AES-256 PDF šifravimą su HotPDF. Abiem atvejais failo pagrindu veikiančios perkrovos grąžina svSourceUnavailable, užuot spėliojusios. Sprendimas yra srauto TStream perkrova, leidžianti pateikti originalius neapdorotus baitus iš ten, kur juos laikote – failo, kurį vis dar turite, atminties buferio ar duomenų bazės objekto

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;

Pranešimas apie tai, ko negalite patikrinti

Tikrintuvas, kuris žino tik reikšmes „galiojantis“ ir „negaliojantis“, klaidingai praneš apie dokumentus, kurių jis tiesiog nesupranta, so būsenų sąrašas atskiria atvejus, kuriuos jūsų naudotojo sąsaja turėtų atskirti. svDigestMismatch reiškia, kad dokumento baitai pasikeitė po pasirašymo – tai klasikinė klastojimo žymė. svSignatureInvalid reiškia, kad baitų maiša teisinga, tačiau RSA patikra nepavyko, kas rodo sugadintą arba suklastotą parašo reikšmę. svUnsupportedAlgorithm yra sąžiningas atsakymas ECDSA raktams ir neatpažįstamoms maišoms: parašas gali būti visiškai geras, tiesiog „HotPDF“ negali jo patikrinti, o pranešimas, kad jis „negalioja“, pakenktų geram dokumentui. svMalformed pažymi CMS konteinerį, kurio išvis nepavyko išanalizuoti. Vartų tipo patikroms VerifyAllLoadedSignatures grąžina true tik tada, kai egzistuoja bent vienas parašo laukas ir kiekvienas iš jie patvirtinamas kaip svValid – tai patogi vienintelė loginė reikšmė archyvo apdorojimo srautui, kuris atmeta bet ką mažesnio

Parašo patvirtinimas, PAdES pasirašymas, AES-256 šifravimas ir įkelto dokumento redagavimo API pateikiami tame pačiame vietiniame VCL bibliotekos pakete, skirtame Delphi ir C++Builder, be išorinių DLL priklausomybių; išsamų funkcijų sąrašą ir palaikomas IDE versijas rasite „HotPDF Component“ produkto puslapyje