Articol tehnic

Liste de încredere eIDAS și semnături PDF calificate

A decide că o semnătură PDF este calificată sub eIDAS înseamnă a răspunde unei întrebări care nu are nimic de-a face cu criptografia: certificatul a fost emis de un serviciu de încredere pe care un stat membru l-a listat drept calificat, în momentul în care semnătura a fost făcută. Răspunsul trăiește într-o listă de încredere, un document XML publicat per teritoriu, iar întreaga valoare a acelui document depinde de autenticitatea lui. Deci componenta PDFium refuză să privească înăuntrul uneia până când cineva nu a garantat pentru ea. TPdfEuropeanTrustedList.ParseAuthenticated predă octeții brute complete unui IPdfTrustedListAuthenticator furnizat de apelant înainte să parseze vreun serviciu, și creează un snapshot doar dacă acel autentificator trece explicit

Flux de autentificare-înainte-de-parsare pentru o listă de încredere europeană în Delphi: XML-ul brut dintr-o aducere proaspătă sau un snapshot din cache trece prin IPdfTrustedListAuthenticator înainte ca TPdfEuropeanTrustedList să parseze ceva
Listele proaspete și din cache întâlnesc același autentificator, iar un snapshot există doar după ce el trece

Acea ordonare este designul. Tot ce este altceva în această funcție decurge din ea, inclusiv părțile care par incomode

Parsat nu înseamnă de încredere

O listă de încredere care parsează curat vă spune că XML-ul este bine format. Nu vă spune nimic despre cine l-a scris. Deoarece lista este ceea ce pe care se sprijină întreaga decizie de stare calificată, a accepta una pentru că a parseat ar face decizia lipsită de sens: un atacator care poate substitui lista își poate declara propria autoritate de certificare calificată

Același raționament se aplică stocării în cache, iar aceasta este capcana care merită numită. Cache-ul de snapshot stochează XML-ul original împreună cu un digest SHA-256, și ar fi ușor să tratați un digest care se potrivește la încărcare drept dovadă că lista este autentică. Nu este. Un digest calculat de același proces care a stocat fișierul, fără nicio cheie implicată, verifică doar că octeții nu s-au schimbat de când i-ați scris; dacă lista era frauduloasă când a fost pusă în cache, digestul confirmă că este aceeași listă frauduloasă. Deci încărcarea unui snapshot din cache trece prin același autentificator ca parsarea uneia proaspete. Integritatea și autenticitatea sunt proprietăți diferite, și doar una dintre ele are nevoie de o cheie

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
  // Politica dumneavoastră trăiește aici: verificați semnătura XMLDSIG
  // envelopată contra certificatului de semnare a listei fixat din afara
  // benzii, și descrieți ce ați verificat pentru pista de audit
  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-ul există doar pentru că autentificatorul a spus da
  Cache := TFileStream.Create('tl-de.snapshot', fmCreate);
  try
    List.SaveCache(Cache);
  finally
    Cache.Free;
  end;
end;

Validatorul nu deține politica de rețea

Un validator PAdES nu are treabă cu a decide cum să ajungă la lista listelor de încredere, dacă să treacă printr-un proxy, cât de des să reîncerce sau ce să facă când un teritoriu este inaccesibil. Acestea sunt decizii de aplicație și de implementare, iar în mediile reglementate sunt frecvent auditate. Deci update-urile sosesc prin IPdfTrustedListSource, căruia i se predă un URI și o limită de octeți și care returnează octeți

Ce aplică efectiv componenta sunt invarianturile care fac un update un update, nu o substituție. Update cere ca teritoriul să fie neschimbat, ca numărul de secvență să crească strict și ca timpul de emitere să nu se mute înapoi. Aceste trei verificări doboară cele mai evidente atacuri de downgrade: reluarea unei liste mai vechi care listează încă un serviciu retras între timp, sau schimbarea cu lista altui teritoriu ale cărui servicii nu ați intenționat niciodată să le aveți încredere

Invarianturi de update aplicate de componenta de liste de încredere PDFium: teritoriu neschimbat, număr de secvență strict crescător și un timp de emitere care nu se mută niciodată înapoi, care împreună blochează atacurile de downgrade
Trei verificări monotone separă un update autentic de o listă rejită sau substituită

Limite ale parserului și deloc DTD

TPdfTrustedListOptions limitează mărimea XML-ului, numărul de tokenuri, adâncimea de imbricare, numărul de servicii, numărul de certificate și mărimea unui certificat individual, cu o funcție de clasă Default care furnizează valori folosibile. Listele de încredere sunt documente publicate de mărime previzibilă, deci limitele sunt ieftin de setat și nu există nicio listă legitimă care să trebuiască să le depășească

