PDFlibPas odpowiada na pytanie FieldMDP przez TPDFlib.GetSignatureModificationVerdict. Dla podpisanego PDF-a, jego hasła i nazwy pola podpisu metoda przechodzi przez każdą aktualizację przyrostową dołączoną po tym podpisie, rozwiązuje w pełni kwalifikowaną nazwę każdego pola formularza, którego dotknęły te aktualizacje, i zwraca 3, gdy zmieniono pole zablokowane przez FieldMDP, a 2, gdy tylko pola niezablokowane. GetSignatureModificationDetail podaje potem rewizję i pole
Tego typu sprawdzenia wymaga formularz z wieloma akceptującymi. Zamawiający wypełnia Order.Customer i Order.Total w zapotrzebowaniu i podpisuje Approval.Requester, blokując te dwa pola. Kierownik dopisuje centrum kosztów i podpisuje Approval.Manager. Dwa tygodnie później ktoś otwiera plik w edytorze desktopowym i w trzeciej aktualizacji przyrostowej podkrada Order.Total do góry. Każdy podpis dalej się weryfikuje, bo każdy obejmuje wyłącznie własny zakres bajtów. Pytania, które faktycznie zadaje audytor, to które pole zmieniło się po którym podpisie i czy ta zmiana była dozwolona
Dlaczego samo DocMDP nie wykryje edycji zablokowanego pola?
DocMDP tego nie wykryje, bo DocMDP klasyfikuje rodzaje zmian, a nie pojedyncze pola. Poziomy uprawnień certyfikacji w ISO 32000-1 §12.8.2.2 mówią, czy późniejsze aktualizacje mogą wypełniać formularze i podpisywać (P 2), czy też także adnotować (P 3), i to uprawnienie dotyczy każdego pola w dokumencie równo. Edycja Order.Total to wypełnianie formularza, więc certyfikacja P 2 jest zadowolona dokładnie tą edycją, której zamawiający chciał zabronić. klasyfikacja modyfikacji na poziomie dokumentu w PDFlibPas pokrywa tamtą warstwę, a ten artykuł schodzi o stopień niżej
FieldMDP to warstwa o granulacji pojedynczego pola. Pole podpisu może nieść słownik /Lock (ISO 32000-1 §12.7.4.5), a trzymany w nim podpis może nieść transformację FieldMDP w swojej referencji podpisu (ISO 32000-1 §12.8.2.4). Oba używają tego samego słownika: /Action /All blokuje każde pole, /Action /Include blokuje nazwy wypisane w /Fields, a /Action /Exclude blokuje wszystko poza nazwami w /Fields. Przed v3.586.0 GetSignatureModificationVerdict wiedział, że późniejsza rewizja zmieniła pole formularza, ale nie które, więc każdy dokument z blokadą pola, którego późniejsze aktualizacje wychodziły poza materiał walidacyjny, wracał jako 4, nieokreślone. Mapa rewizji zapisuje teraz rozwiązaną nazwę pola dla każdego sklasyfikowanego obiektu związanego z polami i właśnie to zamienia tę 4 w prawdziwe 2 albo 3
Zapisywanie blokady pola przy podpisie każdego akceptującego
TPDFlib.SetSignProcessFieldLock ustawia blokadę dla następnego podpisu, jaki produkuje proces podpisywania. Akcja 1 to All, 2 to Include, 3 to Exclude, a 0 czyści blokadę; Include i Exclude przyjmują listę w pełni kwalifikowanych nazw pól rozdzielonych średnikami. Podpisywane pole kończy ze słownikiem /Lock i pasującą transformacją FieldMDP, czyli parą, której oczekują surowi walidatorzy
var
Pdf: TPDFlib;
SignId: Integer;
begin
Pdf := TPDFlib.Create;
try
SignId := Pdf.NewSignProcessFromFile('purchase-request.pdf', '');
if SignId = 0 then
raise Exception.Create('cannot open the form');
try
Pdf.SetSignProcessField(SignId, 'Approval.Requester');
Pdf.SetSignProcessPFXFromFile(SignId, 'requester.pfx', PfxPassword);
// 2 = Include: ten podpis zamraża tylko te pola
if Pdf.SetSignProcessFieldLock(SignId, 2,
'Order.Customer;Order.Total') <> 1 then
raise Exception.Create('field lock rejected');
Pdf.EndSignProcessToFile(SignId, 'purchase-request-signed.pdf');
if Pdf.GetSignProcessResult(SignId) <> 1 then
raise Exception.CreateFmt('signing failed, code %d',
[Pdf.GetSignProcessResult(SignId)]);
finally
Pdf.ReleaseSignProcess(SignId);
end;
finally
Pdf.Free;
end;
end;
SetSignProcessFieldLock zwraca 0 dla wywołania Include albo Exclude bez nazw, a także dla listy zawierającej pusty wpis, jak 'Order.Customer;;Order.Total'; odrzucone wywołanie zostawia wcześniej skonfigurowaną blokadę, więc sprawdzaj wartość zwracaną, zamiast zakładać, że ostatnie wywołanie przeszło. Resztę konfiguracji podpisywania, wybór skrótu, rezerwację /Contents i kody wyników opisuje przewodnik po podpisywaniu i walidacji PAdES w PDFlibPas. Jeden punkt projektowy jest specyficzny dla łańcuchów akceptacji: blokada /All na podpisie zamawiającego zamraża też Approval.Manager, bo pole podpisu też jest polem formularza. Użyj Include dla pól danych należących do danego akceptującego albo Exclude z wypisanymi późniejszymi polami podpisu
Przeprowadzanie werdyktu wzdłuż łańcucha akceptacji
GetSignatureModificationVerdict ocenia jeden podpis naraz, więc łańcuch akceptacji sprawdza się, wywołując go raz na każde pole podpisu. Wartość zwracana to mały, zamknięty zbiór, a każda wartość znaczy dla recenzenta coś innego:
- 0: pliku albo wskazanego pola podpisu nie dało się odczytać
- 1: po podpisie nic nie dołączono
- 2: dołączono tylko zmiany, na które pozwalają efektywne uprawnienia, i nie dotknięto żadnego zablokowanego pola
- 3: późniejsza rewizja wprowadziła zmianę niedozwoloną, łącznie z edycją pola blokowanego przez FieldMDP
- 4: odpowiedzi nie dało się ustalić, czego nigdy nie wolno czytać jako akceptacji
- 5: podpis nie kończy się na granicy rewizji, więc bajty leżą wewnątrz zakresu, który ma obejmować
const
Approvals: array[0..2] of WideString =
('Approval.Requester', 'Approval.Manager', 'Approval.Finance');
var
Pdf: TPDFlib;
I, Verdict: Integer;
begin
Pdf := TPDFlib.Create;
try
for I := Low(Approvals) to High(Approvals) do
begin
Verdict := Pdf.GetSignatureModificationVerdict(
'purchase-request-final.pdf', FormPassword, Approvals[I]);
// oba gettery opisują dopiero co wykonane wywołanie, więc czytaj je przed następnym
Log.Add(Format('%s: verdict %d, MDP level %d, %s',
[Approvals[I], Verdict, Pdf.GetSignatureEffectiveMDPLevel,
Pdf.GetSignatureModificationDetail]));
case Verdict of
1, 2: ; // zaakceptowane
3: RejectForm(Approvals[I]); // detail podaje pole i rewizję
else
QueueForManualReview(Approvals[I]); // 0, 4, 5
end;
end;
finally
Pdf.Free;
end;
end;
Weź podmienione zapotrzebowanie, gdzie rewizja 0 to pusty formularz, rewizje 1 i 2 to dwa podpisy, a rewizja 3 to ta edycja. Ponieważ definicje blokad są składane do wspólnej puli ze wszystkich podpisanych pól, werdykt dla Approval.Manager już egzekwuje blokadę zamawiającego, a jego jedyną późniejszą aktualizacją jest sama edycja. Gdy ta aktualizacja przepisała tylko słownik pola, werdykt to 3, a detail czyta Revision 3 changed locked form field "Order.Total", which FieldMDP does not allow. Ta jedna linia odpowiada na oba pytania audytowe. Werdykt dla Approval.Requester musi najpierw przejść przez podpisanie rewizji przez kierownika i z powodów z sekcji ograniczeń niżej często zgłosi 4, więc najświeższy podpis zwykle daje najciośniejszą odpowiedź. GetSignatureEffectiveMDPLevel raportuje najniższą wartość P DocMDP znalezioną wśród podpisów albo 3, gdy nic nie certyfikowało dokumentu, a oba gettery pozostają ważne tylko do następnego wywołania werdyktu
Skąd PDFlibPas wie, które pole zmieniła rewizja?
PDFlibPas rekonstruuje nazwę pola z grafu obiektów każdej rewizji, bo zmieniony obiekt rzadko nosi swoją pełną nazwę. Słownik pola z /FT albo /V, czy adnotacja widgetu, trzyma w /T tylko swoją nazwę cząstkową. W pełni kwalifikowana nazwa opisana w ISO 32000-1 §12.7.3.2 to łańcuch nazw cząstkowych od korzenia, sklejonych kropkami, więc mapa rewizji wspina się w górę po łączach /Parent i przedkłada każde znalezione /T. Każdy rodzic jest czytany z jego najnowszej definicji w klasyfikowanej rewizji albo wcześniejszej, co chroni historię wcześniejszych edycji przed przepisaniem przez późniejszą zmianę nazwy węzła nadrzędnego
Dopasowanie rozwiązanego nazwiska do blokad jest celowo proste. Nazwa pasuje do wpisu /Fields, gdy równa się mu dokładnie albo zaczyna się od wpisu zakończonego kropką, więc zablokowanie Order blokuje też Order.Total i każdego innego potomka. All blokuje każdą nazwę, Include blokuje pasujące nazwy, a Exclude blokuje każdą nazwę, która nie pasuje. Definicje blokad pochodzą z każdego podpisanego pola podpisu w pliku; gdy pole ma i słownik /Lock, i transformację FieldMDP, używane są wartości /Lock. Żeby zobaczyć dokładnie, co zablokował każdy podpis, niższa klasa TPDFlibSignDoc z modułu PDFlibDigitalSign wystawia raport uprawnień, z którego korzysta werdykt:
uses
PDFlibDigitalSign;
var
SignDoc: TPDFlibSignDoc;
Report: TPDFlibSignaturePermissionReport;
Names: TStringList;
I, J: Integer;
Line: string;
begin
SignDoc := TPDFlibSignDoc.Create;
Names := TStringList.Create;
try
if not SignDoc.Open('purchase-request-final.pdf', FormPassword, False) then
Exit;
try
SignDoc.GetSignatureFieldNames(Names);
for I := 0 to Names.Count - 1 do
begin
SignDoc.GetSignaturePermissionInfoByName(Names[I], Report);
if not (Report.HasFieldLock or Report.HasFieldMDP) then
Continue;
Line := Names[I] + ': ' + string(Report.FieldLockAction);
for J := 0 to High(Report.FieldNames) do
Line := Line + ' ' + string(Report.FieldNames[J]);
if Report.HasFieldLock and Report.HasFieldMDP and
not Report.FieldMDPMatches then
Line := Line + ' (lock and FieldMDP transform disagree)';
Log.Add(Line);
end;
finally
SignDoc.Close;
end;
finally
Names.Free;
SignDoc.Free;
end;
end;
Formularze szyfrowane: podaj hasło, bo inaczej nazwy są nieczytelne
Szyfrowany formularz trzeba odszyfrować, zanim da się rozwiązać jakąkolwiek nazwę pola, więc GetSignatureModificationVerdict potrzebuje argumentu Password do czegoś więcej niż otwarcia pliku. Napisy takie jak /T są szyfrowane per obiekt, a pól zapisanych do skompresowanych strumieni obiektów nie da się w ogóle czytać, dopóki zawierający je strumień nie zostanie odszyfrowany i zdekompresowany. Od v3.586.0 wewnętrzna mapa rewizji przyjmuje hasło, dołącza kontekst deszyfrowania podczas parsowania strumieni obiektów i słowników rewizji oraz odszyfrowuje każdy dekodowany obiekt, łącznie z wcześniejszymi wersjami, z którymi robi diff, i rodzicami, po których wspina się
Deszyfrowanie per obiekt w mapie rewizji włącza się tylko, gdy Password jest niepuste. Werdykt policzony na zaszyfrowanym pliku z pustym hasłem traktuj więc jako niezweryfikowany, nawet jeśli dokument sam otworzył się bez odpytywania. Trzymaj hasło formularza obok rekordu procesu i przekazuj je przy każdym wywołaniu
Czego werdykt FieldMDP nie obejmuje
Sprawdzenie pól to strukturalna odpowiedź na jedno wąskie pytanie, a kilka przypadków wypada poza nim. Wiedza o nich z wyprzedzeniem sprawia, że 4 nie zamienia się w eskalację na support:
- Porównanie pól rusza dopiero po czystym wyniku sprawdzenia poziomu dokumentu i gdy co najmniej jedna późniejsza rewizja zmieniła coś więcej niż materiał walidacyjny albo znaczniki czasu; naruszenie DocMDP jest zgłaszane jako 3, zanim w ogóle spojrzy się na jakiekolwiek pole
- Obiekty sklasyfikowane jako związane z podpisem, ale nieniosące nazwy pola, jak słownik wartości podpisu, przepisany słownik AcroForm albo zmiana katalogu ograniczona do
/AcroFormlub/Perms, każą werdyktowi zwrócić 4 zamiast zgadywać, więc rewizja dodająca podpis kolejnej akceptacji przy aktywnej blokadzie często ląduje na 4 - Nazwę pola dostają tylko słowniki pól i adnotacje widgetów; mapa rewizji nie ma osobnej klasy dla strumieni wyglądu, więc edytor regenerujący wyglądy razem z wartością może zostać zatrzymany przez sprawdzenie poziomu dokumentu jako zmiana treści, zanim ruszy sprawdzenie pól, a napis detail odróżnia oba przypadki
- Skan zatrzymuje się na pierwszym napotkanym zablokowanym polu albo wpisie bez nazwy, więc 3 wskazuje jedno winne pole, a nie wszystkie
- Blokady są składane do puli z każdego podpisanego pola podpisu bez porządkowania ich w czasie, więc blokada zapisana przez późniejszego podpisującego jest stosowana także do aktualizacji sprzed niej; to może tylko pchnąć wynik w stronę 3, nigdy w stronę 2
- Nazwy są porównywane dokładnie i z rozróżnianiem wielkości liter, a wspinaczka po
/Parentkończy się po ośmiu poziomach, więc nietypowo głęboka hierarchia rozwiązuje się do skróconej nazwy - Wpis FieldMDP, którego struktura się nie waliduje i którego akcji nie da się odczytać, każe werdyktowi zwrócić 4
Gdy werdykt wróci jako 4, GetSignatureRevisionIndex podaje rewizję, na której kończy się podpis, a GetDocumentRevisionChanges wypisuje każdy obiekt dodany, zmodyfikowany albo zwolniony przez każdą późniejszą rewizję, z jej klasą zmiany. To wystarcza człowiekowi na decyzję w minutę. Mechanikę dołączania tych rewizji opisuje przewodnik po aktualizacjach przyrostowych dla PDFlibPas
FAQ
Czy werdykt 2 znaczy, że podpisy są ważne?
Nie. GetSignatureModificationVerdict bada, co dołączono po podpisie, i nigdy nie sprawdza wartości podpisu CMS, łańcucha certyfikatów ani rewokacji. Dwójka mówi, że późniejsze aktualizacje zmieściły się w uprawnieniach; walidacja kryptograficzna to osobny krok i potrzebne są oba
Dlaczego podpis drugiego akceptującego daje 4 zamiast 2?
Podpisanie dołącza słownik wartości podpisu i zwykle przepisuje księgowość formularza, jak słownik AcroForm. Te obiekty nie należą do żadnego nazwanego pola, a gdy blokada pola jest aktywna, werdykt odmawia przepuszczenia zmiany, której nie da się przypisać polu. Użyj GetDocumentRevisionChanges, żeby potwierdzić, że rewizja zawiera tylko oczekiwane obiekty podpisu
Czy jedna nazwa zablokuje całą grupę pól?
Tak. Wpis blokady pasuje do wskazanego pola i każdego potomka, którego w pełni kwalifikowana nazwa zaczyna się od niego zakończonego kropką, więc wypisanie Order w blokadzie Include zamraża Order.Customer, Order.Total i każde inne pole pod tym rodzicem
Werdykty FieldMDP, podpisywanie z blokadą pól i inspekcję rewizji znajdziesz w PDFlibPas Delphi PDF Library, razem z certyfikacją DocMDP i długoterminową walidacją PAdES, na których opiera się powyższy proces akceptacji