Teknisk artikel

Post-kvantum- og EdDSA-PDF-signering med HotPDF i Delphi

HotPDF verificerer ML-DSA-44-, ML-DSA-65-, ML-DSA-87-, Ed25519- og Ed448-CMS-signaturer i indlæste PDF-dokumenter, og den signerer gennem plugbare udbydere, så den private nøgle aldrig behøver at bo inde i din Delphi-proces. Den anden halvdel er den del, de fleste teams har brug for først. Et hardware-token, en fjernsigneringstjeneste og et nationalt eID-kort nægter alle at udlevere en nøgle, og indtil signerings-pipelinen er adskilt fra nøglelageret, kan ingen af dem bruges overhovedet

Den opsplitning er pointen med THPDFSignatureProvider. HotPDF bevarer de dele, den bør eje — parsing af CMS, opbygning af SignedData, layout af /ByteRange — og delegerer den ene operation, den ikke kan eje, som er at forvandle et digest til en signatur med en nøgle, den ikke har lov til at se. Alt det følgende følger af den opdeling

Hvorfor fejler en gyldig ML-DSA-signatur verificering?

Fordi HotPDF afviser ML-DSA på et indlæst dokument, der ikke erklærer udvidelsen for det. ML-DSA — det gitter-baserede signatur-skema standardiseret som FIPS 204, og grunden til at folk siger "post-kvantum PDF" — har ingen ISO 32000-2-registrering endnu. En PDF, der bærer en, bruger en algoritme, basisstandarden ikke navngiver, og en fil, der tavst bruger en unavngiven algoritme, er en fil, hvis dom ikke kan reproduceres af nogen anden

Så HotPDF gør kravet eksplicit. EnsureMLDSAExtensions hæver dokumentet til PDF 2.0, hvor det er tilladt, og skriver /Extensions /HotPDF << /BaseVersion /2.0 /ExtensionLevel 1 >> ind i kataloget. På læsesiden rapporterer LoadedDocumentDeclaresMLDSAExtension, om den erklæring overlevede, og VerifyLoadedSignatureWithOptions anvender samme tjek, før den vil honorere Options.AllowMLDSA. Sæt flaget på et ikke-erklæret dokument, og det forbliver slået fra — tilvalget kan lempre politik, aldrig det strukturelle krav

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;

Kald den før du gemmer, ikke efter. Erklæringen er del af det signerede byte-interval, og et katalog, der patches bagefter, er enten en usigneret ændring af en signeret fil eller en second revision, som en validator vil rapportere som en ændring

Tre algoritme-familier, ét verifikations-indgangspunkt

Alle tre familier ankommer gennem VerifyLoadedSignatureWithOptions, som tager et signatur-indeks, kilde-strømmen, en THPDFCMSVerifyOptions-record og en out-parameter til signatur-detaljerne. Recorden har præcis tre felter, og hvert besvarer et spørgsmål, der tidligere krævede en ombygning

SignatureProvider substituerer din egen udbyder for den indbyggede platform-udbyder. OpenSSLLibraryPath vælger et OpenSSL 3-bibliotek, hvilket er det, der leverer den pure-mode Ed25519- og Ed448-verifikation, som Windows CNG ikke tilbyder alle steder. AllowMLDSA melder sig ind under gitter-algoritmerne, underlagt udvidelsestjekket ovenfor. Den præcise algoritme-OID, der blev genkendt, kommer tilbage i THPDFSignatureInfo.SignatureAlgorithmOID, så en revisionslog kan registrere, hvad der blev verificeret, frem for hvad der blev anmodet om

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 og Ed448 behøver ingen udvidelses-erklæring, fordi ISO 32000-2 allerede admit-ter dem. De har brug for en udbyder, der implementerer dem, hvilket på de fleste Windows-udrulninger betyder at pege OpenSSLLibraryPath på et bibliotek, du shipper og kontrollerer, frem for på det, der tilfældigvis er på maskinen

Hvad loveder en signerings-udbyder reelt?

En udbyder loveder én ting: givet en anmodning, returner en status og, når der signeres, byte. THPDFSignatureProviderRequest bærer algoritmen og dens OID, digest-OID'en, PSS-salt-længden, om inputtet er en besked eller et allerede-beregnet digest, selve inputtet, den offentlige nøgle eller certifikat, en nøgle-identifikator og en operations-identifikator. Intet i den record er HotPDF-specifikt — det er det ordforråd, en token-driver eller en signeringstjeneste allerede taler

