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