Stwierdzenie, że podpis PDF jest kwalifikowany według eIDAS, znaczy odpowiedzenie na pytanie, które nie ma nic do rzeczy z kryptografią: czy certyfikat został wystawiony przez usługę zaufania wpisaną przez państwo członkowskie jako kwalifikowana w chwili złożenia podpisu. Odpowiedź mieszka na liście zaufanej, dokumencie XML publikowanym per terytorium, i cała wartość tego dokumentu zależy od jego autentyczności. Komponent PDFium odmawia więc zajrzenia do środka, dopóki ktoś się za nią nie poręczy. TPdfEuropeanTrustedList.ParseAuthenticated wręcza kompletne surowe bajty dostarczonemu przez wywołującego IPdfTrustedListAuthenticator, zanim sparsuje jedną usługę, i tworzy migawkę tylko wtedy, gdy ten uwierzytelniający jawnie przepuści
Ta kolejność jest projektem. Wszystko inne w tej funkcji wynika z niej, łącznie z częściami, które wyglądają na niewygodne
Sparsowane to nie zaufane
Lista zaufana parsująca się czysto mówi ci, że XML jest dobrze uformowany. Nie mówi nic o tym, kto ją napisał. Skoro lista jest tym, na czym opiera się cała decyzja o statusie kwalifikowanym, przyjęcie jej dlatego, że się sparsowała, uczyniłoby decyzję bezsensowną: atakujący potrafiący podmienić listę może ogłosić własny urząd certyfikacji kwalifikowanym
To samo rozumowanie dotyczy buforowania i to jest pułapka warta nazwania. Cache migawek przechowuje oryginalny XML razem z haszem SHA-256 i łatwo byłoby traktować zgodny hasz przy wczytywaniu jako dowód, że lista jest autentyczna. Nie jest. Hasz liczony przez ten sam proces, który zapisał plik, bez udziału klucza, dowodzi tylko, że bajty nie zmieniły się od ich zapisania; jeśli lista była fałszywa w chwili zbuforowania, hasz potwierdza, że to ta sama fałszywa lista. Wczytywanie zbuforowanej migawki przechodzi więc przez ten sam uwierzytelniający co parsowanie świeżej. Integralność i autentyczność to różne własności i tylko jedna z nich potrzebuje klucza
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
// Twoja polityka mieszka tutaj: zweryfikuj otoczoną sygnaturę XMLDSIG
// względem przypiętego pozapasmowo certyfikatu podpisującego listę
// i opisz, co zostało sprawdzone, dla ścieżki audytu
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);
// Migawka istnieje tylko dlatego, że uwierzytelniający powiedział tak
Cache := TFileStream.Create('tl-de.snapshot', fmCreate);
try
List.SaveCache(Cache);
finally
Cache.Free;
end;
end;
Walidator nie posiada polityki sieciowej
Walidatorowi PAdES nie wypada decydować, jak dotrzeć do listy list zaufanych, czy iść przez proxy, jak często ponawiać ani co robić, gdy terytorium jest nieosiągalne. To są decyzje aplikacji i wdrożenia, a w środowiskach regulowanych bywają często audytowane. Aktualizacje przychodzą więc przez IPdfTrustedListSource, któremu wręcza się URI i limit bajtów i który zwraca bajty
To, co komponent faktycznie egzekwuje, to niezmienniki czyniące z aktualizacji aktualizację, a nie podmianę. Update wymaga, by terytorium było niezmienione, by numer sekwencyjny ściśle rósł i by czas wydania nie cofał się. Te trzy kontrole łamią najbardziej oczywiste ataki obniżające: odtworzenie starszej listy, która wciąż wypisuje od tego czasu wycofaną usługę, albo wstawienie listy innego terytorium, której usługom nigdy nie zamierzano ufać
Limity parsera i zero DTD
TPdfTrustedListOptions sufituje rozmiar XML, liczbę tokenów, głębokość zagnieżdżenia, liczbę usług, liczbę certyfikatów i rozmiar pojedynczego certyfikatu, a klasowa funkcja Default dostarcza używalnych wartości. Listy zaufane to publikowane dokumenty przewidywalnego rozmiaru, więc granice są tanie w ustawieniu i nie ma legalnej listy, która musiałaby je przekroczyć
Osobno i bezwarunkowo parser odrzuca deklaracje DTD i encji. To zamyka jedną odmową i rozszerzanie encji jako denial of service, i drogę ujawnienia przez encje zewnętrzne, i nic nie kosztuje, bo listy zaufane nie używają encji. Każdy parser XML osiągalny z niezaufanego wejścia powinien być tak skonfigurowany; różnica polega na tym, że ta odmowa nie jest konfigurowalna, więc nie da się jej wyłączyć dobrze intencjonowaną zmianą opcji
Status kwalifikowany jest zapisywany obok zaufania łańcucha, nie scalany z nim
Strona ewaluacji jest celowo osobna. TPadesTrustValidationOptions.QualifiedTrustEvaluator przyjmuje IPdfQualifiedTrustEvaluator, który migawka listy zaufanej implementuje. Podczas walidacji ewaluator dostaje certyfikat liścia, łańcuch i czas walidacji, dopasowuje certyfikaty usług dokładnym porównaniem DER względem podpisującego i łańcucha, łączy status usługi, identyfikator typu usługi i URI kwalifikatorów w tym punkcie czasu i zwraca rekord ewaluacji
Wynik ląduje w dwóch miejscach na każdym podpisie: QualifiedTrustStatus jako zgrubny status i QualifiedTrust jako pełna ewaluacja z terytorium, nazwą dostawcy, nazwą usługi, identyfikatorem typu, statusem i czasem rozpoczęcia statusu. Tego, czego nie robi, to zmiana CertificateTrustStatus. Zaufanie łańcucha systemu i status kwalifikowany odpowiadają na różne pytania, a raport, który je scali, nie odróżni „zaufany, ale niekwalifikowany” od „kwalifikowany, ale łańcuch się nie waliduje”, a oba są realne i oba wymagają innego traktowania
var
Options: TPadesTrustValidationOptions;
Report: TPadesValidationResult;
I: Integer;
begin
Options := TPadesTrustValidationOptions.Default;
Options.CheckRevocation := True;
Options.QualifiedTrustEvaluator := List; // uwierzytelniona migawka
Options.QualifiedValidationTime := SigningTime; // nie 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
// Brak pasującej usługi albo migawka nie odpowiada za ten czas
Writeln(Format('signature %d: qualified status undetermined', [I]));
end;
Czemu czas walidacji to nie teraz
Bo kwalifikacja jest własnością momentu. Usłudze zaufania można przyznać status kwalifikowany, później go wycofać, a jeszcze później przywrócić, i każde z tych przejść niesie czas rozpoczęcia na liście. Podpis złożony, gdy usługa była kwalifikowana, pozostaje kwalifikowany potem; podpis złożony przed przyznaniem nie staje się kwalifikowany wstecz. Ewaluowanie względem bieżącego czasu daje więc złą odpowiedź w obu kierunkach
Lista niesie to, czego to potrzebuje: każdy rekord usługi ma czas rozpoczęcia statusu i flagę odróżniającą wpisy historyczne od bieżących, a ewaluator łączy je względem czasu, który dostarczysz. W praktyce ten czas pochodzi z zaufanego znacznika czasu na podpisie, a nie z czasu podpisania deklarowanego w CMS, dlatego materiał walidacji długoterminowej ma znaczenie nawet dla pytania wyglądającego na odpytanie polityki; stronę znacznika czasu i DSS omawia artykuł o podpisach długoterminowych
Co wciąż musisz zbudować
Trzy rzeczy i żadna z nich nie należy do biblioteki PDF. Uwierzytelniający, czyli faktyczna weryfikacja XMLDSIG względem certyfikatu podpisującego listę, który pozyskano kanałem godnym zaufania. Polityka pobierania, czyli jak i jak często odświeżasz oraz co robi twoja aplikacja, gdy odświeżenie zawiedzie. I zakres terytorialny, czyli które listy w ogóle nosisz, co jest decyzją biznesową o tym, w których państwach członkowskich podpisują twoi kontrahenci
To, co dostajesz od komponentu, to część, którą łatwo subtelnie zepsuć: kolejność uwierzytelnij-przed-sparsowaniem, ograniczone i wolne od encji parsowanie XML, monotoniczne niezmienniki aktualizacji, dopasowanie usług dokładnym DER, ewaluacja statusu historycznego i wynik pozostający osobny od zwykłego zaufania łańcucha. Jeśli twój bezpośredni problem jest bardziej podstawowy, że walidator odrzuca podpis, który uważasz za poprawny, typowe przyczyny są skatalogowane w czemu walidatory odrzucają podpisy PAdES, a powierzchnia inspekcji podpisów jest opisana w inspekcji podpisów i poziomów PAdES. Możliwości komponentu są wypisane na stronie produktu PDFium Delphi component