Teknisk artikkel

eIDAS-tillitslister og kvalifiserte PDF-signaturer

Å avgjøre at en PDF-signatur er kvalifisert under eIDAS, betyr å svare på et spørsmål som ikke har noe med kryptografi å gjøre: ble sertifikatet utstedt av en tillitstjeneste som en medlemsstat oppførte som kvalifisert, i det øyeblikket signaturen ble laget. Svaret bor i en tillitsliste, et XML-dokument publisert per territorium, og hele verdien av det dokumentet avhenger av dets autentisitet. Så PDFium-komponenten nekter å se inn i én før noen har gått god for den. TPdfEuropeanTrustedList.ParseAuthenticated gir de komplette rå bytene til en kaller-levert IPdfTrustedListAuthenticator før den parser én eneste tjeneste, og den oppretter et øyeblikksbilde bare hvis den autentisatoren eksplisitt passerer

Autentiser-før-parse-flyt for en europeisk tillitsliste i Delphi: rå XML fra en fersk henting eller et bufret øyeblikksbilde passerer gjennom IPdfTrustedListAuthenticator før TPdfEuropeanTrustedList parser noe
Ferske og bufrede lister møter den samme autentisatoren, og et øyeblikksbilde finnes bare etter at den passerer

Den ordeningen er designet. Alt annet i denne funksjonen følger av den, inkludert delene som ser ubeleilige ut

Parsert er ikke tiltrudt

En tillitsliste som parser rent, forteller deg at XML-en er velformet. Den forteller deg ingenting om hvem som skrev den. Siden listen er det hele kvalifisertstatusbeslutningen hviler på, ville det å akseptere én fordi den parsede, gjøre beslutningen meningsløs: en angriper som kan erstatte listen, kan erklære sin egen sertifikatmyndighet kvalifisert

Den samme resonneringen gjelder hurtigbufring, og dette er fellen verdt å navngi. Øyeblikksbildebufferen lagrer den opprinnelige XML-en sammen med en SHA-256-digest, og det ville være lett å behandle en matchende digest ved lasting som bevis på at listen er genuin. Det er den ikke. En digest beregnet av den samme prosessen som lagret filen, uten nøkkel involvert, verifiserer bare at bytene ikke har endret seg siden du skrev dem; hvis listen var svindel da den ble bufret, bekrefter digesten at det er den samme svindellisten. Så lasting av et bufret øyeblikksbilde går gjennom den samme autentisatoren som å parse en fersk. Integritet og autentisitet er forskjellige egenskaper, og bare én av dem trenger en nøkkel

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
  // Politikken din bor her: verifiser den XMLDSIG-konvoluterte signaturen
  // mot liste-signeringssertifikatet du pinnet ut av bånd,
  // og beskriv hva du sjekket for revisjonssporet
  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);
  // Øyeblikksbildet finnes bare fordi autentisatoren sa ja
  Cache := TFileStream.Create('tl-de.snapshot', fmCreate);
  try
    List.SaveCache(Cache);
  finally
    Cache.Free;
  end;
end;

Validatoren eier ikke nettverkspolicy

En PAdES-validator har ikke som sak å avgjøre hvordan man når listen over tillitslister, om man skal gå gjennom en proxy, hvor ofte man skal prøve igjen, eller hva man gjør når et territorium er uoppnåelig. Det er applikasjons- og utrullingsavgjørelser, og i regulerte miljøer revideres de ofte. Så oppdateringer ankommer gjennom IPdfTrustedListSource, som får utlevert en URI og et bytetak og returnerer byte

Det komponenten håndhever, er invariantene som gjør en oppdatering til en oppdatering snarere enn en substitusjon. Update krever at territoriet er uendret, at sekvensnummeret øker strengt, og at utstedelsestiden ikke flytter seg baklengs. De tre sjekkene beseirer de mest opplagte nedgraderingsangrepene: å spille av en eldre liste som fortsatt oppfører en tjeneste som siden er trukket tilbake, eller å bytte inn et annet territoriums liste hvis tjenester du aldri tenkte deg å stole på

Oppdateringsinvarianter håndhevet av PDFium tillitslistekomponenten: uendret territorium, strengt økende sekvensnummer og en utstedelsestid som aldri flytter seg baklengs, noe som sammen blokkerer nedgraderingsangrep
Tre monotone sjekker skiller en genuin oppdatering fra en avspilt eller substituert liste

Parsergrenser, og ingen DTD i det hele tatt

TPdfTrustedListOptions takserer XML-størrelsen, tokenantallet, nesteringsdybden, antall tjenester, antall sertifikater og størrelsen på et individuelt sertifikat, med en Default-klassefunksjon som leverer brukbare verdier. Tillitslister er publiserte dokumenter av forutsigbar størrelse, så grenser er billige å sette, og det finnes ingen legitim liste som trenger å overskride dem

