PDFiumPas dzieli podpisywanie PAdES na dwa wywołania, dzięki czemu klucz prywatny nigdy nie musi znaleźć się w twoim procesie. PreparePadesRemoteSignature zapisuje aktualizację przyrostową z pustym, o stałej szerokości symbolem zastępczym /Contents i zwraca rekord żądania niosący skrót dokumentu SHA-256, dokładny ByteRange oraz odcisk palca przygotowanego pliku. CompletePadesRemoteSignature przyjmuje odłączony CMS zwrócony przez twoją usługę podpisującą i wstawia go w zarezerwowane miejsce
Między tymi dwoma wywołaniami mogą minąć minuty albo godziny, proces może się zrestartować, a praca może przenieść się na inną maszynę. Ta luka to cały powód, dla którego API ma taki kształt
Dlaczego zdalny klucz nie może użyć zwykłego wywołania podpisującego?
Ponieważ SignPadesBytes zakłada, że operacja podpisywania odbywa się wewnątrz wywołania. Buduje aktualizację przyrostową, oblicza skrót nad ByteRange, podpisuje go i zapisuje wynik, wszystko przed zwróceniem sterowania. To dokładnie właściwe zachowanie, gdy klucz mieszka w magazynie certyfikatów Windows albo w pliku PKCS#12, który wczytałeś
Jest niemożliwe, gdy klucz mieszka w sieciowym HSM, kwalifikowanym urządzeniu do składania podpisu obsługiwanym przez dostawcę usług zaufania, albo w chmurowym API podpisującym, które wymaga potwierdzenia przez użytkownika na telefonie. W takich przypadkach sekwencja to nie funkcja, tylko rozmowa: wysyłasz skrót, coś innego uwierzytelnia człowieka, a CMS wraca później. Synchroniczne API nie potrafi wyrazić „później” bez blokowania wątku na operacji, która może wymagać drugiego składnika
Protokół dwufazowy
Faza pierwsza przygotowuje dokument. PDFiumPas dopisuje pole podpisu i słownik wartości podpisu, rezerwuje ContentsSize bajtów miejsca zakodowanego szesnastkowo w /Contents, oblicza ByteRange wokół tej rezerwacji i tworzy TPadesRemoteSigningRequest zawierający FormatVersion, PreparedFingerprint, DocumentDigest, czteroelementowy ByteRange, ContentsHexOffset i ContentsSize
Jedyna wartość, której potrzebuje twoja usługa podpisująca, to DocumentDigest: SHA-256, który zwrócony CAdES SignedData musi nieść jako swój skrót wiadomości. Wszystko inne w rekordzie istnieje po to, aby faza druga mogła udowodnić, że plik, który kończy, jest plikiem, z którego wyliczono ten skrót
uses
FPdfPades;
var
Options: TPadesRemoteSignOptions;
Request: TPadesRemoteSigningRequest;
Source, Prepared, Session: TFileStream;
begin
Options := TPadesRemoteSignOptions.Default;
Options.Reason := 'Approved by finance';
Options.Location := 'Lisbon';
Options.Name := 'A. Moreira';
Options.SigningTimeUtc := NowUtc;
Options.ContentsSize := 16384; // bajty szesnastkowe zarezerwowane dla CMS
Source := TFileStream.Create('contract.pdf', fmOpenRead or fmShareDenyWrite);
Prepared := TFileStream.Create('contract.prepared.pdf', fmCreate);
try
PreparePadesRemoteSignature(Source, Prepared, Options, Request);
finally
Prepared.Free;
Source.Free;
end;
// Zachowaj sesję, aby późniejsze uruchomienie albo inna maszyna mogły ją dokończyć
Session := TFileStream.Create('contract.signreq', fmCreate);
try
SavePadesRemoteSigningRequest(Session, Request);
finally
Session.Free;
end;
SendDigestToSigningService(Request.DocumentDigest);
end;
Co odrzuca Complete i dlaczego istnieje każda kontrola?
Dokończenie to miejsce, gdzie projekt zdalnego podpisywania zwykle idzie źle, więc walidacja jest celowo bezkompromisowa. CompletePadesRemoteSignature odrzuca przygotowany plik PDF, którego odcisk palca już nie pasuje do żądania, ByteRange, który nie pasuje do zapisanych współrzędnych symbolu zastępczego, zmodyfikowane ograniczniki /Contents, symbol zastępczy, który nie jest już pusty, CMS większy niż rezerwacja, CMS, który nie jest dokładnie jedną wartością DER, nieobsługiwany kształt SignedData, brakujący atrybut signing-certificate-v2 oraz CMS, którego skrót wiadomości nie jest równy skrótowi przygotowanego dokumentu
Każde z nich odpowiada realnej awarii. Kontrole odcisku palca i ByteRange wychwytują przypadek, w którym ktoś ponownie wygenerował przygotowany plik między fazami, co dałoby podpis weryfikujący się względem bajtów, których nikt nie ma. Kontrola pustego symbolu zastępczego wychwytuje podwójne dokończenie, gdy drugi CMS zostaje zapisany nad podpisem, który już istnieje. Kontrola skrótu wiadomości wychwytuje najgroźniejszy przypadek ze wszystkich: poprawnie sformowany CMS podpisany nad innym dokumentem, czyli to, co dostajesz, gdy kolejka miesza dwie równoległe sesje podpisywania. Bez niej wyprodukowałbyś plik, który wygląda na podpisany i wszędzie zawodzi walidację, albo, co gorsza, niesie czyjąś cudzą zgodę
Wymóg signing-certificate-v2 to kwestia zgodności z PAdES, nie integralności. ETSI EN 319 142 wymaga, aby certyfikat podpisujący był związany z podpisanymi atrybutami, a CMS bez tego atrybutu nie jest podpisem PAdES, nawet jeśli weryfikuje się kryptograficznie. Odrzucenie go przy dokończeniu oznacza, że dowiadujesz się o tym tutaj, a nie z raportu walidatora od klienta, temat rozwinięty dalej w dlaczego walidatory odrzucają podpisy PAdES
var
Request: TPadesRemoteSigningRequest;
Session, Prepared, Dest: TFileStream;
CmsDer: TBytes;
begin
Session := TFileStream.Create('contract.signreq', fmOpenRead);
try
Request := LoadPadesRemoteSigningRequest(Session);
finally
Session.Free;
end;
CmsDer := FetchDetachedCmsFromService; // zwrócone przez HSM albo TSP
Prepared := TFileStream.Create('contract.prepared.pdf', fmOpenRead);
Dest := TFileStream.Create('contract.signed.pdf', fmCreate);
try
try
CompletePadesRemoteSignature(Prepared, Dest, Request, CmsDer);
except
on E: EPadesCrypto do
// Każde odrzucenie niesie konkretny powód; zaloguj go dosłownie
FailSession(E.Message);
end;
finally
Dest.Free;
Prepared.Free;
end;
end;
Przekraczanie granic procesów i maszyn
SavePadesRemoteSigningRequest i LoadPadesRemoteSigningRequest serializują sesję przez stabilny, wersjonowany format binarny, co czyni projekt praktycznym, a nie tylko poprawnym. Aplikacja webowa może przygotować dokument w jednym żądaniu, zapisać przygotowany plik PDF i blob sesji, zwrócić skrót do przeglądarki dla podpisu kartą inteligentną i dokończyć plik w zupełnie innym obsłudze żądania
Pole FormatVersion to to, co utrzymuje to bezpieczne między aktualizacjami. Sesja zapisana przez starszą kompilację i wczytana przez nowszą jest jawnie rozpoznana albo odrzucona, zamiast zostać błędnie odczytana jako rekord o innym kształcie. Jeśli twoja kolejka może przechowywać sesje przez wiele dni, traktuj wersję formatu jako fakt operacyjny wart logowania, nie szczegół implementacyjny
Rozmiar miejsca zarezerwowanego
ContentsSize to jedyny parametr, nad którym musisz się zastanowić, ponieważ jest ustalany, zanim CMS w ogóle powstanie. Liczy zarezerwowane miejsce zakodowane szesnastkowo, więc CMS DER o rozmiarze 6 KB potrzebuje co najmniej 12 KB miejsca, a implementacja ogranicza rezerwację do 64 MiB
Zarezerwuj za mało, a dokończenie zawodzi błędem zbyt dużego CMS już po tym, jak twoja usługa podpisująca wykonała swoją pracę, co w przypadku licznikowanej usługi podpisu kwalifikowanego oznacza zmarnowaną operację. Zarezerwuj za dużo, a każdy podpisany dokument na zawsze niesie wypełnienie. Rozsądnym podejściem jest zmierzenie: podpisz jeden dokument swoim prawdziwym łańcuchem certyfikatów, sprawdź długość DER, podwój ją dla zapisu szesnastkowego, a potem dodaj hojny zapas na token znacznika czasu, jeśli zamierzasz przejść na podpis poziomu T. Łańcuchy z kilkoma pośrednikami i długą odpowiedzią OCSP rosną szybciej, niż się ludziom wydaje
Co następuje po podpisie
Dokończony podpis zdalny to PAdES B-B. Długoterminowa walidacja wymaga znacznika czasu i materiału walidacyjnego, co jest osobną aktualizacją przyrostową dodającą DSS i jego per-podpisowe słowniki VRI, opisaną w długoterminowych podpisach ze znacznikami czasu RFC 3161 i DSS. Ten krok jest lokalny: dodaje certyfikaty, odpowiedzi OCSP i CRL-e, z których żaden nie potrzebuje klucza prywatnego
Przed wysyłką zweryfikuj to, co wyprodukowałeś, tą samą ścieżką kodu, której użyłaby strona ufająca, omówioną w inspekcji podpisów cyfrowych PDF i poziomów PAdES. Podpisywanie i weryfikacja to różny kod, a potok zdalnego podpisywania to dokładnie miejsce, w którym te dwa mogą się rozjechać, zanim ktokolwiek to zauważy, dopóki nie powie tego zewnętrzny walidator
PDFiumPas to komponent Delphi i Lazarus wokół silnika PDFium z natywnym stosem PAdES w Pascalu, więc podpisywanie, znakowanie czasem i walidacja działają bez zewnętrznych narzędzi wiersza poleceń. Pełna dokumentacja API i wersja próbna są na stronie komponentu PDFium dla Delphi