Samouczek: Podpisywanie PDF PAdES w Delphi

Kompletny przewodnik, który bierze niepodpisany PDF faktury i produkuje dokument PAdES-B-T z osadzonym znacznikiem czasu RFC 3161 — poziom oczekiwany przez eIDAS, weryfikatory AdES i większość portali sektora publicznego. Skonfigurujesz dostawcę kluczy (karta inteligentna PKCS#11 lub Windows certificate store), wybierzesz właściwy profil PAdES i zweryfikujesz podpisany plik end-to-end.

PAdES-B-B / B-T / B-LT / B-LTA
PKCS#11 / Windows Store / PFX
Delphi 7 – RAD Studio 13

Co zbudujesz

Aplikację VCL Forms, która ładuje PDF, podpisuje go kartą inteligentną lub PFX, osadza zaufany znacznik czasu i zapisuje weryfikowalny plik PAdES na dysk.

Wybierz poziom PAdES

PAdES-B-B (podstawowy), PAdES-B-T (ze znacznikiem czasu), PAdES-B-LT (długoterminowy z danymi unieważnienia) lub PAdES-B-LTA (długoterminowy ze znacznikiem czasu archiwum). PAdES-B-T to rozsądny domyślny wybór dla faktur, umów i większości przepływów sektora publicznego.

Załaduj klucz

sgcSign obsługuje 10 dostawców kluczy. Ten samouczek omawia TsgcPKCS11KeyProvider dla tokenów sprzętowych i TsgcWindowsStoreKeyProvider dla certyfikatów już zainstalowanych w Windows. Ta sama ścieżka kodu działa dla PFX, PEM, Azure Trusted Signing, AWS KMS i Google KMS.

Podpisz i zweryfikuj

TsgcSignPDF napędza pipeline podpisywania, TsgcSignProfile_PAdES konfiguruje profil, a TsgcSignatureVerifier waliduje końcowy dokument. Każdy krok udostępnia zdarzenia, abyś mógł sprawdzić, co dzieje się na każdym etapie.

Dodaj jednostki

Każdy dostawca kluczy znajduje się w swojej własnej jednostce. Odwołuj się tylko do dostawcy, którego potrzebujesz, aby nie linkować jednostek, których nie używasz.

Klauzula uses w Delphi

W tym samouczku ściągamy dwóch dostawców kluczy (PKCS#11 i Windows Store) oraz jednostki podpisywania PAdES. sgcSign_TSA to klient znacznika czasu RFC 3161 — profil PAdES używa go automatycznie po ustawieniu URL TSA.

Jeśli masz tylko plik PFX, zamień sgcSign_KeyProvider_PKCS11 na sgcSign_KeyProvider_PFX i użyj TsgcPFXKeyProvider, jak pokazano w 5-minutowym Szybkim starcie.

uPDFSigning.pas
uses
  Classes, SysUtils,
  // sgc
  sgcSign_KeyProvider_PKCS11,
  sgcSign_KeyProvider_WindowsStore,
  sgcSign_PDF,
  sgcSign_Profile_PAdES,
  sgcSign_TSA,
  sgcSign_Verifier;

Skonfiguruj dostawcę kluczy

Dwa typowe wybory — sprzętowy token PKCS#11 (karty inteligentne, USB HSM) lub certyfikat już w Windows certificate store.

Karta inteligentna PKCS#11

Wskaż ModulePath na DLL PKCS#11 dostawcy (na przykład C:\Windows\System32\eTPKCS11.dll dla SafeNet). Właściwość Slot wybiera slot tokenu — użyj 0 dla pierwszego dostępnego tokenu. PIN jest dostarczany przez zdarzenie OnPinPrompt, więc nigdy nie jest przechowywany w źródle.

Wywołanie LoadFromToken otwiera sesję, znajduje certyfikat podpisujący po etykiecie lub odcisku palca i przygotowuje uchwyt klucza prywatnego. Żadne bajty nie opuszczają tokenu — rzeczywiste podpisywanie RSA-PSS lub ECDSA odbywa się na urządzeniu.

step1-pkcs11.pas
var
  vKeyProvider: TsgcPKCS11KeyProvider;
begin
  vKeyProvider := TsgcPKCS11KeyProvider.Create(nil);
  try
    vKeyProvider.ModulePath := 'C:\Windows\System32\eTPKCS11.dll';
    vKeyProvider.Slot := 0;
    vKeyProvider.CertificateLabel := 'My Signing Cert';
    vKeyProvider.OnPinPrompt :=
      procedure(Sender: TObject; var aPIN: string)
      begin
        aPIN := InputBox('Smart card', 'PIN:', '');
      end;
    vKeyProvider.LoadFromToken;
    // vKeyProvider is now ready to sign
  finally
    // keep alive until signing finishes, then Free
  end;
end;

Windows certificate store

Jeśli Twój certyfikat jest już zainstalowany w Windows store — na przykład ponieważ używasz kwalifikowanej karty eID z mini-sterownikiem dostawcy — użyj TsgcWindowsStoreKeyProvider. Dostawca rozmawia z CNG, więc SHA-256/SHA-384 działają niezależnie od oryginalnego CSP.

Dopasuj po podmiocie, odcisku palca lub numerze seryjnym. Ustawienie StoreLocation na slCurrentUser używa magazynu osobistego; slLocalMachine używa magazynu maszyny i zwykle wymaga uprawnień administratora.

step1-winstore.pas
var
  vKeyProvider: TsgcWindowsStoreKeyProvider;
begin
  vKeyProvider := TsgcWindowsStoreKeyProvider.Create(nil);
  vKeyProvider.StoreLocation := slCurrentUser;
  vKeyProvider.StoreName := 'MY';
  vKeyProvider.Thumbprint :=
    '1234ABCD5678EF901234567890ABCDEF12345678';
  vKeyProvider.LoadFromStore;
end;

Skonfiguruj profil PAdES-B-T

PAdES-B-T dodaje znacznik czasu RFC 3161 do podpisu, więc weryfikatorzy mogą udowodnić, że dokument został podpisany przed znanym punktem w czasie — nawet jeśli certyfikat podpisujący później wygaśnie lub zostanie unieważniony.

Właściwości profilu

Level wybiera poziom AdES — palB, palT, palLT lub palLTA. Dla PAdES-B-T ustaw go na palT; profil oczekuje wtedy niepustego TSA.URL.

HashAlgorithm domyślnie ma shaSHA256, czego oczekuje każdy nowoczesny weryfikator. SHA-384 i SHA-512 są również dostępne; SHA-1 nie jest celowo oferowany, ponieważ eIDAS już go nie akceptuje.

Podobiekt SignatureField kontroluje, gdzie widoczny widget podpisu jest umieszczony wewnątrz PDF — numer strony, współrzędne prostokąta i opcjonalne pola tekstowe powód / lokalizacja / kontakt. Zostaw puste dla niewidocznych podpisów (domyślnie).

step2-profile.pas
var
  vProfile: TsgcSignProfile_PAdES;
begin
  vProfile := TsgcSignProfile_PAdES.Create(nil);
  vProfile.Level := palT;
  vProfile.HashAlgorithm := shaSHA256;

  // RFC 3161 timestamp authority
  vProfile.TSA.URL := 'http://timestamp.digicert.com';
  vProfile.TSA.HashAlgorithm := shaSHA256;

  // Optional visible signature widget
  vProfile.SignatureField.Visible := True;
  vProfile.SignatureField.Page := 1;
  vProfile.SignatureField.Rect := Rect(50, 50, 250, 120);
  vProfile.SignatureField.Reason := 'Approved';
  vProfile.SignatureField.Location := 'Madrid';
end;

Podpisz PDF

Podłącz dostawcę kluczy i profil do TsgcSignPDF, potem wywołaj SignFile z nazwami pliku wejściowego i wyjściowego.

Pełne wywołanie podpisywania

SignFile czyta wejściowy PDF, oblicza digest dokumentu, wywołuje dostawcę kluczy w celu wytworzenia podpisu, pobiera znacznik czasu z TSA i zapisuje wynik. Jeśli wejście już zawiera podpisy, sgcSign dołącza inkrementalną aktualizację — oryginalne podpisy pozostają ważne.

Zdarzenie OnProgress raportuje każdą fazę (czytanie, hashowanie, podpisywanie, znacznik czasu, zapis), przydatne dla pasków postępu na plikach wielomegabajtowych.

step3-sign.pas
var
  vSigner: TsgcSignPDF;
begin
  vSigner := TsgcSignPDF.Create(nil);
  try
    vSigner.KeyProvider := vKeyProvider;
    vSigner.Profile := vProfile;
    vSigner.OnProgress :=
      procedure(Sender: TObject; const aPhase: string; aPct: Integer)
      begin
        Memo1.Lines.Add(Format('%s — %d%%', [aPhase, aPct]));
      end;
    vSigner.SignFile('invoice.pdf', 'invoice-signed.pdf');
  finally
    vSigner.Free;
  end;
end;

Zweryfikuj podpisany PDF

Podpisany dokument jest przydatny tylko wtedy, gdy się weryfikuje. TsgcSignatureVerifier waliduje kryptograficzny podpis, łańcuch certyfikatów i osadzony znacznik czasu w jednym wywołaniu.

Odczytywanie wyniku

Metoda VerifyFile zwraca TsgcSignatureReport z podziałem na podpisy. Dla każdego wpisu dostajesz Status (svValid, svInvalid, svUnknown), podmiot podpisującego, faktycznie wykryty poziom AdES, wartość znacznika czasu oraz wszelkie problemy z łańcuchem lub unieważnieniem.

Porównaj Report.Level z oczekiwanym poziomem — chciałeś palT, więc każda weryfikacja, która raportuje palB, oznacza, że żądanie znacznika czasu cicho zawiodło. URL TSA to zwykły podejrzany.

step4-verify.pas
var
  vVerifier: TsgcSignatureVerifier;
  vReport: TsgcSignatureReport;
  i: Integer;
begin
  vVerifier := TsgcSignatureVerifier.Create(nil);
  try
    vReport := vVerifier.VerifyFile('invoice-signed.pdf');
    for i := 0 to vReport.SignatureCount - 1 do
    begin
      Memo1.Lines.Add('Signer:    ' + vReport.Signatures[i].Subject);
      Memo1.Lines.Add('Level:     ' + vReport.Signatures[i].LevelAsString);
      Memo1.Lines.Add('Status:    ' + vReport.Signatures[i].StatusAsString);
      Memo1.Lines.Add('TSA time:  ' +
        DateTimeToStr(vReport.Signatures[i].TimestampUTC));
    end;
  finally
    vVerifier.Free;
  end;
end;

Co zwykle idzie nie tak

Krótka lista problemów, które widzimy najczęściej, gdy deweloperzy podpisują swój pierwszy dokument PAdES.

TSA zwraca HTTP 200, ale błędną odpowiedź

Niektóre TSA są tylko HTTPS i odrzucają zwykłe URL http:// z miękkim błędem. Jeśli Report.Level wraca jako palB, gdy prosiłeś o palT, przełącz URL TSA na jego odpowiednik https://. Większość publicznych TSA publikuje oba schematy.

Certyfikat nie ma rozszerzonego użycia klucza

Certyfikaty testowe często nie mają EKU id-kp-documentSigning. Adobe Reader wyświetli ostrzeżenie, mimo że kryptograficzny podpis jest ważny. Do produkcji poproś o certyfikat z jawnym Document Signing EKU od swojego CA.

Dokumenty PDF/A potrzebują PAdES-B-LT

Archiwa PDF/A-2 i PDF/A-3 wymagają, aby wszystkie informacje o unieważnieniu były osadzone w samym dokumencie. Użyj palLT lub palLTA — sgcSign zbierze odpowiedzi OCSP i CRL w czasie podpisywania i osadzi je w słowniku DSS.

Sesje karty inteligentnej wygasają

Niektóre sterowniki PKCS#11 zamykają sesję po kilku sekundach bezczynności. Trzymaj instancję TsgcPKCS11KeyProvider żywą przez całą operację podpisywania i wywołaj Free dopiero po zwróceniu SignFile.

Dokąd dalej

Archiwizacja długoterminowa, profile krajowe lub przeniesienie podpisywania z pulpitu do centralnego serwera.

21 profili krajowych

Jednolinijkowe wywołania dla VeriFactu, FatturaPA, KSeF, FACTUR-X i unijnych profili umów o pracę — wszystkie zbudowane na tym samym silniku PAdES / XAdES.

Czytaj więcej →

sgcSign Server

Przenieś podpisywanie do samoobsługowego demona — REST API, integracja z GitHub Actions, Authenticode i ClickOnce na bazie PAdES.

Czytaj więcej →

10 dostawców kluczy

Poza PFX i Windows store — Azure Trusted Signing, AWS KMS, Google KMS, HashiCorp Vault, Certum i dostawcy chmurowi CSC v2.

Czytaj więcej →

Gotowy podpisać swój pierwszy PDF?

Pobierz wersję próbną, uruchom ten samouczek na swojej własnej fakturze, licencjonuj sgcSign przy wdrożeniu.