Zu entscheiden, dass eine PDF-Signatur unter eIDAS qualifiziert ist, bedeutet, eine Frage zu beantworten, die mit Kryptografie nichts zu tun hat: Wurde das Zertifikat von einem Vertrauensdienst ausgestellt, den ein Mitgliedstaat als qualifiziert gelistet hat, im Moment der Signatur. Die Antwort lebt in einer Trusted List, einem je Territorium veröffentlichten XML-Dokument, und der ganze Wert dieses Dokuments hängt an seiner Authentizität. Die PDFium-Komponente weigert sich deshalb, in eine hineinzuschauen, bis jemand dafür gebürgt hat. TPdfEuropeanTrustedList.ParseAuthenticated reicht die vollständigen Rohbytes an einen vom Aufrufer gelieferten IPdfTrustedListAuthenticator weiter, bevor sie einen einzigen Dienst parst, und sie erzeugt einen Snapshot nur, wenn dieser Authenticator ausdrücklich besteht
Diese Reihenfolge ist das Design. Alles andere in diesem Feature folgt daraus, einschließlich der Teile, die unbequem aussehen
Geparst ist nicht vertrauenswürdig
Eine Trusted List, die sauber parst, sagt Ihnen, dass das XML wohlgeformt ist. Sie sagt nichts darüber, wer sie schrieb. Da die Liste das Fundament Ihrer gesamten Qualified-Status-Entscheidung ist, würde eine Liste zu akzeptieren, weil sie parst, die Entscheidung bedeutungslos machen: Ein Angreifer, der die Liste austauschen kann, kann seine eigene Zertifizierungsstelle für qualifiziert erklären
Dasselbe Denken gilt für das Caching, und das ist die Falle, die es wert ist, benannt zu werden. Der Snapshot-Cache speichert das ursprüngliche XML zusammen mit einem SHA-256-Digest, und es wäre leicht, einen passenden Digest beim Laden als Beweis zu nehmen, dass die Liste echt ist. Ist sie nicht. Ein Digest, berechnet von demselben Prozess, der die Datei speicherte, ohne dass ein Schlüssel beteiligt war, verifiziert nur, dass die Bytes sich nicht geändert haben, seit Sie sie schrieben; war die Liste betrügerisch, als sie gecacht wurde, bestätigt der Digest, dass es dieselbe betrügerische Liste ist. Also läuft das Laden eines gecachten Snapshots durch denselben Authenticator wie das Parsen einer frischen. Integrität und Authentizität sind verschiedene Eigenschaften, und nur eine von ihnen braucht einen Schlüssel
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
// Ihre Richtlinie lebt hier: die XMLDSIG-enveloped Signatur gegen das
// List-Signaturzertifikat prüfen, das Sie out-of-band gepinnt haben,
// und beschreiben, was Sie für den Audit-Trail geprüft haben
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);
// Der Snapshot existiert nur, weil der Authenticator Ja sagte
Cache := TFileStream.Create('tl-de.snapshot', fmCreate);
try
List.SaveCache(Cache);
finally
Cache.Free;
end;
end;
Der Validator besitzt keine Netzwerkrichtlinie
Ein PAdES-Validator hat nicht zu entscheiden, wie er die Liste der Trusted Lists erreicht, ob er durch einen Proxy geht, wie oft er es erneut versucht oder was zu tun ist, wenn ein Territorium unerreichbar ist. Das sind Anwendungs- und Deployment-Entscheidungen, und in regulierten Umgebungen werden sie häufig auditiert. Updates kommen deshalb durch IPdfTrustedListSource, dem eine URI und eine Byte-Obergrenze gereicht werden und das Bytes zurückgibt
Was die Komponente durchsetzt, sind die Invarianten, die ein Update zu einem Update machen statt zu einer Ersetzung. Update verlangt, dass das Territorium unverändert ist, dass die Sequenznummer streng steigt und dass der Ausgabezeitpunkt nicht rückwärts wandert. Diese drei Prüfungen schlagen die naheliegendsten Downgrade-Angriffe: das Abspielen einer älteren Liste, die einen inzwischen zurückgezogenen Dienst noch listet, oder das Einschieben der Liste eines anderen Territoriums, dessen Dienste man nie zu vertrauen beabsichtigte
Parser-Grenzen und überhaupt kein DTD
TPdfTrustedListOptions begrenzt die XML-Größe, die Tokenzahl, die Verschachtelungstiefe, die Zahl der Dienste, die Zahl der Zertifikate und die Größe eines einzelnen Zertifikats, mit einer Default-Klassenfunktion, die brauchbare Werte liefert. Trusted Lists sind veröffentlichte Dokumente vorhersagbarer Größe, also sind Grenzen billig zu setzen, und es gibt keine legitime Liste, die sie überschreiten müsste
Separat und bedingungslos weist der Parser DTD- und Entity-Deklarationen zurück. Das schließt sowohl die Entity-Expansion-Denial-of-Service als auch den External-Entity-Enthüllungsweg in einer einzigen Ablehnung, und es kostet nichts, denn Trusted Lists verwenden keine Entities. Jeder XML-Parser, der von nicht vertrauenswürdiger Eingabe erreichbar ist, sollte so konfiguriert sein; der Unterschied hier ist, dass die Ablehnung nicht konfigurierbar ist, also nicht durch eine gut gemeinte Optionsänderung abgeschaltet werden kann
Qualified-Status wird neben dem Kettenvertrauen erfasst, nicht hineinvermischt
Die Auswertungsseite ist bewusst getrennt. TPadesTrustValidationOptions.QualifiedTrustEvaluator nimmt einen IPdfQualifiedTrustEvaluator, den der Trusted-List-Snapshot implementiert. Während der Validierung empfängt der Evaluator das Blattzertifikat, die Kette und eine Validierungszeit, matcht Dienstzertifikate per exaktem DER-Vergleich gegen Unterzeichner und Kette, kombiniert den Dienststatus, die Diensttyp-Kennung und die Qualifier-URIs zu diesem Zeitpunkt und gibt einen Auswertungsdatensatz zurück
Das Ergebnis landet an zwei Stellen je Signatur: QualifiedTrustStatus als grober Status und QualifiedTrust als die volle Auswertung mit Territorium, Providername, Dienstname, Typkennung, Status und Statusbeginn. Was es nicht tut, ist CertificateTrustStatus zu ändern. System-Kettenvertrauen und Qualified-Status beantworten verschiedene Fragen, und ein Bericht, der sie zusammenfaltet, kann „vertrauenswürdig, aber nicht qualifiziert“ nicht von „qualifiziert, aber die Kette validiert nicht“ unterscheiden, beides ist real und beides braucht unterschiedliche Behandlung
var
Options: TPadesTrustValidationOptions;
Report: TPadesValidationResult;
I: Integer;
begin
Options := TPadesTrustValidationOptions.Default;
Options.CheckRevocation := True;
Options.QualifiedTrustEvaluator := List; // der authentifizierte Snapshot
Options.QualifiedValidationTime := SigningTime; // nicht 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
// Kein passender Dienst, oder der Snapshot kann für diese Zeit nicht antworten
Writeln(Format('signature %d: qualified status undetermined', [I]));
end;
Warum die Validierungszeit nicht jetzt ist
Denn Qualifizierung ist eine Eigenschaft eines Moments. Ein Vertrauensdienst kann qualifizierten Status erhalten, ihn später entzogen bekommen und später noch wiedereingesetzt werden, und jeder dieser Übergänge trägt einen Beginnzeitpunkt in der Liste. Eine Signatur, die gemacht wurde, während der Dienst qualifiziert war, bleibt danach qualifiziert; eine Signatur vor der Verleihung wird nicht rückwirkend qualifiziert. Gegen die aktuelle Zeit auszuwerten liefert also in beide Richtungen die falsche Antwort
Die Liste trägt, was dafür nötig ist: Jeder Dienstdatensatz hat einen Statusbeginn und ein Flag, das historische Einträge von aktuellen unterscheidet, und der Evaluator kombiniert sie gegen die Zeit, die Sie liefern. In der Praxis stammt diese Zeit aus einem vertrauenswürdigen Zeitstempel auf der Signatur statt aus der im CMS behaupteten Signaturzeit, weshalb Langzeit-Validierungsmaterial selbst für eine Frage zählt, die wie eine Richtlinien-Nachschlagearbeit aussieht; die Zeitstempel- und DSS-Seite behandelt der Artikel zu Langzeitsignaturen
Was Sie noch bauen müssen
Drei Dinge, und keines davon gehört in eine PDF-Bibliothek. Den Authenticator, also eine echte XMLDSIG-Verifikation gegen ein List-Signaturzertifikat, das Sie über einen Kanal beschafft haben, dem Sie vertrauen. Die Abruf-Richtlinie, also wie und wie oft Sie auffrischen und was Ihre Anwendung tut, wenn eine Auffrischung scheitert. Und den territorialen Umfang, also welche Listen Sie überhaupt mitführen, was eine Geschäftsentscheidung darüber ist, in welchen Mitgliedstaaten Ihre Gegenparteien signieren
Was Sie von der Komponente bekommen, ist der Teil, der subtil schiefgehen kann: die Authenticate-before-parse-Reihenfolge, begrenztes und entity-freies XML-Parsing, monotone Update-Invarianten, exaktes DER-Dienst-Matching, historische Statusauswertung und ein Ergebnis, das getrennt vom gewöhnlichen Kettenvertrauen bleibt. Ist Ihr unmittelbares Problem grundsätzlicher, dass ein Validator eine Signatur zurückweist, die Sie für in Ordnung halten, sind die üblichen Ursachen katalogisiert in warum Validatoren PAdES-Signaturen zurückweisen, und die Signaturinspektions-Oberfläche beschreibt Signaturen und PAdES-Stufen inspizieren. Komponentenfähigkeiten listet die Produktseite der PDFium Delphi-Komponente