Odlučivanje da je PDF potpis kvalifikovan pod eIDAS znači odgovaranje na pitanje koje nema nikakve veze sa kriptografijom: da li je sertifikat izdat od servisa poverenja koji je država članica navela kao kvalifikovan, u trenutku kada je potpis učinjen. Odgovor živi u popisu poverenja, XML dokumentu objavljenom po teritoriji, i cela vrednost tog dokumenta zavisi od njegove autentičnosti. Pa PDFium komponenta odbija da zagleda unutra dok neko ne jamči za nju. TPdfEuropeanTrustedList.ParseAuthenticated predaje kompletne sirove bajtove IPdfTrustedListAuthenticator-u koji dostavlja pozivalac pre nego što raščlani ijedan servis, i stvara snimak samo ako taj autentikator eksplicitno prolazi
To uređenje je dizajn. Sve ostalo u ovoj funkciji proizlazi iz njega, uključujući delove koji deluju nezgodno
Raščlanjeno nije isto što i povereno
Popis poverenja koji se čisto raščlani kaže vam da je XML dobro oblikovan. Ne kaže vam ništa o tome ko ga je napisao. Pošto je popis ono na čemu počiva cela vaša odluka o kvalifikovanom statusu, prihvatanje jednog zato što se raščlanio učinilo bi odluku besmislenom: napadač koji može zameniti popis može proglasiti sopstveni sertifikatski autoritet kvalifikovanim
Isto rasuđivanje primenjuje se na keširanje, i to je zamka vredna imenovanja. Keš snimaka čuva prvobitni XML zajedno sa SHA-256 sažetkom, i lako bi bilo tretirati poklapajući sažetak pri učitavanju kao dokaz da je popis pravi. Nije. Sažetak izračunat od istog procesa koji je čuvao datoteku, bez uključenog ključa, verifikuje samo da se bajtovi nisu promenili otkako ste ih napisali; ako je popis bio prevaran kada je keširan, sažetak potvrđuje da je isti prevarni popis. Pa učitavanje keširanog snimka ide kroz isti autentikator kao raščlanjivanje svežeg. Celovitost i autentičnost različita su svojstva i samo jedno od njih traži ključ
uses
FPdfTrustedList;
type
TListAuthenticator = class(TInterfacedObject, IPdfTrustedListAuthenticator)
public
function Authenticate(const XmlData: TBytes;
out AuthenticationDetails: string): Boolean;
end;
function TListAuthenticator.Authenticate(const XmlData: TBytes;
out AuthenticationDetails: string): Boolean;
begin
// Vaša politika živi ovde: verifikujte XMLDSIG omotani potpis
// protiv sertifikata potpisivanja popisa koji ste pribili van opsega,
// i opišite šta ste proverili za revizionu tragu
Result := VerifyEnvelopedXmlSignature(XmlData, FPinnedListSigner);
if Result then
AuthenticationDetails := 'XMLDSIG verified against pinned LOTL signer';
end;
var
List: TPdfEuropeanTrustedList;
Cache: TFileStream;
begin
List := TPdfEuropeanTrustedList.ParseAuthenticated(RawXml,
TListAuthenticator.Create, TPdfTrustedListOptions.Default);
// Snimak postoji samo zato što je autentikator rekao da
Cache := TFileStream.Create('tl-de.snapshot', fmCreate);
try
List.SaveCache(Cache);
finally
Cache.Free;
end;
end;
Validator ne poseduje mrežnu politiku
PAdES validator nema posao odlučivati kako dopreti do popisa popisa poverenja, da li ići kroz proksi, koliko često ponoviti, ili šta činiti kada je teritorija nedostižna. To su odluke aplikacije i raspoređivanja, i u regulisanim okruženjima često su revizionirane. Pa ažuriranja stižu kroz IPdfTrustedListSource, kojem se predaje URI i granica bajtova i koji vraća bajtove
Ono što komponenta sprovodi jesu invarijante koje čine ažuriranje ažuriranjem umesto zamenom. Update traži da je teritorija nepromenjena, da se serijski broj strogo uvećava, i da se vreme izdavanja ne pomera nazad. Te tri provere pobeđuju najočiglednije napade obaranja: ponavljanje starijeg popisa koji još navodi uslugu od tada povučenu, ili ubacivanje popisa druge teritorije čije usluge nikad niste nameravali verovati
Granice parsera, i nikakav DTD
TPdfTrustedListOptions ograničava XML veličinu, broj tokena, dubinu ugrađivanja, broj servisa, broj sertifikata i veličinu pojedinačnog sertifikata, sa klasnom funkcijom Default koja daje upotrebljive vrednosti. Popisi poverenja objavljeni su dokumenti predvidive veličine, pa su granice jeftine za postaviti i nema legitimnog popisa kojem treba prekoračiti ih
Odvojeno i bezuslovno, parser odbacuje DTD i deklaracije entiteta. To zatvara i uskraćenje usluge širenjem entiteta i put otkrivanja spoljašnjim entitetom u jedno odbijanje, i ne košta ništa jer popisi poverenja ne koriste entitete. Svaki XML parser dostižan iz nepoverljivog ulaza trebao bi biti tako konfigurisan; razlika ovde je što odbijanje nije konfigurabilno, pa ga ne može isključiti dobronamerna izmena opcije
Kvalifikovani status beleži se pored poverenja lanca, ne stapa u njega
Strana evaluacije namerno je odvojena. TPadesTrustValidationOptions.QualifiedTrustEvaluator prima IPdfQualifiedTrustEvaluator, koji snimak popisa poverenja implementira. Tokom validacije evaluator prima listni sertifikat, lanac i vreme validacije, poklapa sertifikate servisa tačnim DER poređenjem protiv potpisnika i lanca, kombinuje status servisa, identifikator tipa servisa i URI-je kvalifikatora u tom trenutku, i vraća zapis evaluacije
Rezultat sleće na dva mesta na svakom potpisu: QualifiedTrustStatus kao grubi status, i QualifiedTrust kao puna evaluacija sa teritorijom, imenom pružaoca, imenom servisa, identifikatorom tipa, statusom i vremenom početka statusa. Ono što ne radi jeste promena CertificateTrustStatus. Sistemsko poverenje lanca i kvalifikovani status odgovaraju različitim pitanjima, i izveštaj koji ih skupi ne može razlikovati "poverljivo ali ne kvalifikovano" od "kvalifikovano ali lanac se ne validira", od kojih su oba stvarna i oba traže različito rukovanje
var
Options: TPadesTrustValidationOptions;
Report: TPadesValidationResult;
I: Integer;
begin
Options := TPadesTrustValidationOptions.Default;
Options.CheckRevocation := True;
Options.QualifiedTrustEvaluator := List; // autentikovani snimak
Options.QualifiedValidationTime := SigningTime; // ne Now
Report := Pdf.ValidatePadesTrust(Options);
for I := 0 to High(Report.Signatures) do
if Report.Signatures[I].QualifiedTrustStatus = pcsValid then
Writeln(Format('signature %d qualified by %s / %s (%s)',
[I, Report.Signatures[I].QualifiedTrust.Territory,
Report.Signatures[I].QualifiedTrust.ProviderName,
Report.Signatures[I].QualifiedTrust.ServiceName]))
else if Report.Signatures[I].QualifiedTrustStatus = pcsIndeterminate then
// Nema poklapajućeg servisa, ili snimak ne može odgovoriti za ovo vreme
Writeln(Format('signature %d: qualified status undetermined', [I]));
end;
Zašto vreme validacije nije sada
Zato je kvalifikacija svojstvo trenutka. Servisu poverenja može biti dodeljen kvalifikovani status, kasnije povučen, i kasnije još vraćen, i svaki od tih prelaza nosi vreme početka u popisu. Potpis učinjen dok je servis bio kvalifikovan ostaje kvalifikovan posle; potpis učinjen pre dodele ne postaje kvalifikovan retroaktivno. Evaluacija protiv trenutnog vremena dakle daje pogrešan odgovor u oba smera
Popis nosi ono što je potrebno za ovo: svaki zapis servisa ima vreme početka statusa i zastavicu koja razlikuje istorijske unose od tekućih, i evaluator ih kombinuje protiv vremena koje vi dostavite. U praksi to vreme dolazi iz poverljive vremenske oznake na potpisu umesto iz vremena potpisivanja tvrđenog u CMS-u, što je razlog zašto materijal dugoročne validacije važi čak i za pitanje koje deluje kao upit politike; strana vremenske oznake i DSS-a pokrivena je u članku o dugoročnim potpisima
Šta još morate izgraditi
Tri stvari, i nijedna ne pripada PDF biblioteci. Autentikator, značeći stvarnu XMLDSIG verifikaciju protiv sertifikata potpisivanja popisa kojeg ste nabavili kanalom kome verujete. Politika preuzimanja, značeći kako i koliko često osvežavate, i šta vaša aplikacija radi kada osvežavanje otkazuje. I teritorijalni obuhvat, značeći koje popise uopšte nosite, što je poslovna odluka o državama članicama u kojima vaši partneri potpisuju
Ono što dobijate od komponente jeste deo koji je lako pogrešiti suptilno: uređenje potvrdi-pre-raščlanjivanja, ograničeno i entitetima-slobodno XML parsiranje, monotonije invarijante ažuriranja, poklapanje servisa tačnim DER-om, istorijska evaluacija statusa, i rezultat koji ostaje odvojen od običnog poverenja lanca. Ako je vaš neposredan problem osnovniji, da validator odbija potpis za koji verujete da je u redu, uobičajeni uzroci katalogizovani su u zašto validatori odbijaju PAdES potpise, a površina pregleda potpisa opisana je u pregledu potpisa i PAdES nivoa. Mogućnosti komponente navedene su na stranici proizvoda PDFium Delphi component