Teknisk artikel

eIDAS-förtroendelistor och kvalificerade PDF-signaturer

Att avgöra att en PDF-signatur är kvalificerad enligt eIDAS betyder att svara på en fråga som inte har något med kryptografi att göra: var certifikatet utfärdat av en förtroendetjänst som en medlemsstat listat som kvalificerad, i stunden signaturen gjordes. Svaret bor i en förtroendelista, ett XML-dokument publicerat per territorium, och hela värdet av det dokumentet beror på dess äkthet. Så PDFium-komponenten vägrar titta inuti en innan någon har gått i god för den. TPdfEuropeanTrustedList.ParseAuthenticated räcker de fullständiga råa bytena till en anropartillhandahållen IPdfTrustedListAuthenticator innan den parsar en enda tjänst, och den skapar en ögonblicksbild bara om den autentiseraren uttryckligen godkänns

Flöde för autentisera-innan-parsning för en europeisk förtroendelista i Delphi: rå XML från en färsk hämtning eller en cachad ögonblicksbild passerar IPdfTrustedListAuthenticator innan TPdfEuropeanTrustedList parsar någonting
Färska och cachade listor möter samma autentiserare, och en ögonblicksbild finns bara efter att den godkänts

Den ordningen är designen. Allt annat i denna funktion följer av den, inklusive delarna som ser obekväma ut

Parsad är inte betrodd

En förtroendelista som parsar rent talar om för dig att XML:en är välformad. Den talar inte om för dig något om vem som skrev den. Eftersom listan är vad hela ditt beslut om kvalificerad status vilar på skulle att acceptera en för att den parsade göra beslutet meningslöst: en angripare som kan byta ut listan kan förklara sin egen certifikatutfärdare kvalificerad

Samma resonemang gäller cachning, och det är fällan värd att namnge. Ögonblicksbildscachen lagrar den ursprungliga XML:en tillsammans med ett SHA-256-sammandrag, och det vore lätt att behandla ett matchande sammandrag vid inläsning som bevis för att listan är äkta. Det är det inte. Ett sammandrag beräknat av samma process som lagrade filen, utan någon nyckel inblandad, verifierar bara att bytena inte har ändrats sedan du skrev dem; om listan var bedräglig när den cachades bekräftar sammandraget att det är samma bedrägliga lista. Så att läsa in en cachad ögonblicksbild går genom samma autentiserare som att parsa en färsk. Integritet och äkthet är olika egenskaper och bara en av dem behöver en nyckel

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 policy bor här: verifiera XMLDSIG:s omslutande signatur
  // mot listsigneringscertifikatet du pinnade på out-of-band,
  // och beskriv vad du kontrollerade för spårbarheten
  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);
  // Ögonblicksbilden finns bara för att autentiseraren sa ja
  Cache := TFileStream.Create('tl-de.snapshot', fmCreate);
  try
    List.SaveCache(Cache);
  finally
    Cache.Free;
  end;
end;

Validatören äger inte nätverkspolicyn

En PAdES-validator har inte att bestämma hur listan över förtroendelistor ska nås, huruvida den ska gå via en proxy, hur ofta den ska försöka igen eller vad den ska göra när ett territorium inte kan nås. De är applikations- och distributionsbeslut, och i reglerade miljöer granskas de ofta. Så uppdateringar anländer via IPdfTrustedListSource, som räcks en URI och ett bytetak och returnerar byte

Det komponenten gör att tillämpa är de invarianter som gör en uppdatering till en uppdatering i stället för en substitution. Update kräver att territoriet är oförändrat, att sekvensnumret strängt ökar och att utfärdningstiden inte går bakåt. De tre kontrollerna besegrar de mest uppenbara nedgraderingsattackerna: att spela upp en äldre lista som fortfarande listar en numera indragen tjänst, eller att byta in en annan territoriums lista vars tjänster du aldrig avsåg att lita på

Uppdateringsinvarianter som PDFium-förtroendelistakomponenten tillämpar: oförändrat territorium, strängt ökande sekvensnummer och en utfärdningstid som aldrig går bakåt, vilket tillsammans blockerar nedgraderingsattacker
Tre monotoniska kontroller skiljer en äkta uppdatering från en uppspelad eller utbytt lista

Parsargränser, och ingen DTD alls

TPdfTrustedListOptions takar XML-storleken, tokenantalet, nästlingsdjupet, antalet tjänster, antalet certifikat och storleken på ett enskilt certifikat, med en klassfunktion Default som tillhandahåller användbara värden. Förtroendelistor är publicerade dokument av förutsägbar storlek, så gränser är billiga att sätta och det finns ingen legitim lista som behöver överskrida dem

