Artykuł techniczny

Klasyfikacja zmian w PDF po podpisaniu dokumentu

Podpis nałożony na PDF nie zabrania późniejszych zmian. Fiksuje zakres bajtów, a aktualizacja przyrostowa dokleja nowe bajty po nim, więc podpis pozostaje matematycznie ważny, podczas gdy dokument nabywa nową treść. To, czy ta treść jest akceptowalna, jest pytaniem o politykę, a DocMDP jest miejscem, gdzie autor tę politykę zapisuje: żadnych zmian w ogóle, tylko wypełnianie formularzy i podpisywanie, albo to plus adnotacje. Egzekwowanie jej oznacza klasyfikowanie tego, co faktycznie się zmieniło, i to robi AnalyzeModifications. Skieruj ją na wcześniejszą rewizję, a potem odczytaj GetModificationLevel dla werdyktu ogólnego oraz akcesory per znalezisko dla poziomu, numeru obiektu i opisu każdej różnicy

Gdy to jest na miejscu, egzekwowanie DocMDP zapada się do porównania: czy obliczony poziom jest równy poziomowi dozwolonemu przez politykę albo poniżej niego

Diagram drabiny poziomów modyfikacji PDFlibPas od mlNone do mlUnclassified pokazujący egzekwowanie polityki DocMDP jako jedno porównanie w Delphi
Drabina TPLModificationLevel biegnie od mlNone do mlUnclassified, a egzekwowanie DocMDP redukuje się do porównania obliczonego poziomu z polityką

Dlaczego podpisany PDF ma prawo się zmieniać

Trzy legalne przypadki i pokrywają większość tego, co zobaczysz. Drugi podpisujący dokłada swój podpis. Odbiorca wypełnia pola formularza, które autor zostawił otwarte. I doklejany jest materiał walidacji długoterminowej: odpowiedzi OCSP i listy CRL zapisywane do magazynu zabezpieczeń dokumentu, żeby podpis pozostał weryfikowalny, gdy respondenci już nie istnieją. Ten ostatni nie tylko jest dozwolony — to dokładnie to, co dobrze prowadzone archiwum celowo robi z podpisanymi dokumentami

Więc „plik urośł po podpisaniu” nie niesie żadnej informacji. Pytanie zawsze brzmi, co zostało dodane, a odpowiedź musi pochodzić z porównania stanów dokumentu, a nie z obserwowania bajtów. Mechanikę doklejania samej w sobie opisuje artykuł o aktualizacji przyrostowej

Klasyfikuj po kształcie obiektu, nie po ścieżce, która go stworzyła

Klasyfikator patrzy na to, czym obiekt jest po zmianie, a nie na to, które wywołanie biblioteki go stworzyło. To zamierzone, bo analiza działa na plikach wyprodukowanych przez inne oprogramowanie, gdzie żadna ścieżka wywołań nie jest dostępna do zbadania

Rozpoznawane są cztery kształty. Słowniki informacji magazynu zabezpieczeń dokumentu i danych walidacyjnych, obiekty strumienia odwołań krzyżowych, wpis metadanych katalogu oraz słowniki podpisów niosące zakres bajtów to materiał archiwum długoterminowego. Obiekt niosący jednocześnie typ pola i wartość pola to wypełnianie formularza. Obiekt, którego typem jest adnotacja albo którego podtyp jest jednym z wymienionych w tabeli 168 ISO 32000-2, to zmiana adnotacyjna. Wszystko inne jest niesklasyfikowane

Drzewo decyzyjne, które PDFlibPas stosuje do każdego zmienionego obiektu PDF, sortując aktualizacje na poziomy archiwum, wypełniania formularzy, adnotacji lub niesklasyfikowane
Każdy zmieniony obiekt jest klasyfikowany po tym, czym jest — magazyn zabezpieczeń, strumień xref, pole, adnotacja — nigdy po wywołaniu, które go stworzyło

Usunięcia traktuje się surowiej niż dodatki. Usunięty obiekt trafia na białą listę tylko wtedy, gdy obiekt po starej stronie był sam w sobie materiałem archiwalnym, co obejmuje normalny przypadek wymiany magazynu zabezpieczeń na nowszy. Każde inne usunięcie jest niesklasyfikowane, bo usuwanie treści z podpisanego dokumentu to nie jest coś, co poziom uprawnień autoryzuje. Różnice na poziomie dokumentu są surowsze jeszcze: zmiana liczby stron idzie od razu do niesklasyfikowanych bez badania pojedynczych obiektów, skoro żaden poziom DocMDP nie zezwala na dodawanie ani usuwanie stron

Biała lista myli się w stronę odmowy

To jest reguła projektowa rządząca każdą decyzją na granicy. Zmiana błędnie sklasyfikowana jako dozwolona to podpis, który waliduje się nad treścią, której autor nigdy nie autoryzował. Zmiana błędnie sklasyfikowana jako niesklasyfikowana to dokument, który zostaje oflagowany i przejrzany przez człowieka. Te dwa błędy nie są symetryczne, więc biała lista pozostaje wąska, a nierozpoznane kształty spadają do niesklasyfikowanych, zamiast być zgadywane

Ma to praktyczną konsekwencję wartą przewidzenia: pliki od nietypowych producentów będą czasem zgłaszać niesklasyfikowane zmiany, które po zbadaniu są łagodne. Właściwą reakcją jest spojrzenie na szczegół znaleziska i numer obiektu, a nie poszerzanie białej listy, bo biała lista rosnąca po to, by uciszać pojedyncze zgłoszenia, przestaje być zabezpieczeniem

uses
  PDFlibrary, PDFlibCompare;

var
  Pdf: TPDFlib;
  I, Level: Integer;