Separat și necondiționat, parserul respinge declarațiile DTD și de entități. Aceasta închide atât refuzul de serviciu prin expansiune de entități, cât și ruta de divulgare prin entități externe într-un singur refuz, și nu costă nimic deoarece listele de încredere nu folosesc entități. Orice parser XML accesibil din date de intrare nesigure ar trebui configurat astfel; diferența aici este că refuzul nu este configurabil, deci nu poate fi dezactivat de o schimbare de opțiune bine intenționată

Starea calificată este înregistrată lângă încrederea de lanț, nu fuzionată în ea

Partea de evaluare este în mod deliberat separată. TPadesTrustValidationOptions.QualifiedTrustEvaluator primește un IPdfQualifiedTrustEvaluator, pe care snapshot-ul listei de încredere îl implementează. În timpul validării evaluatorul primește certificatul frunză, lanțul și un timp de validare, potrivește certificatele de serviciu prin comparație DER exactă contra semnatarului și lanțului, combină starea serviciului, identificatorul de tip de serviciu și URI-urile de calificator la acel moment în timp și returnează o înregistrare de evaluare

Rezultatul aterizează în două locuri pe fiecare semnătură: QualifiedTrustStatus ca stare grosieră, iar QualifiedTrust ca evaluare completă cu teritoriu, nume de furnizor, nume de serviciu, identificator de tip, stare și timp de început al stării. Ce nu face este să schimbe CertificateTrustStatus. Încrederea de lanț de sistem și starea calificată răspund la întrebări diferite, iar un raport care le colapsează nu poate distinge „de încredere dar nu calificat" de „calificat dar lanțul nu validează", ambele fiind reale și ambele necesitând tratare diferită

Evaluare de încredere calificată PAdES în Delphi: IPdfQualifiedTrustEvaluator potrivește certificatele de serviciu prin comparație DER exactă și umple QualifiedTrustStatus și QualifiedTrust, în timp ce CertificateTrustStatus rămâne neatins
Încrederea de lanț și starea calificată eIDAS sunt înregistrate una lângă alta, astfel încât ambele constatări rămân vizibile
var
  Options: TPadesTrustValidationOptions;
  Report: TPadesValidationResult;
  I: Integer;
begin
  Options := TPadesTrustValidationOptions.Default;
  Options.CheckRevocation := True;
  Options.QualifiedTrustEvaluator := List;      // snapshot-ul autentificat
  Options.QualifiedValidationTime := SigningTime; // nu 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
      // Niciun serviciu potrivit, sau snapshot-ul nu poate răspunde pentru acest timp
      Writeln(Format('signature %d: qualified status undetermined', [I]));
end;

De ce timpul de validare nu este acum

Pentru că calificarea este o proprietate a unui moment. Un serviciu de încredere poate primi statut calificat, să i se retragă mai târziu și să fie reinstaurat mai târziu, iar fiecare dintre acele tranziții poartă un timp de început în listă. O semnătură făcută în timp ce serviciul era calificat rămâne calificată după; o semnătură făcută înaintea acordării nu devine calificată retroactiv. Evaluarea contra timpului curent dă prin urmare răspunsul greșit în ambele direcții

Lista poartă ceea ce este necesar pentru asta: fiecare înregistrare de serviciu are un timp de început al stării și un fanion care distinge intrările istorice de cele curente, iar evaluatorul le combină contra timpului pe care îl furnizați. În practică acel timp vine de la un marcaj temporal de încredere pe semnătură, nu de la timpul de semnare declarat în CMS, ceea ce este motivul pentru care materialul de validare pe termen lung contează chiar și pentru o întrebare care pare o căutare de politică; partea de marcaj temporal și DSS este tratată în articolul despre semnăturile pe termen lung

Ce trebuie totuși să construiți

Trei lucruri, și niciunul nu aparține unei biblioteci PDF. Autentificatorul, adică verificarea XMLDSIG efectivă contra unui certificat de semnare a listei obținut printr-un canal în care aveți încredere. Politica de aducere, adică cum și cât de des reîmprospătați și ce face aplicația dumneavoastră când o reîmprospătare eșuează. Și sfera teritorială, adică ce liste purtați deloc, ceea ce este o decizie de afaceri privind statele membre în care semnează contrapărțile dumneavoastră

Ce primiți de la componentă este partea care este ușor de greșit subtil: ordonarea autentificare-înainte-de-parsare, parsare XML mărginită și fără entități, invarianturi monotone de update, potrivirea de servicii după DER exact, evaluarea istorică a stării și un rezultat care rămâne separat de încrederea obișnuită de lanț. Dacă problema imediată este mai de bază, că un validator respinge o semnătură pe care credeți că este bună, cauzele obișnuite sunt catalogate în de ce resping validatorii semnăturile PAdES, iar suprafața de inspecție a semnăturilor este descrisă în inspectarea semnăturilor și a nivelurilor PAdES. Capacitățile componentei sunt listate pe pagina de produs PDFium Delphi component