Technical Article

PDF skaitmeniniai parašai ir PAdES „Delphi“ aplinkoje su HotPDF

PDF parašas iš esmės yra baitų apskaita, o būtent joje dažniausiai ir daromos klaidos. Kriptografija veikia pagal kodą, kuris buvo audituojamas du dešimtmečius, ir ši dalis beveik niekada nesugenda. Gamybinėje aplinkoje nesėkmes lemia paprastesni dalykai: per maža rezervuota vieta tikrajam parašui, ne tos failo dalies maišos (angl. hash) skaičiavimas arba failo išsaugojimas po pasirašymo, kuris nepastebimai perrašo baitus, kuriuos parašas jau buvo užfiksavęs. Teisingai išdėstykite baitus ir žalioji varnelė atsiras pati savaime.

HotPDF palaiko pasirašymą „Delphi“ ir „C++Builder“ trijuose lygiuose, o pasirinkimas priklauso nuo vieno klausimo: kur saugomas privatusis raktas? PFX failui diske pakanka vieno funkcijos iškvietimo. Raktui, užrakintam HSM įrenginyje arba nuotolinėje pasirašymo paslaugoje, reikalinga seka „rezervuoti-apdoroti-įterpti“, nes jokia biblioteka negali pasiekti fizinio žetono (angl. token) ir ištraukti iš jo rakto. Parašui, kuris turi atitikti Europos reglamentus, papildomai reikia PAdES bazinių struktūrų. Toliau pateikti skyriai aprašo šią eigą.

Kaip /ByteRange apibrėžia pasirašytus baitus

Parašas turi būti pačiame pasirašomame faile, tačiau jis negali pasirašyti pats savęs. PDF formatas šį paradoksą išsprendžia palikdamas tuščią vietą. Prieš pasirašant, dokumento kūrimo įrankis rezervuoja fiksuoto dydžio /Contents įrašą, užpildytą nuliais, ir įrašo /ByteRange masyvą dviem sritims abiejose jo pusėse: viskam prieš tuščią vietą ir viskam po jos. Pasirašymo priemonė sukuria šių dviejų sričių maišos kodą ir įrašo gautą CMS bloką į tuščią vietą šešioliktainiu formatu. Pavojus slypi žodyje fiksuotas. Jūs įsipareigojate dėl šios vietos dydžio dar prieš sužinodami, kokio dydžio bus galutinis parašas, zodžiu, rezervacija turi būti užtikrintai didesnė nei tikėtina. Aštuoni kilobaitai lengvai talpina atskirtą (angl. detached) CMS parašą su trumpa sertifikatų grandine.

HotPDF išskiria šiuos du atvejus į du iškvietimus, o jų painiojimas yra dažna pradedančiųjų klaida. AddSignatureField prideda tuščią, matomą lauką, kurį asmuo galės pasirašyti vėliau peržiūros programoje. AddSignedSignatureField sukuria lauką ir rezervuoja /Contents vietą, kurios jums prireiks, kai parašą užbaigs kodas, o ne žmogus. Jei išoriniam pasirašymo procesui pateiksite tuščią lauką, jis neturės ko užpildyti.

Vieno iškvietimo kelias: pasirašymas naudojant PFX

Kai sertifikatas ir jo privatusis raktas yra PFX/PKCS#12 faile, kurį jūsų procesas gali nuskaityti, visas procesas susiveda į klasės funkciją:

if THotPDF.SignPDFWithPFX('invoice-unsigned.pdf', 'invoice-signed.pdf',
    'company-cert.pfx', 'pfx-password') then
  Writeln('Signed: invoice-signed.pdf')
else
  raise Exception.Create('PFX signing failed');

Kai šis veiksmas nepavyksta, problema retai būna pats PDF failas. Dažniausiai klystama dėl PFX. HotPDF nuskaito konteinerius, apsaugotus PBES2, o tai reiškia PBKDF2 raktų išvestinę naudojant AES-256-CBC. PFX, eksportuotas senesnio Windows sertifikatų vedlio arba senesnės nei 3.0 versijos OpenSSL, dažniausiai yra supakuotas naudojant pasenusį RC2 arba 3DES algoritmą ir tiesiog nebus nuskaitytas. Sprendimas yra iš naujo eksportuoti konteinerį su šiuolaikine apsauga; šiandieninis OpenSSL tai atlieka pagal numatytuosius nustatymus ir tam nereikia keisti kodo. Taigi, jei pasirašymas iškart sugenda su sertifikatu, kuris „veikia visur kitur“, prieš pradėdami įtarti savo kodą, pažiūrėkite, kaip buvo sukurtas PFX failas.

