Teknisk artikel

eIDAS betroede lister og kvalificerede PDF-signaturer

At afgøre, at en PDF-signatur er kvalificeret under eIDAS, betyder at besvare et spørgsmål, der intet har med kryptografi at gøre: blev certifikatet udstedt af en trust service, som en medlemsstat listede som kvalificeret, i det øjeblik, signaturen blev lavet. Svaret bor i en betroet liste, et XML-dokument offentliggjort pr. område, og hele værdien af det dokument afhænger af dets autenticitet. Så PDFium-komponenten nægter at kigge ind i en, før nogen har kautioneret for den. TPdfEuropeanTrustedList.ParseAuthenticated giver de komplette rå bytes til en kalder-leveret IPdfTrustedListAuthenticator, før den parser en eneste service, og den opretter et snapshot kun, hvis den autentifikator eksplicit består

Authenticate-before-parse-flow for en europæisk betroet liste i Delphi: rå XML fra en frisk hentning eller et cachet snapshot passerer gennem IPdfTrustedListAuthenticator, før TPdfEuropeanTrustedList parser noget
Friske og cachede lister møder samme autentifikator, og et snapshot findes kun, efter den består

Den rækkefølge er designet. Alt andet i denne funktion følger af den, inklusive de dele, der ser upraktiske ud

Parses er ikke betroet

En betroet liste, der parser rent, fortæller dig, at XML-en er velformet. Den fortæller dig intet om, hvem der skrev den. Da listen er det, hele din kvalificeret-status-beslutning hviler på, ville det at acceptere en, fordi den parser, gøre beslutningen meningsløs: en angriber, der kan udskifte listen, kan erklære sin egen certifikatautoritet kvalificeret

Samme ræsonnement gælder caching, og det er fælden værd at navngive. Snapshot-cachen gemmer den oprindelige XML sammen med en SHA-256-digest, og det ville være let at behandle en matchende digest ved indlæsning som bevis på, at listen er ægte. Det er den ikke. En digest beregnet af samme proces, der gemte filen, uden nogen nøgle involveret, verificerer kun, at bytene ikke er ændret siden, du skrev dem; hvis listen var frauduløs, da den blev cachet, bekræfter digesten, at det er samme frauduløse liste. Så indlæsning af et cachet snapshot kører gennem samme autentifikator som parsing af en frisk. Integritet og autenticitet er forskellige egenskaber, og kun én af dem behøver en nøgle

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
  // Din politik bor her: verificér XMLDSIG enveloped signaturen
  // mod det liste-signerende certifikat, du pinnede ud af båndet,
  // og beskriv, hvad du tjekkede til revisionssporet
  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 findes kun, fordi autentifikatoren sagde ja
  Cache := TFileStream.Create('tl-de.snapshot', fmCreate);
  try
    List.SaveCache(Cache);
  finally
    Cache.Free;
  end;
end;

Validatoren ejer ikke netværkspolitikken

En PAdES-validator har intet med at afgøre, hvordan listen over betroede lister nås, om der skal gås gennem en proxy, hvor ofte der retries, eller hvad der gøres, når et område ikke kan nås. Det er applikations- og udrulningsbeslutninger, og i regulerede miljøer revideres de ofte. Så opdateringer ankommer gennem IPdfTrustedListSource, som får udleveret en URI og et byttetag og returnerer bytes

Hvad komponenten håndhæver, er de invarianter, der gør en opdatering til en opdatering snarere end en substitution. Update kræver, at området er uændret, at sekvensnummeret strengt tager stigende, og at udgivelsestidspunktet ikke bevæger sig baglæns. De tre tjek besejrer de mest oplagte nedgraderingsangreb: at genafspille en ældre liste, der stadig lister en siden indtrukket service, eller at bytte en anden områdes liste ind, hvis services du aldrig havde til hensigt at stole på

Opdateringsinvarianter håndhævet af PDFium trusted list-komponenten: uændret område, strengt stigende sekvensnummer og et udgivelsestidspunkt, der aldrig bevæger sig baglæns, hvilket tilsammen blokerer nedgraderingsangreb
Tre monotone tjek adskiller en ægte opdatering fra en genafspillet eller substitueret liste

Parsergrænser, og slet ingen DTD

TPdfTrustedListOptions tager på XML-størrelsen, tokenantallet, nesting-dybden, antallet af services, antallet af certifikater og størrelsen af et enkelt certifikat, med en Default class function, der leverer brugbare værdier. Betroede lister er offentliggjorte dokumenter af forudsigelig størrelse, så grænser er billige at sætte, og der findes ingen legitim liste, der behøver at overskride dem

