PDFlibPas ponovno pokušava pogrešnu lozinku za šifrirani PDF tako da odbacuje TPDFDocument koji je upravo zakazao i stvara potpuno novi za sljedeći pokušaj, uz OnPassword povratni poziv (TPDFlibPasswordEvent) koji se izvršava najviše šesnaest puta prije odustajanja. To je namjerno odstupanje od prvog pristupa za kojim većina Delphi programera posegne: zadržati objekt dokumenta koji već postoji u memoriji, proslijediti mu ispravljenu lozinku i ponovno učitati dokument na mjestu umjesto da se krene ispočetka. PDFlibPasova petlja za ponovni pokušaj, dodana u v3.245.0, zauzima suprotan stav zbog onoga što neuspjeli pokušaj lozinke ostavlja iza sebe. Scenarij je dovoljno uobičajen da ga većina Delphi aplikacija koje intenzivno rade s dokumentima prije ili poslije doživi: zaslon za prihvat učita PDF, šifrirani trailer zatraži dijalog za lozinku, operater pogrešno upiše niz, a dijalog se ponovno pojavi za drugi pokušaj. Ništa u tom korisničkom iskustvu nije neobično, pa kod mora prihvatiti više kandidata lozinke za istu datoteku i to učiniti sigurno, bez propuštanja stanja odbijenog pokušaja u onaj koji slijedi
Zašto jednostavno ne možete ponovno pokušati s istim objektom dokumenta?
Ponovna uporaba objekta TPDFDocument kroz više pokušaja lozinke ne funkcionira, jer neuspjeli pokušaj već interno rastavi taj objekt umjesto da ga ostavi u nekom zaustavljenom stanju iz kojeg se može nastaviti. Otvaranje šifriranog PDF-a znači raščlaniti unakrsnu referentnu tablicu, izgraditi čitač nad temeljnim izvorom i konstruirati rukovatelj kriptografijom iz dostavljene lozinke, sve prije nego što PDFlibPas uopće može provjeriti je li lozinka točna. Kada se pokaže da lozinka nije točna, interna rutina učitavanja dokumenta tijekom prekida očisti čitač, unakrsnu referentnu tablicu i rukovatelj kriptografijom, upravo kako treba, što znači da ondje nema napola izgrađenog parsera koji bi čekao ispravljenu lozinku pri drugom pozivu. Ako taj isti objekt ipak provedete kroz novi pokušaj učitavanja, dobit ćete upravo onu vrstu greške koju je teško otkloniti: greška se pojavi iz internog stanja izgrađenog za drugi, već neuspjeli pokušaj raščlambe, bez ičega što bi je očito povezalo s lozinkom unesenom tri poziva ranije. PDFlibPas izbjegava cijelu tu skupinu problema tako što nikada ne pokušava oporaviti objekt dokumenta nakon neuspjelog otvaranja; svaki pokušaj dobiva dokument koji još nije vidio pogrešnu lozinku, uključujući čitač i unakrsnu referentnu tablicu
Kako OnPassword povratnim pozivom traži sljedeću lozinku?
TPDFlibPasswordEvent je vrsta povratnog poziva koju PDFlibPas poziva kroz TPDFlib.LoadFromFile, LoadFromStream i LoadFromString kad se pokaže da je upravo isprobana lozinka pogrešna, a rukovatelju predaje tri stvari: koji će se pokušaj pokrenuti, parametar Password koji treba prebrisati sljedećim kandidatom i zastavicu Retry koja je prema zadanim postavkama false
TPDFlibPasswordEvent = procedure(Sender: TObject; AttemptNumber: Integer;
var Password: WideString; var Retry: Boolean) of object;
property OnPassword: TPDFlibPasswordEvent read FOnPassword write FOnPassword;
Lozinka predana izvornom pozivu LoadFromFile računa se kao prvi pokušaj, pa prvi put kada se OnPassword uopće aktivira, AttemptNumber ima vrijednost 2. Ako Retry ostavite nepostavljenim, učitavanje će se uredno neuspješno završiti s LastErrorCode 404; postavite ga na true i PDFlibPas će ponovno pokušati s onim što je rukovatelj upravo upisao u Password
Unutar petlje ponovnog pokušaja: novi TPDFDocument za svaki pokušaj
Interno PDFlibPas na pitanje životnog ciklusa objekta odgovara na isti način za LoadFromFile, LoadFromStream i LoadFromString: svaki pokušaj, uključujući prvi, konstruira novi TPDFDocument, provodi ga kroz cijeli slijed otvaranja s lozinkom koju taj pokušaj koristi i zadržava objekt samo ako se lozinka potvrdi. TPDFDocument odbijenog pokušaja odmah se oslobađa, a s njim nestaju i čitač, unakrsna referentna tablica te rukovatelj kriptografijom, dok sljedeći pokušaj počinje s objektom koji nema nikakvu povijest
// Simplified excerpt from inside LoadFromFile: every attempt gets a
// document that has never seen a previously rejected password. FileName,
// AttemptNumber and AttemptPassword come from the enclosing method.
Var
Doc: TPDFDocument;
LoadResult: TPLLoadResult;
Success: Boolean;
Begin
Success := False;
Repeat
Doc := TPDFDocument.Create;
Doc.DecodeMode := FDefaultDecodeMode;
Try
LoadResult := Doc.LoadFromFile(FileName, AttemptPassword);
Success := LoadResult = lrOkay;
if Success then
begin
FDocs.Add(Doc); // hand the verified document to the
Doc := nil; // caller's collection; skip the Free below
end;
Finally
Doc.Free; // a rejected attempt's reader, xref table
End; // and crypt handler are torn down right here
if Success or (LoadResult <> lrWrongPassword) then
Break; // success, or a non-password failure: stop
Inc(AttemptNumber);
Until not RequestPasswordRetry(AttemptNumber, AttemptPassword);
End;
Redak Doc := nil neposredno prije bloka Finally cijeli je ugovor životnog ciklusa objekta izražen jednom naredbom. Dokument koji ne uspije nestaje zajedno sa svojim napola izgrađenim stanjem parsera, namjerno, a dokument koji uspije jedini se dodaje u FDocs, zbirku koju TPDFlib održava za svaki dokument koji je pozivatelj otvorio. Izvan petlje ponovnog pokušaja ništa od odbijenog pokušaja nije vidljivo: ni napola inicijalizirani čitač, ni zastarjeli broj stranica, ni rukovatelj kriptografijom izgrađen iz pogrešnog ključa
Koliko će puta PDFlibPas ponovno pokušati s pogrešnom lozinkom?
PDFlibPas dopušta ukupno šesnaest pokušaja za jedan poziv LoadFromFile, LoadFromStream ili LoadFromString, pri čemu se lozinka predana samom pozivu računa kao prvi pokušaj. OnPassword se ikada aktivira samo za pokušaje od drugog do šesnaestog, pa je broj poziva povratnog poziva ograničen na petnaest; zatražite sedamnaesti pokušaj i PDFlibPas će ga odbiti bez ikakvog poziva rukovatelju. Ako u bilo kojem trenutku ostavite Retry na zadanoj vrijednosti false ili iscrpite svih šesnaest pokušaja bez točne lozinke, LoadFromFile vraća 0, a LastErrorCode postavlja na 404, što je PDFlibPasov kod za odbijenu lozinku. Ograničenje postoji iz razloga koji nadilaze urednost: neograničena petlja ponovnih pokušaja jednostavan je način da se jedna pogrešno upisana lozinka pretvori u slučajni napad uskraćivanja usluge nad niti koja obavlja učitavanje, osobito kada je rukovatelj povezan s automatiziranim izvorom, poput popisa ranije viđenih lozinki, umjesto s čovjekom koji klikće kroz dijalog. PDFlibPas također uvažava poziv Abort na instanci TPDFlib iz samog rukovatelja, jer Sender stiže kao taj isti objekt, što je korisno iza gumba Odustani u dijalogu za lozinku, te zaustavlja petlju ponovnog pokušaja pri sljedećoj provjeri bez obzira na to kako je Retry postavljen. Učitavanje koje ne uspije zbog razloga koji nije pogrešna lozinka, primjerice zbog oštećene unakrsne referentne tablice, uopće ne ulazi u petlju ponovnog pokušaja: PDFlibPas prijavljuje LastErrorCode 401 i zaustavlja se nakon prvog pokušaja, jer nijedan broj pogađanja lozinke ne može popraviti strukturno oštećenu datoteku
Radi li petlja ponovnog pokušaja jednako za datoteke, tokove i nizove?
Povratni poziv OnPassword i ograničenje od šesnaest pokušaja ponašaju se jednako za LoadFromFile, LoadFromStream i LoadFromString, iako te tri ulazne točke različito zadržavaju svoj izvor između pokušaja. Putanju datoteke je lako ponovno otvoriti jer svaki pokušaj jednostavno ponovno otvara imenovanu datoteku, a izvor u obliku niza već je u memoriji kao vlastita kopija pozivatelja, pa nijedan od njih ne traži pomoć pozivatelja između pokušaja. Tok koji predaje pozivatelj jedini je slučaj kod kojega vrijedi zastati: LoadFromStream vraća taj tok na poziciju nula i interno ga kopira prije prvog pokušaja raščlambe, pa se svaki sljedeći pokušaj i svježe konstruirani TPDFDocument iza njega ponovno pokreću iz te interne kopije, a ne s mjesta na kojem je neuspjela raščlamba ostavila poziciju toka. Predate li PDFlibPas objekt TFileStream ili TMemoryStream za dokument zaštićen lozinkom, nema potrebe vraćati ga na početak između pokušaja; PDFlibPas već uzima u obzir pomak pozicije koji je prvi, neuspjeli pokušaj mogao napraviti
Uklapanje ponovnog pokušaja lozinke u zaslon za prihvat dokumenata
Tijek prihvata dokumenta prirodno je mjesto za ovaj povratni poziv jer je upravo za takav problem OnPassword napravljen: datoteka dolazi izvan aplikacije, njezina lozinka unaprijed nije pouzdano poznata, a osoba koja unosi kandidate treba više od jednog pokušaja bez pisanja vlastite petlje ponovnih pokušaja oko LoadFromFile
procedure TIntakeForm.SupplyPassword(Sender: TObject; AttemptNumber: Integer;
var Password: WideString; var Retry: Boolean);
var
Typed: string;
begin
// AttemptNumber counts from 2: the password already tried was attempt 1.
Typed := '';
Retry := InputQuery('Password required',
Format('Attempt %d of 16 - enter the document password', [AttemptNumber]), Typed);
if Retry then
Password := Typed;
// Retry is False when the operator cancels, which leaves
// LastErrorCode at 404 for the caller to report.
end;
procedure TIntakeForm.LoadInboundDocument;
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.OnPassword := SupplyPassword;
if Lib.LoadFromFile('inbound-invoice.pdf', '') = 1 then
RegisterIntakeDocument(Lib) // only a verified document reaches here
else
LogRejectedIntake('inbound-invoice.pdf', Lib.LastErrorCode);
finally
Lib.Free;
end;
end;
RegisterIntakeDocument prima Lib samo nakon što LoadFromFile vrati 1, što znači da se neka lozinka u toj razmjeni doista potvrdila prema rukovatelju kriptografijom datoteke; odbijeni pokušaj nikada ne doseže taj redak, kao ni napola otvoreni dokument. Nakon što se ovakav dokument potvrdi kao otvoren, vrijedi ponovno provjeriti njegove postavke zaštite, a ne pretpostaviti da lozinka koja je uspjela predstavlja cijelu sigurnosnu priču: revizija onoga što rječnik /Encrypt dokumenta stvarno navodi obuhvaća čitanje algoritma, revizije i bitova dozvola koje PDFlibPas izlaže nakon učitavanja ovakve datoteke
Ponovni pokušaj lozinke također je uski primjer šire discipline koju PDFlibPas primjenjuje kroz cijeli sloj raščlambe: datoteka koja se još nije dokazala ne dobiva povjerenje, bilo da je pitanje koja je lozinka otključava ili laže li polje duljine o veličini međuspremnika koji mu je potreban. Ojačavanje Pascal PDF parsera protiv zlonamjernih datoteka obrađuje drugu polovicu te discipline, dekodere koji svaki program fonta i slikovni tok u ulaznom PDF-u tretiraju kao neprijateljski ulaz, a ne kao ispravan dokument koji je samo zaboravio lozinku
OnPassword i petlja ponovnog pokušaja iza njega dio su standardne PDFlibPas PDF biblioteke za Delphi i C++Builder, dostupni svugdje gdje su već dostupni LoadFromFile, LoadFromStream ili LoadFromString, bez zasebnog modula ili razine licence za dokument kojemu je potreban samo još jedan pokušaj lozinke