Artykuł techniczny

Dowody LTV PAdES i wartości seed w HotPDF

Świeżo podpisany PDF to podpis B-B i nic więcej. Dowodzi, kto podpisał i że bajty się nie ruszyły, ale nie niesie dowodu, że certyfikat podpisującego był ważny w chwili podpisu, więc walidator za lata musi szukać danych o unieważnieniach, które mogą już nie istnieć. Zasypanie tej luki znaczy zapisanie odpowiedzi OCSP i list CRL do poziomu dokumentu, w Document Security Store, a w HotPDF to jedno wywołanie: PopulatePAdESLTVEvidence przechodzi przez każdy wczytany podpis, wyprowadza żądania unieważnień ze zbioru certyfikatów, wykonuje je przez transport, który dostarczysz, i zapisuje pobrany materiał plus łańcuch CMS do DSS. Zwraca liczbę podpisów, których dowody wylądowały, albo minus jeden, gdy dokument w ogóle nie ma pola podpisu

Decyzją projektową wartą zrozumienia przed użyciem jest to, że biblioteka nigdy nie otwiera gniazda. Każdy bajt przybywający z sieci przybywa przez callback, który napiszesz. To nie ostrożność sama w sobie; to jedyny sposób, by ta funkcja działała wewnątrz środowisk, które faktycznie wymagają walidacji długoterminowej

Dlaczego biblioteka odmawia własnego HTTP?

Bo miejsca wymagające podpisów B-LT to miejsca, gdzie biblioteki nie wolno ufać z siecią. Usługi podpisywania działają za proxy z uwierzytelnianiem i korporacyjnymi rootami. Odcięte od sieci poziomy podpisywania nie mają trasy do respondenta i trzeba je karmić zbuforowanymi dowodami. Reżimy audytowe wymagają, by każde wychodzące żądanie było logowane przez aplikację, nie zakopane w zależności. I zestawy testowe potrzebują deterministycznych odpowiedzi, co jest niemożliwe, gdy biblioteka sama się wybiera w świat

Transport to zwykła referencja funkcji o stałym kształcie, więc polityka pozostaje twoja. HotPDF wręcza ci rekord żądania opisujący dokładnie, co pobrać, łącznie z typem zawartości i limitem rozmiaru odpowiedzi, a ty zwracasz bajty plus status

Przepływ PopulatePAdESLTVEvidence w HotPDF: transport FetchEvidence dostarczony przez wywołującego, pola rekordu żądania i statusy per podpis
Każdy bajt z sieci przechodzi przez twój callback FetchEvidence, a każdy podpis dostaje własny status, więc jeden timeout nigdy nie przerywa przebiegu
function FetchEvidence(const Request: THPDFSignatureEvidenceRequest;
  Attempt: Integer; CancellationToken: THPDFCancellationToken;
  out Response: TBytes; out RetryAfterMS: Cardinal;
  out ErrorMessage: UnicodeString): THPDFSignatureEvidenceTransportStatus;
begin
  RetryAfterMS := 0;
  try
    // Request.Kind mówi, czy to POST OCSP czy GET CRL;
    // Request.ContentType i Request.Body są już przygotowane,
    // a Request.MaxResponseBytes to limit, który musisz uszanować
    Response := HttpExchange(Request.URI, Request.ContentType,
      Request.Body, Request.MaxResponseBytes);
    Result := setsSucceeded;
  except
    on E: Exception do
    begin
      ErrorMessage := E.Message;
      // setsRetry pozwala polityce ponowień się wycofać; użyj
      // setsPermanentFailure dla 404 albo złego URL-a
      Result := setsRetry;
    end;
  end;
end;

// Aktualizacja B-B do B-LT jednym wywołaniem dla każdego podpisu
// w wczytanym pliku
var
  Pdf: THotPDF;
  Upgraded: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.BeginIncrementalUpdate('signed.pdf');
    Upgraded := Pdf.PopulatePAdESLTVEvidence(FetchEvidence,
      THPDFSignatureEvidenceRetryPolicy.Default);
    if Upgraded > 0 then
      // Zapis tylko-dodający: bajty pokrywane przez istniejące podpisy
      // są zachowane co do znaku
      Pdf.SaveIncrementalUpdate('signed-lt.pdf');
  finally
    Pdf.Free;
  end;