Separat og ubetinget afviser parseren DTD- og entitetserklæringer. Det lukker både entity-expansion denial of service og external-entity-offentliggørelsesvejen i ét afslag, og det koster ingenting, for betroede lister bruger ikke entiteter. Enhver XML-parser, der kan nås fra upålideligt input, bør konfigureres sådan; forskellen her er, at afslaget ikke kan konfigureres, så det ikke kan slås fra ved en velmenende indstillingsændring

Kvalificeret status registreres ved siden af kædetillid, ikke flettes ind i den

Evalueringssiden er bevidst adskilt. TPadesTrustValidationOptions.QualifiedTrustEvaluator tager en IPdfQualifiedTrustEvaluator, som trusted list-snapshotet implementerer. Under validering modtager evaluatoren bladcertifikatet, kæden og et valideringstidspunkt, matcher servicecertifikater ved eksakt DER-sammenligning mod underskriveren og kæden, kombinerer servicestatus, service type-identifikatoren og qualifier-URI'erne på det tidspunkt og returnerer en evalueringspost

Resultatet lander to steder på hver signatur: QualifiedTrustStatus som en grov status, og QualifiedTrust som den fulde evaluering med område, udbydernavn, servicenavn, type-identifikator, status og status-starttidspunkt. Hvad den ikke gør, er at ændre CertificateTrustStatus. Systemkædetillid og kvalificeret status besvarer forskellige spørgsmål, og en rapport, der kollapser dem, kan ikke skelne "betroet men ikke kvalificeret" fra "kvalificeret men kæden validerer ikke", som begge er reelle, og som begge behøver forskellig håndtering

PAdES kvalificeret tillidsevaluering i Delphi: IPdfQualifiedTrustEvaluator matcher servicecertifikater ved eksakt DER-sammenligning og udfylder QualifiedTrustStatus og QualifiedTrust, mens CertificateTrustStatus forbliver urørt
Kædetillid og eIDAS kvalificeret status registreres side om side, så begge fund forbliver synlige
var
  Options: TPadesTrustValidationOptions;
  Report: TPadesValidationResult;
  I: Integer;
begin
  Options := TPadesTrustValidationOptions.Default;
  Options.CheckRevocation := True;
  Options.QualifiedTrustEvaluator := List;      // det autentificerede snapshot
  Options.QualifiedValidationTime := SigningTime; // ikke 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
      // Ingen matchende service, eller snapshotet kan ikke svare for dette tidspunkt
      Writeln(Format('signature %d: qualified status undetermined', [I]));
end;

Hvorfor valideringstidspunktet ikke er nu

Fordi kvalifikation er en egenskab ved et øjeblik. En trust service kan tildeles kvalificeret status, senere få den trukket tilbage og endnu senere genindsættes, og hver af de overgange bærer et starttidspunkt i listen. En signatur lavet, mens servicen var kvalificeret, forbliver kvalificeret bagefter; en signatur lavet før tildelingen bliver ikke kvalificeret retrospektivt. At evaluere mod det aktuelle tidspunkt giver derfor det forkerte svar i begge retninger

Listen bærer det, der behøves til dette: hver servicepost har et status-starttidspunkt og et flag, der skelner historiske poster fra aktuelle, og evaluatoren kombinerer dem mod det tidspunkt, du leverer. I praksis kommer det tidspunkt fra et betroet tidsstempel på signaturen snarere end fra den signeringstid, CMS'en hævder, hvilket er grunden til, at langtidsvalideringsmateriale tæller selv for et spørgsmål, der ligner et politikopslag; tidsstempel- og DSS-siden er dækket i artiklen om langtidssignaturer

Hvad du stadig må bygge

Tre ting, og ingen af dem hører hjemme i et PDF-bibliotek. Autentifikatoren, altså faktisk XMLDSIG-verificering mod et liste-signerende certifikat, du erhvervede gennem en kanal, du stoler på. Hentepolitikken, altså hvordan og hvor ofte du opfrisker, og hvad din applikation gør, når en opfriskning fejler. Og det territoriale omfang, altså hvilke lister du overhovedet bærer, hvilket er en forretningsbeslutning om, hvilke medlemsstater dine modparter signerer i

Det, du får fra komponenten, er den del, der er let at tage diskret fejl af: authenticate-before-parse-rækkefølgen, afgrænset og entitetsfri XML-parsing, monotone opdateringsinvarianter, eksakt-DER-service-matching, historisk statusevaluering og et resultat, der forbliver adskilt fra almindelig kædetillid. Hvis dit umiddelbare problem er mere basalt, at en validator afviser en signatur, du tror er fin, er de sædvanlige årsager katalogiseret i hvorfor validatorer afviser PAdES-signaturer, og signaturinspektionsoverfladen er beskrevet i inspektion af signaturer og PAdES-niveauer. Komponentevner er opført på produktsiden PDFium Delphi component