Artykuł techniczny

Podpisywanie PDF post-kwantowe i EdDSA z HotPDF w Delphi

HotPDF weryfikuje podpisy CMS ML-DSA-44, ML-DSA-65, ML-DSA-87, Ed25519 i Ed448 we wczytywanych dokumentach PDF i podpisuje przez wymienne dostawców, dzięki czemu klucz prywatny nigdy nie musi żyć wewnątrz Twojego procesu Delphi. Ta druga połowa to część, której większość zespołów potrzebuje najpierw. Sprzętowy token, zdalna usługa podpisująca i narodowa karta eID wszystkie odmawiają wydania klucza, a dopóki potok podpisywania nie zostanie oddzielony od magazynu kluczy, żaden z nich nie da się użyć wcale

To oddzielenie to sedno THPDFSignatureProvider. HotPDF zatrzymuje części, które powinien posiadać — parsowanie CMS, budowanie SignedData, układanie /ByteRange — i deleguje jedną operację, której nie może posiadać, czyli zamianę skrótu na podpis kluczem, który nie wolno mu zobaczyć. Wszystko poniżej wynika z tego podziału

Dlaczego poprawny podpis ML-DSA nie przechodzi weryfikacji?

Ponieważ HotPDF odmawia ML-DSA we wczytywanym dokumencie, który nie deklaruje dla niego rozszerzenia. ML-DSA — schemat podpisu sieciowo-kratkowego standaryzowany jako FIPS 204 i powód, dla którego mówi się „post-kwantowy PDF" — nie ma jeszcze rejestracji w ISO 32000-2. PDF, który go niesie, używa algorytmu, którego norma bazowa nie wymienia, a plik, który po cichu używa nienazwanego algorytmu, to plik, którego werdyktu nikt inny nie potrafi odtworzyć

Więc HotPDF czyni roszczenie jawnym. EnsureMLDSAExtensions podnosi dokument do PDF 2.0 tam, gdzie to dozwolone, i wpisuje /Extensions /HotPDF << /BaseVersion /2.0 /ExtensionLevel 1 >> do katalogu. Po stronie odczytu LoadedDocumentDeclaresMLDSAExtension raportuje, czy ta deklaracja przetrwała, a VerifyLoadedSignatureWithOptions stosuje ten sam test, zanim zbonoruje Options.AllowMLDSA. Ustaw flagę na niezadeklarowanym dokumencie i pozostanie wyłączona — opcja może poluzować politykę, nigdy wymóg strukturalny

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'contract-pq.pdf';
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 11);
    Pdf.CurrentPage.TextOut(50, 720, 0, 'Supply agreement 2026-114');
    Pdf.EnsureMLDSAExtensions;   // declare before the signature is written
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Wywołaj to przed zapisem, nie po. Deklaracja jest częścią podpisanego zakresu bajtów, a katalog łatany po nim to albo niepodpisana zmiana w podpisanym pliku, albo druga rewizja, którą walidator zaraportuje jako modyfikację

Trzy rodziny algorytmów, jeden punkt wejścia weryfikacji

Wszystkie trzy rodziny przychodzą przez VerifyLoadedSignatureWithOptions, który przyjmuje indeks podpisu, strumień źródłowy, rekord THPDFCMSVerifyOptions i parametr out na szczegóły podpisu. Rekord ma dokładnie trzy pola i każde odpowiada na pytanie, które kiedyś wymagało przebudowy

SignatureProvider podstawia własnego dostawcę zamiast wbudowanego platformowego. OpenSSLLibraryPath wybiera bibliotekę OpenSSL 3, która dostarcza weryfikacji Ed25519 i Ed448 w trybie pure, której Windows CNG nie oferuje wszędzie. AllowMLDSA włącza algorytmy sieciowo-kratkowe, podlegając pod wyżej opisane sprawdzenie rozszerzenia. Dokładny OID rozpoznanego algorytmu wraca w THPDFSignatureInfo.SignatureAlgorithmOID, więc dziennik audytu może zapisywać, co zostało zweryfikowane, a nie co zostało żądane

var
  Opts: THPDFCMSVerifyOptions;
  Info: THPDFSignatureInfo;
  Status: THPDFSignatureVerifyStatus;
  Src: TFileStream;
begin
  Opts := THPDFCMSVerifyOptions.Default;
  Opts.OpenSSLLibraryPath := 'C:\openssl3\libcrypto-3-x64.dll';
  Opts.AllowMLDSA := Pdf.LoadedDocumentDeclaresMLDSAExtension;
  Src := TFileStream.Create('contract-pq.pdf', fmOpenRead or fmShareDenyWrite);
  try
    Status := Pdf.VerifyLoadedSignatureWithOptions(0, Src, Opts, Info);
    if Status = svValid then
      Memo1.Lines.Add('signed with OID ' + string(Info.SignatureAlgorithmOID));
  finally
    Src.Free;
  end;
end;

Ed25519 i Ed448 nie potrzebują deklaracji rozszerzenia, ponieważ ISO 32000-2 już je dopuszcza. Potrzebują za to dostawcy, który je implementuje, co w większości wdrożeń Windows oznacza wskazanie OpenSSLLibraryPath na bibliotekę, którą sam dostarczasz i kontrolujesz, zamiast na to, co akurat jest na maszynie

Co właściwie obiecują dostawca podpisywania?

Dostawca obiecuje jedną rzecz: otrzymawszy żądanie, zwrócić status, a przy podpisywaniu — bajty. THPDFSignatureProviderRequest niesie algorytm i jego OID, OID skrótu, długość soli PSS, czy wejście to komunikat, czy już obliczony skrót, samo wejście, klucz publiczny lub certyfikat, identyfikator klucza i identyfikator operacji. Nic w tym rekordzie nie jest specyficzne dla HotPDF — to słownictwo, jakim mówią już sterownik tokenu czy usługa podpisująca

