Å 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
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å
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
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