Rezervavimo-apdorojimo-įterpimo kelias HSM ir žetonams

Vieno iškvietimo kelias daro prielaidą, kad jūsų procesas gali nuskaityti raktą kaip failą. Tačiau vis dažniau tai neįmanoma. Raktas saugomas HSM įrenginyje, USB žetone arba už pasirašymo paslaugos API, ir biblioteka negali jo pasiekti tiesiogiai. HotPDF tai išsprendžia suskaidydama pasirašymą į baitų lygio žingsnius: įrašo vietos rezervavimo dokumentą, užklausia bibliotekos dėl maišos rėžių, perduoda maišos įvestį raktą saugančiam įrenginiui, o tada įterpia grąžintą CMS atgal į rezervuotą vietą.

var
  Doc: THotPDF;
  Fs: TFileStream;
  PdfBytes, HashInput, SigHex: AnsiString;
  R1Start, R1Len, R2Start, R2Len, CStart, CLen: Integer;
begin
  // 1. Write the document with a reserved /Contents hole
  Doc := THotPDF.Create(nil);
  try
    Doc.FileName := 'placeholder.pdf';
    Doc.BeginDoc;
    Doc.CurrentPage.AddSignedSignatureField('Sig1',
      Rect(50, 100, 350, 150), 8192, 'adbe.pkcs7.detached',
      'Contract approval', 'Boston, MA', 'legal@example.com');
    Doc.EndDoc;
  finally
    Doc.Free;
  end;

  // 2. Load the saved bytes; the returned offsets are 0-based
  Fs := TFileStream.Create('placeholder.pdf', fmOpenRead);
  try
    SetLength(PdfBytes, Fs.Size);
    Fs.ReadBuffer(PdfBytes[1], Fs.Size);
  finally
    Fs.Free;
  end;
  THotPDF.PreparePDFForSigning(PdfBytes, R1Start, R1Len, R2Start, R2Len,
    CStart, CLen);

  // 3. Hash both spans and sign externally (HSM, token, service)
  HashInput := Copy(PdfBytes, R1Start + 1, R1Len) +
               Copy(PdfBytes, R2Start + 1, R2Len);
  SigHex := SignWithHsm(HashInput);  // your integration: returns CMS as hex

  // 4. Splice the signature into the reserved hole
  THotPDF.InsertSignatureHex(PdfBytes, SigHex);
  Fs := TFileStream.Create('signed.pdf', fmCreate);
  try
    Fs.WriteBuffer(PdfBytes[1], Length(PdfBytes));
  finally
    Fs.Free;
  end;
end;

Dvi šios sekos detalės sukelia daugumą atsitiktinių klaidų. Pirmoji yra ta, kad PreparePDFForSigning veikia su sukurto failo baitais. Vietos rezervavimo failas turi būti visiškai įrašytas ir išsaugotas, kad poslinkiai turėtų prasmę; skaičiuojant juos sraute, kuris vis dar surenkamas, jie nesutaps su baitais, kuriuos vėliau maišysite. Antroji detalė vėlgi yra rezervacijos dydis. Prašomi 8192 baitai turi talpinti galutinį CMS, o parašas su tarpiniais sertifikatais arba parašas, kurį paslauga papildo pasirašytais atributais, gali šį dydį viršyti. InsertSignatureHex nepadidins tuščios vietos, kad atsirastų daugiau vietos. Požymis, kad susidūrėte su šia problema, yra tada, kai procesas veikia su vienu sertifikatu, bet sugenda su kitu; sprendimas yra sugeneruoti rezervavimo failą iš naujo su dydžiu, išmatuotu pagal tikrąjį parašą, sukurtą realaus pasirašytojo, o ne spėjamą.

PAdES baziniai lygiai ir laiko žymos, palaikančios parašo galiojimą

Jei pasirašote pagal Europos taisykles, taikomas standartas yra ETSI EN 319 142-1, kuris apibrėžia keturis PAdES bazinius lygius. B-B yra paprastas parašas. B-T prideda patikimą laiko žymą, įrodančią parašo sukūrimo laiką. B-LT įterpia patvirtinimo duomenis, sertifikatus ir atšaukimo informaciją į patį dokumentą, kad jį būtų galima patikrinti po daugelio metų. B-LTA prideda periodines dokumento laiko žymas, kad įrodymai pergyventų algoritmus, kuriais jie buvo sukurti. HotPDF sugeneruoja kiekvienam lygiui reikalingas dokumento struktūras:

// PAdES baseline signature field (ETSI EN 319 142-1)
Pdf.CurrentPage.AddPAdESSignatureField(
  'ApprovalSig', Rect(50, 100, 350, 150), 'B-B',
  'Contract approval', 'Boston, MA', 'legal@example.com');

// Document timestamp: larger reservation for the TSA token and chain
Pdf.CurrentPage.AddDocumentTimestampSignature('ArchiveTS', 16384);

16384 baitų rezervacija laiko žymai yra apgalvota. Laiko žymos tarnyba grąžina žetoną su savo sertifikatų grandine, todėl jai paprastai reikia daugiau vietos nei 8 KB, kurių pakanka paprastam parašui. Šios dokumentų laiko žymos taip pat yra B-LTA mechanizmo pagrindas: archyvuoto parašo pakartotinis pasirašymas laiko žyma kas kelerius metus, naudojant vis dar aktualius algoritmus, leidžia užtikrinti, kad 2026 metais pasirašytas dokumentas išliks patikrinamas ir 2040 metais.

Keletas žodžių apie priežasties, vietos ir kontakto eilutes, kurias priima abu laukų iškvietimai: tai yra tik patogumo dėlei skirti metaduomenys ir nieko daugiau. HotPDF saugo juos kaip paprastus žodyno įrašus ir atvaizduoja juos matomame parašo vaizde, tačiau joks tikrintuvas jų netikrina. Pildykite juos nuosekliai pagal savo darbo eigos duomenis, nes auditoriai juos skaito, tačiau niekada nepainiokite jų su įrodymais. Tikrasis kriptografinis tvirtinimas visiškai slypi CMS ir jo sertifikatų grandinėje, o tikrinimo programa visiškai nepaiso matomo teksto.

Po pasirašymo failas gali tik didėti

Kai tik atsiranda parašas, jo rėžiuose esantys baitai užšaldomi. Vienintelis teisėtas būdas vėliau pakeisti failą yra ISO 32000-1 §7.5.6 apibrėžtas laipsniškas atnaujinimas, kuris prideda naujus ir pakeistus objektus po pradinių baitų ir susieja naują kryžminių nuorodų sekciją su jais. Tokiu būdu parašas išlieka galiojantis savo versijai, o peržiūros programa praneša teisingą būseną: pasirašyta versija yra nepaliesta, o dokumentas vėliau buvo papildytas. Jei vietoj to perrašysite visą failą iš naujo, pakeisite pasirašytus rėžius, o tai sugadins parašą, net jei niekas vizualiai nepasikeitė. Šis versijų kūrimo mechanizmas taip pat leidžia tam pačiam dokumentui turėti kelis parašus: kiekvienas naujas parašas įrašomas į atskirą laipsnišką atnaujinimą, o jo rėžiai apima viską prieš jį, įskaitant ankstesnius parašus. Tik priedų rašymo mechanika ir atvejai, kada saugu juos suglaudinti, yra aprašyti straipsnyje apie objektų srautus ir laipsniškus atnaujinimus.

Kuriant sprendimą verta atsižvelgti į du apribojimus. HotPDF PDF/A išvesties režimas visiškai atmeta parašo laukus, zodžiu, archyvavimo atitiktis ir įterptasis parašas turi būti pateikiami kaip atskiri failai. Be to, pasirašymas nieko nesako apie slaptumą: jis įrodo, kas sukūrė dokumentą ir kad jis nevo pakeistas, tačiau bet kas vis tiek gali jį perskaityti. Turinio slėpimas yra atskira užduotis, kurią atlieka AES-256 šifravimas ir leidimų politika.

Kad ir ką sukurtumėte, išbandykite tai su kuo nors kitu, o ne su kodu, kuris įrašė failą. Atidarykite rezultatą „Acrobat“ parašų skydelyje ir įsitikinkite trimis dalykais: parašas yra galiojantis, tapatybė susieta su tikėtina šakniniu sertifikatu ir skydelis praneša, kad po pasirašymo nebuvo atlikta jokių pakeitimų. Tada pakeiskite vieną baitą pasirašytame testinės kopijos rėžyje ir įsitikinkite, kad skydelis dabar praneša apie pakeistą dokumentą. Pasirašymo procesas, kurio veikimo niekada nestebėjote atmetant pakeistą failą, nėra iš tikrųjų išbandytas.

Visi trys pasirašymo lygiai pateikiami kartu su HotPDF komponentu, skirtu „Delphi“ ir „C++Builder“; produkto puslapyje pateikiama nuoroda į pilną parašų API aprašymą.