Tre implementeringer shipper med biblioteket. THPDFCallbackSignatureProvider indpakker anonyme metoder, hvilket er den korteste vej fra en eksisterende intern signerings-rutine til en fungerende PDF-signatur. THPDFRemoteSignatureProvider indpakker en transport-callback med en retry-grænse, et annullerings-register og grænser for input- og signatur-størrelse, så et hængende HSM ikke kan blive et hængende program. THPDFPKCS11SignatureProvider serialiserer RSA-operationer mod en kalder-ejet, allerede-autentificeret PKCS#11-session og private-nøgle-handle — HotPDF logger aldrig ind, ser aldrig en PIN og lukker aldrig en session, den ikke selv åbnede

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;

Hvorfor har status-enum'en seks værdier i stedet for en boolean?

THPDFSignatureProviderStatus skelner mellem spsValid, spsInvalid, spsUnsupported, spsMalformed, spsProviderError og spsCancelled, og at kollapse dem koster dig evnen til at handle korrekt. En signatur, der er kryptografisk forkert (spsInvalid), er en sikkerhedshændelse. En algoritme, som udbyderen ikke implementerer (spsUnsupported), er et udrulnings-hul. Et transport-fejl (spsProviderError) er værd at prøve igen, og en bruger-annulleret token-prompt (spsCancelled) slet ikke værd at prøve igen

Reglen for signering er snæver: en signerings-udbyder returnerer kun spsValid med en ikke-tom signatur. Verifikations-udbydere returnerer spsValid eller spsInvalid, og de andre fire forbliver adskilte på begge stier. Skriver du en udbyder, så modstå fristelsen til at mappe alt, du ikke genkender, over på spsInvalid — det forvandler en manglende DLL til en rapport om, at kundens signatur er forfalsket

Hvor signaturen reelt lander i filen

To funktioner forbinder udbydere med rigtige PDF-byte. HPDFCMSBuildSignedDataWithProvider bygger detached CMS fra et dokument-SHA-256-digest, hvilket er det rigtige indgangspunkt, når din arbejdsgang beregner digestet andetsteds. HPDFCMSSignPDFStreamWithProvider signerer en eksisterende signatur-placeholder i en PDF-strøm og bevarer den standard /ByteRange-pipeline, hvilket er det rigtige indgangspunkt, når HotPDF selv layoutede placeholderen

At bevare den pipeline betyder mere, end det lyder som. /ByteRange-konventionen — to intervaller, der springer hex-signatur-vinduet over — er det, enhver validator tjekker først, og en udbyder-baseret sti, der omskrev den, ville bryde PAdES-compliance uanset hvor sund kryptografien var. HotPDF bevarer layoutet identisk med den indbyggede signeringssti, så et dokument signeret gennem en PKCS#11-token verificerer med samme signatur-verifikationskode som et signeret fra en PFX-fil. For de profil-regler, der ligger over algoritme-valget, se gennemgangen af PAdES-baseline-signaturer i Delphi, og for de ECDSA-specifikke kodningsfælder, der gik forud for denne udbyder-model, noterne om ECDSA-CMS-verifikation og P1363-signaturformater

En migrationsrækkefølge, der ikke efterlader dine dokumenter i stikken

Post-kvantum-beredskab er et planlægningsproblem, ikke en kontakt. Næsten ingen udrullet PDF-fremviser validerer ML-DSA i dag, så et dokument signeret med den alene er, fra læserens synspunkt, et dokument med en unverificerbar signatur. Den rækkefølge, der overlever mødet med rigtige arkiver, er: bevar RSA eller ECDSA som den signatur, en validator vil bedømme, tilføj udvidelses-erklæringen og en anden ML-DSA-signatur, hvor en politik kræver kvantum-resistent bevis, og flyt den primære signatur kun, når de forbrugende systemer har indhentet sig

Hvad HotPDF giver dig i dag er evnen til at skrive og verificere begge, fra samme kode, med algoritmen optegnet ærligt i filen og i verifikationsresultatet. HotPDF er en native VCL PDF-komponent til Delphi og C++Builder uden ekstern PDF-runtime, så signerings- og verifikations-stierne shipper inde i din eksekverbare frem for ved siden af den — se HotPDF Delphi PDF-komponent-siden for den fulde funktionsliste og trial-download