begin
  Pdf := TPDFlib.Create(nil);
  try
    Pdf.LoadFromFile('contract-countersigned.pdf', '');
    if Pdf.AnalyzeModifications('contract-as-signed.pdf', '') < 0 then
      raise Exception.Create('the earlier revision could not be loaded');

    // TPLModificationLevel w kolejności mlNone, mlLTAUpdates, mlFormFilling,
    // mlAnnotations, mlUnclassified; getter zwraca jej wartość porządkową
    Level := Pdf.GetModificationLevel;
    // Egzekwowanie DocMDP to teraz jedno porównanie z polityką
    if Level > Ord(mlFormFilling) then
      for I := 0 to Pdf.GetModificationFindingCount - 1 do
        Report.Add(Format('object %d, level %d: %s',
          [Pdf.GetModificationFindingObjNum(I),
           Pdf.GetModificationFindingLevel(I),
           Pdf.GetModificationFindingDetail(I)]));
  finally
    Pdf.Free;
  end;
end;

Poziom ogólny to maksimum po wszystkich znaleziskach, i to jedyne dające się obronić agregowanie: dokument zawierający dziewięćdziesiąt dziewięć archiwalnych dodatków i jedną niesklasyfikowaną zmianę jest dokumentem z niesklasyfikowaną zmianą

Pod spodem: odciski, nie kryptograficzne skróty

Silnik porównawczy, który wystawia CompareWith i na którym zbudowana jest analiza modyfikacji, identyfikuje obiekty po odcisku ich znormalizowanego ciała, używając niekryptograficznego 64-bitowego hasza zamiast SHA-256. To przemyślany wybór. Strukturalne porównanie potrzebuje determinizmu: to samo ciało obiektu musi zawsze produkować ten sam odcisk w ramach jednego uruchomienia. Nie potrzebuje odporności na kolizje, bo atakujący kontrolujący obie strony porównania już wygrał innymi środkami, a płacenie pełnym kryptograficznym haszem za każdy obiekt w dokumencie o milionie obiektów to realny koszt bez żadnej korzyści

Dwie reguły normalizacji znaczą więcej niż wybór hasza. Referencje pośrednie zwijają się do tokenu zastępczego zamiast być rozwijane do wskazywanej treści: rozwijanie kopiowałoby ciało współdzielonego obiektu do każdego odwołującego, więc drobna edycja współdzielonego deskryptora fontu unieważniłaby odcisk każdego obiektu, który do niego sięga, a raport byłby nieczytelny. Numery obiektów same są wyłączone z odcisku, bo przepisanie może przenumerować obiekty, nie zmieniając niczego semantycznie

Dopasowywanie biegnie potem w dwóch przebiegach, najpierw wyrównując po odcisku, a resztę parując po numerze obiektu, żeby rozpoznać zmiany, a nie dodatek plus usunięcie. Tanich sprawdzeń używa się najpierw na całej linii: różnica liczby stron jest zgłaszana, zanim jakiekolwiek przechodzenie po obiektach się zacznie

Dwuprzebiegowy diff rewizji PDF w PDFlibPas: najpierw sprawdzenie liczby stron, 64-bitowe odciski, wyrównanie po odciskach, potem parowanie po numerach obiektów
Silnik porównawczy liczy odciski znormalizowanych ciał obiektów, najpierw zgłasza różnice liczby stron, potem dopasowuje po odcisku i numerze obiektu

Pułapka: porównanie ze sobą nie musi dać identyczności

Naturalnym pierwszym testem dla silnika diff jest porównanie pliku z nim samym i asercja, że wynik jest identyczny. Ta asercja tutaj nie obowiązuje, a powód jest pouczający. Publiczna ścieżka wczytywania i ścieżka wczytywania dokumentu niższego poziomu nie konfigurują dekodowania identycznie, więc ten sam plik wczytany dwiema drogami może dać odciski różniące się dla niektórych obiektów. Silnik się nie myli; dwa wczytania naprawdę wyprodukowały różne stany w pamięci

Zamiast zmuszać obie ścieżki do zguły, semantyka porównania jest sformułowana wąsko: analiza porównuje bieżący stan dokumentu z wcześniejszą rewizją i zgłasza identyczność tylko wtedy, gdy oba zbiory odcisków pokrywają się dokładnie. To jest pytanie, które użytkownicy faktycznie zadają, i nie wymaga, by oba wczytywania były wymienne. Projektując funkcję porównywania, zdefiniowanie, co znaczy „to samo”, to większa część roboty niż jej obliczenie

Gdzie tego używać

Dwa miejsca. W raporcie walidacji, obok sprawdzania podpisów, żeby recenzent widział nie tylko to, czy podpis jest kryptograficznie nietknięty, ale i to, co stało się z dokumentem potem; stronę podpisową omawia podpisywanie i walidacja PAdES. I w bramce przyjęcia, gdzie dokument przychodzący z zewnątrz jest sprawdzany względem wysłanej kopii, więc zwrócony kontrakt z dodaną adnotacją jest traktowany inaczej niż taki z zedytowaną stroną

Jedno zastrzeżenie co do zakresu. Ta analiza mówi ci, co zmieniło się między dwiema rewizjami tego samego rodowodu dokumentu. Nie mówi, czy widoczna treść jest myląca, czy strumień wyglądu pola formularza zgadza się z jego wartością ani czy tekst schowany pod nakładką wciąż jest obecny w strumieniu zawartości. To wymaga osobnego traktowania, a stronę usuwania treści omawia artykuł o prawdziwej redakcji. Punkty wejścia analizy i porównywania są udokumentowane na stronie produktu losLab PDF Developer Library