Odlučiti da je PDF potpis kvalificiran pod eIDAS-om znači odgovoriti na pitanje koje nema nikakve veze s kriptografijom: je li certifikat izdao servis povjerenja koji je država članica unijela u popis kao kvalificiranoga u trenutku kad je potpis napravljen. Odgovor živi u popisu povjerenja, XML dokumentu objavljenom po teritoriju, i cijela vrijednost tog dokumenta ovisi o njegovoj istinitosti. Zato PDFium komponenta odbija pogledati u nju dok je netko nije jamstvom pokrio. TPdfEuropeanTrustedList.ParseAuthenticated predaje potpune sirove bajtove pozivateljem opskrbljenom IPdfTrustedListAuthenticator prije nego raščlani ijedan servis, i stvara snimku samo ako taj autentifikator izričito prolazi
Taj je redoslijed dizajn. Sve ostalo u ovoj značajci slijedi iz njega, uključujući dijelove koji izgledaju nezgodno
Raščlanjeno nije povjerljivo
Popis povjerenja koji se čisto raščlani kaže vam da je XML dobro oblikovan. Ne govori vam ništa o tome tko ga je napisao. Budući da je popis ono na čemu počiva cijela vaša odluka o kvalificiranom stanju, prihvatiti jednoga jer se raščlanio učinilo bi odluku besmislenom: napadač koji može zamijeniti popis može proglasiti vlastitu ovlast za izdavanje certifikata kvalificiranom
Isto razmišljanje primjenjuje se na predmemoriju, i to je zamka vrijedna imenovanja. Predmemorija snimki sprema izvorni XML zajedno sa sažetkom SHA-256, i lako bi bilo tretirati podudaran sažetak pri učitavanju kao dokaz da je popis pravi. Nije. Sažetak izračunat od istog procesa koji je spremio datoteku, bez ikakvog ključa, provjerava samo da se bajtovi nisu promijenili otkako ste ih napisali; ako je popis bio prevarantski kad je predmemoriran, sažetak potvrđuje da je to isti prevarantski popis. Zato učitavanje predmemorirane snimke prolazi kroz isti autentifikator kao raščlamba svježeg. Cjelovitost i istinitost različita su svojstva i samo jedno od njih treba 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 ovdje: provjerite XMLDSIG omotani potpis
// u odnosu na certifikat potpisnika popisa koji ste prikovali izvan
// kanala, i opišite što ste provjerili za revizijski trag
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);
// Snimka postoji samo zato što je autentifikator rekao da
Cache := TFileStream.Create('tl-de.snapshot', fmCreate);
try
List.SaveCache(Cache);
finally
Cache.Free;
end;
end;
Validator ne posjeduje mrežnu politiku
PAdES validator nema posla odlučivati kako dohvatiti popis popisa povjerenja, hoće li ići kroz posrednika, koliko često ponavljati ili što učiniti kad je teritorij nedostižan. To su odluke aplikacije i raspoređivanja, i u reguliranim okruženjima često se revidiraju. Zato ažuriranja stižu kroz IPdfTrustedListSource, kojem se predaje URI i granica bajtova, a koji vraća bajtove
Ono što komponenta provodi jesu invarijante koje od ažuriranja čine ažuriranje, a ne zamjenu. Update zahtijeva da se teritorij nije promijenio, da redni broj strogo raste, i da se vrijeme izdavanja ne pomjera natrag. Te tri provjere pobjeđuju najočiglednije napade na nižu verziju: ponavljanjem starijeg popisa koji još popisuje servis povučen od tada, ili zamjenom popisa drugog teritorija čije servise nikad niste namjeravali vjerovati
Granice parsera i nimalo DTD-a
TPdfTrustedListOptions ograničava veličinu XML-a, broj tokena, dubinu gniježđenja, broj servisa, broj certifikata i veličinu pojedinačnog certifikata, s klasnom funkcijom Default koja pruža upotrebljive vrijednosti. Popisi povjerenja objavljeni su dokumenti predvidive veličine, pa su granice jeftine za postaviti i nema legitimnog popisa kojem treba ih prekoračiti
Odvojeno i bezuvjetno, parser odbija DTD i deklaracije entiteta. To zatvara i odbijanje usluge širenjem entiteta i put otkrivanja vanjskih entiteta u jednoj odbitnici, i ne košta ništa jer popisi povjerenja ne koriste entitete. Svaki XML parser dostižan iz nepouzdanog unosa trebao bi se tako konfigurirati; razlika ovdje je što odbitnica nije konfigurabilna, pa je ne može isključiti dobronamjerna promjena opcije
Kvalificirano stanje bilježi se uz povjerenje lanca, ne spaja u njega
Strana evaluacije namjerno je odvojena. TPadesTrustValidationOptions.QualifiedTrustEvaluator uzima IPdfQualifiedTrustEvaluator, koji snimka popisa povjerenja implementira. Tijekom provjere valjanosti evaluator prima listni certifikat, lanac i vrijeme provjere, podudara certifikate servisa točnom DER usporedbom u odnosu na potpisnika i lanac, kombinira stanje servisa, identifikator tipa servisa i URI-jeve kvalifikatora u tom trenutku, i vraća zapis evaluacije
Rezultat slijeće na dva mjesta na svakom potpisu: QualifiedTrustStatus kao gruba stanica i QualifiedTrust kao potpuna evaluacija s teritorijem, imenom davatelja, imenom servisa, identifikatorom tipa, stanjem i vremenom početka stanja. Ono što ne čini jest ne mijenja CertificateTrustStatus. Sistemsko povjerenje lanca i kvalificirano stanje odgovaraju na različita pitanja, i izvješće koje ih uruši ne može razlikovati "povjerljivo ali ne kvalificirano" od "kvalificirano ali lanac se ne provjerava", a oboje je stvarno i oboje treba različito rukovanje
var
Options: TPadesTrustValidationOptions;
Report: TPadesValidationResult;
I: Integer;
begin
Options := TPadesTrustValidationOptions.Default;
Options.CheckRevocation := True;
Options.QualifiedTrustEvaluator := List; // autentificirana snimka
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 podudarnog servisa, ili snimka ne može odgovoriti za ovo vrijeme
Writeln(Format('signature %d: qualified status undetermined', [I]));
end;
Zašto vrijeme provjere nije sada
Zato što je kvalifikacija svojstvo trenutka. Povjerenički servis može dobiti kvalificirano stanje, kasnije ga izgubiti povlačenjem, i kasnije još biti ponovno uspostavljen, i svaki od tih prijelaza nosi vrijeme početka u popisu. Potpis napravljen dok je servis bio kvalificiran ostaje kvalificiran poslije; potpis napravljen prije odobrenja ne postaje kvalificiran retroaktivno. Evaluacija u odnosu na trenutno vrijeme dakle daje pogrešan odgovor u oba smjera
Popis nosi ono što je za ovo potrebno: svaki zapis servisa ima vrijeme početka stanja i zastavicu koja razlikuje povijesne unose od trenutnih, i evaluator ih kombinira u odnosu na vrijeme koje opskrbljujete. U praksi to vrijeme dolazi iz pouzdanog vremenskog žiga na potpisu umjesto iz vremena potpisivanja tvrđenog u CMS-u, pa zato materijal dugotrajne provjere valjanosti važi čak i za pitanje koje izgleda kao političko traženje; strana vremenskog žiga i DSS-a pokrivena je u članku o dugotrajnim potpisima
Što još morate izgraditi
Tri stvari, i nijedna ne pripada PDF knjižnici. Autentifikator, što znači stvarnu XMLDSIG provjeru u odnosu na certifikat potpisnika popisa koji ste dobili kanalom kojem vjerujete. Politika dohvata, što znači kako i koliko često osvježavate i što vaša aplikacija radi kad osvježavanje padne. I teritorijalni opseg, što znači koje popise uopće nosite, što je poslovna odluka o državama članicama u kojima potpisuju vaši poslovni partneri
Ono što dobivate od komponente jest dio koji se lako pogriješi suptilno: redoslijed autentificiraj-prije-raščlambe, ograničena i entiteta slobodna XML raščlamba, monotone invarijante ažuriranja, podudaranje servisa točnim DER-om, evaluacija povijesnog stanja, i rezultat koji ostaje odvojen od običnog povjerenja lanca. Ako je vaš neposredni problem osnovniji, da validator odbija potpis za koji vjerujete da je u redu, uobičajeni su uzroci katalogizirani u zašto validatori odbijaju PAdES potpise, a površina pregleda potpisa opisana je u pregledu potpisa i PAdES razina. Mogućnosti komponente navedene su na stranici proizvoda PDFium Delphi component