Rozhodnúť, že podpis PDF je kvalifikovaný podľa eIDAS, znamená odpovedať na otázku, ktorá nemá nič s kryptografiou: bol certifikát vydaný službou dôvery, ktorú členský štát vypísal ako kvalifikovanú, v momente, keď bol podpis urobený. Odpoveď býva v trusted liste, XML dokumente publikovanom per územie, a celá hodnota toho dokumentu závisí od jeho autentickosti. Preto komponent PDFium odmieta pozrieť sa dovnútra, kým sa zaň niekto prihovorí. TPdfEuropeanTrustedList.ParseAuthenticated odovzdá kompletné surové bajty volajúcim dodanému IPdfTrustedListAuthenticator skôr, než parsuje jedinú službu, a vytvorí snapshot len vtedy, keď ten autentizátor výslovne prejde
Toto poradie je návrh. Všetko ostatné v tejto funkcii z neho vyplýva, vrátane častí, ktoré vyzerajú nepríjemne
Parseované nie je dôveryhodné
Trusted list, ktorý sa parseuje čisto, vám hovorí, že XML je formálne správne. Nepovie vám nič o tom, kto ho napísal. Keďže list je to, na čom spočíva vaše celé rozhodnutie o kvalifikovanom statuse, prijatie jedného preto, že sa parseoval, by urobilo rozhodnutie bezvýznamným: útočník, ktorý dokáže list vymeniť, môže deklarovať vlastnú certifikačnú autoritu za kvalifikovanú
Toto isté uvažovanie platí pre cacheovanie a toto je pasca hodná pomenovania. Snapshot cache uchováva pôvodné XML spolu s digestom SHA-256 a bolo by ľahké brať zhodný digest pri načítaní ako dôkaz, že list je pravý. Nie je. Digest vypočítaný tým istým procesom, ktorý súbor uložil, bez zapojenia akéhokoľvek kľúča, overuje len to, že bajty sa nezmenili od chvíle, keď ste ich zapísali; ak bol list podvodný v čase cacheovania, digest potvrdzuje, že ide o ten istý podvodný list. Načítanie cacheovaného snapshotu preto beží cez ten istý autentizátor ako parsovanie čerstvého. Integrita a autentickosť sú odlišné vlastnosti a len jedna z nich potrebuje kľúč
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 býva tu: overiť obalený podpis XMLDSIG
// voči certifikátu podpisujúcemu list, ktorý ste pripinuli mimo pásma,
// a popísať, čo ste kontrolovali, pre auditnú stopu
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);
// Snapshot existuje len preto, lebo autentizátor povedal áno
Cache := TFileStream.Create('tl-de.snapshot', fmCreate);
try
List.SaveCache(Cache);
finally
Cache.Free;
end;
end;
Validátor nevlastní sieťovú politiku
Validátor PAdES nemá čo rozhodovať, ako sa dostať k listu trusted listov, či ísť cez proxy, ako často opakovať, alebo čo robiť, keď je územie nedosiahnuteľné. Toto sú rozhodnutia aplikácie a nasadenia a v regulovaných prostrediach sú často auditované. Aktualizácie preto prichádzajú cez IPdfTrustedListSource, ktorému sa odovzdá URI a bajtový strop a ktorý vracia bajty
To, čo komponent vynucuje, sú invarianty, ktoré robia z aktualizácie aktualizáciu, nie zámenu. Update vyžaduje, že územie je nezmenené, že číslo sekvencie prísne rastie a že čas vydania sa nepohne dozadu. Tieto tri kontroly porazia najzjavnejšie downgrade útoky: prehrávanie staršieho listu, ktorý stále vypisuje odvtedy stiahnutú službu, alebo zapojenie listu iného územia, ktorého služby ste nikdy nechceli brať za dôveryhodné
Limity parsera a žiadne DTD vôbec
TPdfTrustedListOptions stropuje veľkosť XML, počet tokenov, hĺbku zanorenia, počet služieb, počet certifikátov a veľkosť jednotlivého certifikátu, pričom triedová funkcia Default dodáva použiteľné hodnoty. Trusted listy sú publikované dokumenty predvídateľnej veľkosti, takže hranice sa nastavujú lacno a neexistuje legitímny list, ktorý by ich potreboval presiahnuť
Osobitne a bezpodmienečne parser odmieta deklarácie DTD a entít. Tým zatvára v jednom odmietnutí aj zamietnutie služby expanziou entít aj cestu prezrádzania externými entitami a nestojí to nič, pretože trusted listy nepoužívajú entity. Akýkoľvek XML parser dosiahnuteľný z nedôveryhodného vstupu by mal byť takto nakonfigurovaný; rozdiel tu je, že odmietnutie nie je konfigurovateľné, takže ho nemožno vypnúť dobre mínenej zmenou voľby
Kvalifikovaný status sa zaznamenáva vedľa dôvery reťaze, neslučuje sa do nej
Strana vyhodnocovania je zámerne osobitná. TPadesTrustValidationOptions.QualifiedTrustEvaluator berie IPdfQualifiedTrustEvaluator, ktorý snapshot trusted listu implementuje. Počas validácie prijíma evaluátor listový certifikát, reťaz a čas validácie, zhoduje certifikáty služieb presným DER porovnaním voči podpisovateľovi a reťazi, kombinuje status služby, identifikátor typu služby a URI kvalifikátorov v tom okamihu a vracia záznam vyhodnotenia
Výsledok pristane na dvoch miestach na každom podpise: QualifiedTrustStatus ako hrubý status a QualifiedTrust ako úplné vyhodnotenie s územím, menom poskytovateľa, menom služby, identifikátorom typu, statusom a časom začiatku statusu. To, čo nespraví, je zmena CertificateTrustStatus. Systémová dôvera reťaze a kvalifikovaný status odpovedajú na odlišné otázky a správa, ktorá ich zrúti, nedokáže rozlíšiť „dôveryhodný, ale nie kvalifikovaný“ od „kvalifikovaný, ale reťaz nevaliduje“, pričom oba sú reálne a oba potrebujú odlišné zaobchádzanie
var
Options: TPadesTrustValidationOptions;
Report: TPadesValidationResult;
I: Integer;
begin
Options := TPadesTrustValidationOptions.Default;
Options.CheckRevocation := True;
Options.QualifiedTrustEvaluator := List; // autentizovaný snapshot
Options.QualifiedValidationTime := SigningTime; // nie 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
// Žiadna zhodná služba, alebo snapshot nedokáže odpovedať za tento čas
Writeln(Format('signature %d: qualified status undetermined', [I]));
end;
Prečo čas validácie nie je teraz
Pretože kvalifikácia je vlastnosť okamihu. Službe dôvery môže byť priznaný kvalifikovaný status, neskôr odobratý a ešte neskôr obnovený a každý z týchto prechodov nesie v liste čas začiatku. Podpis urobený, kým bola služba kvalifikovaná, zostáva kvalifikovaný aj potom; podpis urobený pred priznaním sa nestráva kvalifikovaným spätne. Vyhodnocovanie voči aktuálnemu času preto dáva zlú odpoveď oboma smermi
List nesie to, čo je na to potrebné: každý záznam služby má čas začiatku statusu a príznak rozlišujúci historické záznamy od aktuálnych a evaluátor ich kombinuje voči času, ktorý dodáte. V praxi ten čas pochádza z dôveryhodnej časovej pečiatky na podpise, nie z času podpisovania tvrdeného v CMS, čo je dôvod, prečo materiál dlhodobej validácie má význam aj pre otázku, ktorá vyzerá ako dohľadanie politiky; strana pečiatky a DSS je pokrytá v článku o dlhodobých podpisoch
Čo ešte musíte postaviť
Tri veci a žiadna z nich nepatrí do PDF knižnice. Autentizátor, teda skutočné overenie XMLDSIG voči certifikátu podpisujúcemu list, ktorý ste získali kanálom, ktorému veríte. Politika sťahovania, teda ako a ako často obnovujete a čo vaša aplikácia robí, keď obnovenie zlyhá. A územný rozsah, teda ktoré listy vôbec nosíte, čo je obchodné rozhodnutie o tom, v ktorých členských štátoch podpisujú vaše protistrany
To, čo od komponentu dostanete, je časť, ktorá sa ľahko pokazí jemne: poradie autentizuj-pred-parsovaním, ohraničené parsovanie XML bez entít, monotonické invarianty aktualizácie, zhodovanie služieb presným DER, historické vyhodnocovanie statusu a výsledok, ktorý zostáva oddelený od bežnej dôvery reťaze. Ak je váš bezprostredný problém základnejší, že validátor odmieta podpis, o ktorom veríte, že je v poriadku, obvyklé príčiny sú skatalogizované v prečo validátory odmietajú podpisy PAdES a povrch inšpekcie podpisov popisuje inšpekcia podpisov a úrovní PAdES. Schopnosti komponentu sú uvedené na produktovej stránke PDFium Delphi component