Separat och villkorslöst avvisar parsaren DTD- och entitetsdeklarationer. Det stänger både entitetsexpansions-denial of service och den externa entitetsavslöjningsvägen i en enda vägran, och det kostar inget eftersom förtroendelistor inte använder entiteter. Varsomhelst XML-parser nåbar från obetrodd indata bör konfigureras så; skillnaden här är att vägran inte är konfigurerbart, så det kan inte slås av av en välvillig alternativändring

Kvalificerad status registreras bredvid kedjetillit, inte sammansluten med den

Utvärderingssidan är med flit separat. TPadesTrustValidationOptions.QualifiedTrustEvaluator tar en IPdfQualifiedTrustEvaluator, som förtroendelisteögonblicksbilden implementerar. Under valideringen tar utvärderaren emot lövcertifikatet, kedjan och en valideringstid, matchar tjänstcertifikat genom exakt DER-jämförelse mot undertecknaren och kedjan, kombinerar tjänststatus, tjänsttypsidentifieraren och kvalificerar-URI:erna vid den tidpunkten och returnerar en utvärderingspost

Resultatet landar på två ställen på varje signatur: QualifiedTrustStatus som en grov status och QualifiedTrust som den fullständiga utvärderingen med territorium, providernamn, tjänstenamn, typidentifierare, status och statusstarttid. Vad den inte gör är att ändra CertificateTrustStatus. Systemkedjetillit och kvalificerad status svarar olika frågor, och en rapport som kollapsar dem kan inte skilja ”betrodd men inte kvalificerad” från ”kvalificerad men kedjan validerar inte”, båda är verkliga och båda behöver olika hantering

PAdES-kvalificerad tillitsutvärdering i Delphi: IPdfQualifiedTrustEvaluator matchar tjänstcertifikat genom exakt DER-jämförelse och fyller QualifiedTrustStatus och QualifiedTrust medan CertificateTrustStatus förblir orörd
Kedjetillit och eIDAS-kvalificerad status registreras sida vid sida så att båda fynden förblir synliga
var
  Options: TPadesTrustValidationOptions;
  Report: TPadesValidationResult;
  I: Integer;
begin
  Options := TPadesTrustValidationOptions.Default;
  Options.CheckRevocation := True;
  Options.QualifiedTrustEvaluator := List;      // den autentiserade ögonblicksbilden
  Options.QualifiedValidationTime := SigningTime; // inte 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 matchande tjänst, eller ögonblicksbilden kan inte svara för denna tid
      Writeln(Format('signature %d: qualified status undetermined', [I]));
end;

Varför valideringstiden inte är nu

För kvalificering är en egenskap hos ett ögonblick. En förtroendetjänst kan beviljas kvalificerad status, senare få den indragen och ännu senare återinsättas, och vart och ett av de övergångarna bär en starttid i listan. En signatur gjord medan tjänsten var kvalificerad förblir kvalificerad därefter; en signatur gjord före beviljandet blir inte kvalificerad retroaktivt. Att utvärdera mot aktuell tid ger därför fel svar i båda riktningarna

Listan bär det som behövs för detta: varenda tjänstepost har en statusstarttid och en flagga som skiljer historiska poster från aktuella, och utvärderaren kombinerar dem mot tiden du tillhandahåller. I praktiken kommer tiden från en betrodd tidsstämpel på signaturen i stället för från signeringsstiden som hävdas i CMS:en, vilket är varför material för långtidsvalidering spelar roll även för en fråga som ser ut som en policyuppslag; tidsstämpel- och DSS-sidan tas upp i artikeln om långtidssignaturer

Vad du fortfarande måste bygga

Tre saker, och ingen av dem hör hemma i ett PDF-bibliotek. Autentiseraren, menande verklig XMLDSIG-verifiering mot ett listsigneringscertifikat du skaffat genom en kanal du litar på. Hämtningspolicyn, menande hur och hur ofta du uppdaterar och vad din applikation gör när en uppdatering misslyckas. Och den territoriella omfattningen, menande vilka listor du överhuvudtaget bär, vilket är ett affärsbeslut om i vilka medlemsstater dina motparter signerar

Det du får från komponenten är den del som är lätt att få subtilt fel: autentisera-innan-parsning-ordning, avgränsad och entitetsfri XML-parsning, monotoniska uppdateringsinvarianter, exakt DER-tjänstmatchning, historisk statusutvärdering och ett resultat som förblir åtskilt från vanlig kedjetillit. Om ditt omedelbara problem är mer grundläggande, att en validator avvisar en signatur du tror är fin, finns de vanliga orsakerna katalogiserade i varför validatorer avvisar PAdES-signaturer, och signaturinspektionsytan beskrivs i inspektion av signaturer och PAdES-nivåer. Komponentens förmågor listas på produktsidan för PDFium Delphi component