Separat og ubetinget avviser parseren DTD- og entitetsdeklarasjoner. Det lukker både entitetsekspansjonstjenestenekt og den eksterne-entitet-offentliggjøringsruten i ett avslag, og det koster ingenting fordi tillitslister ikke bruker entiteter. Enhver XML-parser nåbar fra utiltrudt input bør konfigureres slik; forskjellen her er at avslaget ikke er konfigurerbart, så det kan ikke slås av med en velmenende valgendring

Kvalifisert status registreres ved siden av kjedetro, ikke flettet inn i den

Evalueringssiden er bevisst separat. TPadesTrustValidationOptions.QualifiedTrustEvaluator tar en IPdfQualifiedTrustEvaluator, som tillitsliste-øyeblikksbildet implementerer. Under validering mottar evaluatoren bladsertifikatet, kjeden og en valideringstid, matcher tjenestesertifikater ved eksakt DER-sammenligning mot signataren og kjeden, kombinerer tjenestestatusen, tjenestetypeidentifikatoren og kvalifikator-URI-ene på det tidspunktet, og returnerer en evalueringsrecord

Resultatet lander to steder på hver signatur: QualifiedTrustStatus som en grov status, og QualifiedTrust som den fullstendige evalueringen med territorium, providernavn, tjenestenavn, typeidentifikator, status og statusstarttid. Det den ikke gjør, er å endre CertificateTrustStatus. Systemkjedetro og kvalifisert status svarer forskjellige spørsmål, og en rapport som kollapser dem, kan ikke skille «tiltrudt men ikke kvalifisert» fra «kvalifisert men kjeden validerer ikke», som begge er reelle og begge trenger ulik håndtering

PAdES kvalifisert tillitsevaluering i Delphi: IPdfQualifiedTrustEvaluator matcher tjenestesertifikater ved eksakt DER-sammenligning og fyller QualifiedTrustStatus og QualifiedTrust mens CertificateTrustStatus forblir urørt
Kjedetro og eIDAS kvalifisert status registreres side om side slik at begge funnene forblir synlige
var
  Options: TPadesTrustValidationOptions;
  Report: TPadesValidationResult;
  I: Integer;
begin
  Options := TPadesTrustValidationOptions.Default;
  Options.CheckRevocation := True;
  Options.QualifiedTrustEvaluator := List;      // det autentiserte øyeblikksbildet
  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 tjeneste, eller øyeblikksbildet kan ikke svare for denne tiden
      Writeln(Format('signature %d: qualified status undetermined', [I]));
end;

Hvorfor valideringstiden ikke er nå

Fordi kvalifisering er en egenskap ved et øyeblikk. En tillitstjeneste kan innvilges kvalifisert status, senere få den trukket tilbake, og senere igjen bli gjeninnsatt, og hver av de overgangene bærer en starttid i listen. En signatur laget mens tjenesten var kvalifisert, forblir kvalifisert etterpå; en signatur laget før innvilgelsen blir ikke kvalifisert retroaktivt. Å evaluere mot nåtiden gir derfor feil svar i begge retninger

Listen bærer det som trengs for dette: hver tjenesterecord har en statusstarttid og et flagg som skiller historiske oppføringer fra nåværende, og evaluatoren kombinerer dem mot tiden du leverer. I praksis kommer den tiden fra et tiltrudt tidsstempel på signaturen snarere enn fra signeringstidspunktet hevdet i CMS-en, noe som er hvorfor langtidsvalideringsmateriale betyr noe selv for et spørsmål som ser ut som et policyoppslag; tidsstempel- og DSS-siden er dekket i artikkelen om langtidssignaturer

Hva du fortsatt må bygge

Tre ting, og ingen av dem hører hjemme i et PDF-bibliotek. Autentisatoren, altså faktisk XMLDSIG-verifisering mot et listesigneringssertifikat du skaffet gjennom en kanal du stoler på. Hentepolicyen, altså hvordan og hvor ofte du fornyer, og hva applikasjonen din gjør når en fornyelse feiler. Og det territoriale omfanget, altså hvilke lister du i det hele tatt bærer, noe som er en forretningsavgjørelse om hvilke medlemsstater motpartene dine signerer i

Det du får fra komponenten, er delen som er lett å ta subtilt feil på: autentiser-før-parse-ordening, avgrenset og entitetsfri XML-parsing, monotone oppdateringsinvarianter, eksakt-DER-tjenestematching, historisk statusevaluering, og et resultat som forblir adskilt fra ordinær kjedetro. Hvis ditt umiddelbare problem er mer grunnleggende, at en validator avviser en signatur du tror er fin, er de vanlige årsakene katalogisert i hvorfor validatorer avviser PAdES-signaturer, og signaturinspeksjonsflaten er beskrevet i inspeksjon av signaturer og PAdES-nivåer. Komponentevner er oppført på produktsiden for PDFium Delphi component