Trzy implementacje są dostarczane z biblioteką. THPDFCallbackSignatureProvider opakowuje anonimowe metody, co jest najkrótszą drogą od istniejącej wewnętrznej procedury podpisującej do działającego podpisu PDF. THPDFRemoteSignatureProvider opakowuje callback transportowy z limtem powtórzeń, rejestrem anulowań i granicami na rozmiar wejścia i podpisu, więc zawieszony HSM nie może stać się zawieszoną aplikacją. THPDFPKCS11SignatureProvider serializuje operacje RSA względem sesji PKCS#11 należącej do wywołującego, już uwierzytelnionej, i uchwytu klucza prywatnego — HotPDF nigdy się nie loguje, nigdy nie widzi PIN-u i nigdy nie zamyka sesji, której nie otworzył

var
  Provider: THPDFRemoteSignatureProvider;
begin
  Provider := THPDFRemoteSignatureProvider.Create(
    function(const Req: THPDFSignatureProviderRequest; Attempt: Integer;
      out Signature: TBytes): THPDFSignatureProviderStatus
    begin
      // POST Req.Input to the signing service; Req.KeyIdentifier selects the key
      if PostToSigningService(Req.KeyIdentifier, Req.Input, Signature) then
        Result := spsValid
      else
        Result := spsProviderError;
    end,
    3,          // RetryLimit
    1048576,    // MaxInputBytes
    65536);     // MaxSignatureBytes
  try
    // hand Provider to the signing call
  finally
    Provider.Free;
  end;
end;

Dlaczego enum statusu ma sześć wartości zamiast boolean

THPDFSignatureProviderStatus rozróżnia spsValid, spsInvalid, spsUnsupported, spsMalformed, spsProviderError i spsCancelled, a ich złożenie kosztuje Cię zdolność do poprawnego działania. Podpis, który jest kryptograficznie zły (spsInvalid), to zdarzenie bezpieczeństwa. Algorytm, którego dostawca nie implementuje (spsUnsupported), to luka w wdrożeniu. Porażka transportu (spsProviderError) wartą jest powtórki, a monit tokenu anulowany przez użytkownika (spsCancelled) nie warty powtórki wcale

Reguła dla podpisywania jest wąska: dostawca podpisywania zwraca spsValid tylko z niepustym podpisem. Dostawcy weryfikacji zwracają spsValid lub spsInvalid, a pozostałe cztery pozostają rozróżnialne na obu ścieżkach. Jeśli piszesz dostawcę, oprzyj się pokusie zmapowania wszystkiego, czego nie rozpoznajesz, na spsInvalid — to zamienia brakujący DLL w raport, że podpis klienta jest sfałszowany

Gdzie podpis faktycznie ląduje w pliku

Dwie funkcje łączą dostawców z prawdziwymi bajtami PDF. HPDFCMSBuildSignedDataWithProvider buduje odłączony CMS ze skrótu SHA-256 dokumentu, co jest właściwym punktem wejścia, gdy Twój przepływ oblicza skrót gdzie indziej. HPDFCMSSignPDFStreamWithProvider podpisuje istniejący zastępczy pola podpisu w strumieniu PDF i zachowuje standardowy potok /ByteRange, co jest właściwym punktem wejścia, gdy HotPDF sam ułożył zastępcę

Zachowanie tego potoku ma większe znaczenie, niż się wydaje. Konwencja /ByteRange — dwa zakresy pomijające szesnastkowe okno podpisu — to to, co każdy walidator sprawdza najpierw, a ścieżka oparta na dostawcy, która by ją przepisała, złamałaby zgodność PAdES bez względu na to, jak solidna była kryptografia. HotPDF utrzymuje układ identyczny z wbudowaną ścieżką podpisywania, więc dokument podpisany przez token PKCS#11 weryfikuje się tym samym kod weryfikacji podpisów, co dokument podpisany z pliku PFX. Dla reguł profilu siedzących powyżej wyboru algorytmu zobacz przewodnik po podpisach bazowych PAdES w Delphi, a dla pułapek kodowania specyficznych dla ECDSA, które poprzedzają ten model dostawców, notatki o weryfikacji CMS ECDSA i formatach podpisu P1363

Kolejność migracji, która nie pozostawi Twoich dokumentów na lodzie

Gotowość post-kwantowa to problem harmonogramu, nie przełącznik. Niemal żaden wdrożony czytnik PDF nie waliduje dziś ML-DSA, więc dokument podpisany nim samym jest, z punktu widzenia czytelnika, dokumentem z podpisem nie do zweryfikowania. Kolejność, która przetrwa zderzenie z realnymi archiwami, to: utrzymaj RSA lub ECDSA jako podpis, który osądzi walidator, dodaj deklarację rozszerzenia i drugi podpis ML-DSA tam, gdzie polityka wymaga kwantowo-odpornego dowodu, i przenieś podpis podstawowy dopiero wtedy, gdy systemy konsumenckie nadążą

Co HotPDF daje Ci dziś, to zdolność do zapisu i weryfikacji obu z tego samego kodu, z algorytmem zapisanym uczciwie w pliku i w wyniku weryfikacji. HotPDF to natywny komponent PDF VCL dla Delphi i C++Builder bez zewnętrznego runtime PDF, więc ścieżki podpisywania i weryfikacji dostarczane są wewnątrz Twojego pliku wykonywalnego zamiast obok niego — zobacz stronę komponentu HotPDF Delphi PDF, by poznać pełną listę funkcji i wersję próbną