end;

Awarie są per podpis, nie per dokument. Respondent, który przekracza limit czasu dla jednego podpisującego, pomija materiał tego podpisującego i zostawia resztę przebiegu nietkniętą, co jest zachowaniem, jakiego chcesz w batchu: częściowe dowody biją przerwaną próbę, a wartość zwrotna mówi, ile podpisów faktycznie zyskało

Łańcuch, którego CMS zapomniał dołączyć

Sprawdzanie unieważnień potrzebuje certyfikatu wystawcy, a zaskakująco wiele stosów podpisujących pomina pośredników w kontenerze CMS. Drogą naprawczą jest rozszerzenie Authority Information Access, metoda dostępu 1.3.6.1.5.5.7.48.2, która podaje URL, skąd certyfikat wystawcy można pobrać. HPDFFetchAIAIntermediates przechodzi te URL-e przez ten sam transport, wycina DER z każdej odpowiedzi i zwraca tylko te certyfikaty, których CMS już nie niósł, kluczowane haszem DER, żeby duplikaty i pętle nie mogły się kręcić

Dwa szczegóły decydują, czy to działa wobec prawdziwych urzędów certyfikacji. Pierwszy to kodowanie: punkty końcowe CA serwują certyfikat jako goły DER mniej więcej tak często, jak w pancerzu PEM, i nie ma niezawodnego typu zawartości, by je odróżnić. Solidna sonda jest najpierw tekstowa, potem strukturalna. Szukaj znacznika -----BEGIN CERTIFICATE-----, zdejmij pancerz i zdekoduj base64, jeśli jest obecny, a w obu ścieżkach potwierdź, że pierwszy bajt wyniku to $30, tag DER dla SEQUENCE. Drugi to głębokość: pobrany pośrednik może sam podawać URL AIA dla własnego wystawcy, więc przejście dokłada nowych kandydatów do kolejki i domyka łańcuchy krótkie o dwa lub trzy skoki. To trzeba ograniczyć sufitem i do tego służy parametr MaxFetch

Diagram domykania łańcucha AIA w HotPDF: pobieranie URL-i caIssuers, sonda PEM kontra DER, deduplikacja haszem DER i sufit głębokości MaxFetch
HPDFFetchAIAIntermediates przechodzi URL-e caIssuers tym samym transportem, sondując pancerz PEM i ograniczając kolejkę przez MaxFetch

Czym jest wartość seed podpisu i czemu zawodzi po cichu?

Wartość seed to ograniczenie, które autor dokumentu doczepia do pola podpisu, by powiedzieć podpisującemu, jaki rodzaj podpisu jest akceptowalny: jaki SubFilter, jaki algorytm skrótu, jakie powody, jaka minimalna wersja PDF, czy informacje o unieważnieniach muszą być osadzone. Mieszka w słowniku /SV na polu i jest zdefiniowana w ISO 32000-1 §12.7.5.5. HotPDF zapisuje ją przez AttachPAdESSeedValue i sprawdza przez CheckLoadedSignatureSeedValue, która zwraca True, gdy pole jest nieograniczone albo każde obecne ograniczenie przechodzi, a przy False nazywa pierwsze zawodzące ograniczenie przez parametr wyjściowy, który możesz wstawić prosto do komunikatu błędu

Mechanizmem, który sprawia, że wartości seed łatwo zepsuć, jest wpis flag /Ff opisany w §12.7.5.5.3. Ustawiony bit oznacza jego ograniczenie jako wymagane: niezgodność jest błędem, a podpisujący musi odmówić. Zerowany bit oznacza to samo ograniczenie jako preferencję: wartość filtruje, co interfejs powinien oferować, i nic więcej. Płyną z tego dwie pułapki. Po pierwsze, /Ff mieszka wewnątrz słownika /SV, nie na adnotacji widgetu, więc kod czytający /Ff poziomu pola dostaje pustą odpowiedź na zawsze i wnioskuje, że nic nie jest wymuszone. Po drugie, przypisania bitów nie są prostym biegiem jeden, dwa, cztery, osiem; w HotPDF writer emituje 2 dla SubFilter, 4 dla MinVersion, 32 dla AddRevInfo i 64 dla DigestMethod. Czytnik zakładający kolejne bity dekoduje każde ograniczenie jako opcjonalne i przechodzi każdy test poza tym jednym, który się liczy

Tabela bitów flag wartości seed dla podpisywania PAdES w HotPDF pokazująca bity Ff 2, 4, 32 i 64 oraz obsługę ograniczeń wymaganych kontra preferowanych
Wpis /Ff mieszka wewnątrz /SV, a każda pozycja bitu decyduje, czy niezgodność to twarda odmowa, czy preferencja interfejsu
var
  Violation: AnsiString;
begin
  // Zapytaj pole, czy profil, którym zaraz podpiszemy, jest dozwolony
  if not Pdf.CheckLoadedSignatureSeedValue(0, 'ETSI.CAdES.detached',
       'SHA256', 'Approved for payment', 1, Violation) then
    raise Exception.Create('Signature field rejects this profile: ' +
      String(Violation));
  // Ograniczenie spełnione: kontynuuj przebieg podpisywania
end;

Test, który wystawił pierwotny błąd dekodowania, nie był testem pozytywnym. Była to asercja, że wymuszona niezgodność musi zostać odrzucona, i to jedyny rodzaj testu, który potrafi złapać tę klasę defektów: dekoder czytający zły słownik albo złe pozycje bitów produkuje „żadne ograniczenie nie naruszone” dla każdego wejścia, co wygląda dokładnie jak poprawne zachowanie, dopóki celowo nie naruszysz jednego

Gdzie to siedzi na drabinie LTV

Cztery szczeble i każdy potrzebuje tego pod sobą. B-B to goły podpis. B-T dodaje zaufany znacznik czasu, który fiksuje czas podpisania, więc walidator wie, względem którego momentu oceniać unieważnienia. B-LT dodaje dowody unieważnień do DSS, co automatyzuje PopulatePAdESLTVEvidence. B-LTA dodaje znaczniki czasu dokumentu odnawiane, zanim poprzedni osłabnie, przedłużając ważność nieograniczenie; HotPDF wystawia to jako RenewPAdESLTATimestamp, który dokleja nowy znacznik jako przyrostową rewizję i zachowuje każdy wcześniejszy podpis, znacznik i wpis DSS nietknięte

Model aktualizacji przyrostowej to jedyny poprawny sposób dodawania dowodów do podpisanego dokumentu, bo przepisanie pliku połamałoby zakresy bajtów pokrywane przez istniejące podpisy. Jeśli musisz rozumować o tym, co się zmieniło między rewizjami i czy te zmiany są rodzaju, który podpis pozwala, ta analiza jest omówiona osobno w analizie rewizji DocMDP i FieldMDP. Sam potok podpisywania, łącznie ze źródłami certyfikatów i pułapkami kolejności bajtów, jest w przewodniku podpisywania PAdES, a strona walidacji w weryfikacji podpisów na wczytanych dokumentach

Jedno praktyczne ostrzeżenie o kolejności. Zbieraj dowody jak najszybciej po podpisaniu, najlepiej w tym samym zadaniu. Respondenci, którzy potrafią odpowiedzieć za certyfikat, są online, dopóki certyfikat jest aktualny, i znikają po latach, więc dokument, który opuszcza twój potok jako B-B, może nigdy nie dać się już podnieść. HotPDF działa jako natywny komponent VCL dla Delphi i C++Builder, a cały przebieg dowodów jest w procesie poza twoim własnym transportem; wspierane profile są wypisane na stronie produktu HotPDF Delphi PDF component