Teknisk artikkel

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

HotPDF verifiserer ML-DSA-44-, ML-DSA-65-, ML-DSA-87-, Ed25519- og Ed448-CMS-signaturer i innlastede PDF-dokumenter, og den signerer gjennom pluggbare leverandører slik at den private nøkkelen aldri trenger å bo inne i Delphi-prosessen din. Den andre halvdelen er den delen de fleste team trenger først. En maskinvare-token, en fjernsigneringstjeneste og et nasjonalt eID-kort nekter alle å utlevere en nøkkel, og inntil signerings-pipelinen er skilt fra nøkkellageret, kan ingen av dem brukes i det hele tatt

Delingen er poenget med THPDFSignatureProvider. HotPDF beholder delene den bør eie — parsing av CMS, bygging av SignedData, utlegg av /ByteRange — og delegerer den ene operasjonen den ikke kan eie, som er å gjøre et digest om til en signatur med en nøkkel den ikke har lov til å se. Alt nedenfor følger av den delingen

Hvorfor feiler en gyldig ML-DSA-signatur å verifisere?

Fordi HotPDF nekter ML-DSA på et innlastet dokument som ikke erklærer utvidelsen for det. ML-DSA — det gittersignatur-skjemaet standardisert som FIPS 204, og grunnen til at folk sier «post-kvantum-PDF» — har ingen ISO 32000-2-registrering ennå. En PDF som bærer en slik algoritme, bruker en algoritme basisstandarden ikke navngir, og en fil som i stillhet bruker en unavngitt algoritme, er en fil hvis dom ingen andre kan reprodusere

Så HotPDF gjør kravet eksplisitt. EnsureMLDSAExtensions hever dokumentet til PDF 2.0 der det er tillatt, og skriver /Extensions /HotPDF << /BaseVersion /2.0 /ExtensionLevel 1 >> inn i katalogen. På lesesiden rapporterer LoadedDocumentDeclaresMLDSAExtension om den erklæringen overlevde, og VerifyLoadedSignatureWithOptions anvender den samme testen før den vil hedre Options.AllowMLDSA. Sett flagget på et uerklært dokument og det forblir av — opsjonen kan løsne policy, aldri strukturkravet

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;

Kall det før lagring, ikke etter. Erklæringen er del av det signerte byte-intervallet, og en katalog lappet etterpå er enten en usignert endring av en signert fil eller en ny revisjon som en validator vil rapportere som en modifikasjon

Tre algoritmefamilier, ett verifiseringsinngangspunkt

Alle tre familiene ankommer gjennom VerifyLoadedSignatureWithOptions, som tar en signaturindeks, kildestrømmen, en THPDFCMSVerifyOptions-post og en ut-parameter for signaturdetaljene. Posten har nøyaktig tre felt, og hvert svarer på et spørsmål som tidligere krevde ombygging

SignatureProvider bytter ut din egen leverandør mot den innebygde plattformen. OpenSSLLibraryPath velger et OpenSSL 3-bibliotek, som er det som leverer ren-modus Ed25519- og Ed448-verifisering som Windows CNG ikke tilbyr overalt. AllowMLDSA melder seg på gittalgoritmene, underlagt utvidelsessjekken ovenfor. Den eksakte algoritme-OID-en som ble gjenkjent, kommer tilbake i THPDFSignatureInfo.SignatureAlgorithmOID, slik at en revisjonslogg kan registrere hva som ble verifisert snarere enn hva som ble bedt 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 trenger ingen utvidelseserklæring, fordi ISO 32000-2 allerede innrømmer dem. De trenger derimot en leverandør som implementerer dem, noe som på de fleste Windows-utrullinger betyr å peke OpenSSLLibraryPath mot et bibliotek du leverer og kontrollerer snarere enn mot det som tilfeldigvis finnes på maskinen

Hva lover en signeringsleverandør faktisk?

En leverandør lover én ting: gitt en forespørsel, returner en status og, ved signering, bytes. THPDFSignatureProviderRequest bærer algoritmen og dens OID, digest-OID-en, PSS-saltlengden, hvorvidt inndata er en melding eller et allerede beregnet digest, selve indataene, den offentlige nøkkelen eller sertifikatet, en nøkkelidentifikator og en operasjonsidentifikator. Ingenting i den posten er HotPDF-spesifikt — det er ordforrådet en token-driver eller en signeringstjeneste allerede snakker

Tre implementasjoner levereres med biblioteket. THPDFCallbackSignatureProvider pakker inn anonyme metoder, som er den korteste veien fra en eksisterende intern signeringrutine til en fungerende PDF-signatur. THPDFRemoteSignatureProvider pakker inn en transport-callback med en ny grense for nytt forsøk, et kanselleringsregister og grenser for inndata- og signaturstørrelse, slik at en fast HSM ikke kan bli en fast applikasjon. THPDFPKCS11SignatureProvider serialiserer RSA-operasjoner mot en oppringer-eid, allerede autentisert PKCS#11-økt og privatnøkkel-håndtak — HotPDF logger aldri inn, ser aldri en PIN, og lukker aldri en økt den ikke åpnet

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-enumen seks verdier i stedet for en boolsk?

THPDFSignatureProviderStatus skiller spsValid, spsInvalid, spsUnsupported, spsMalformed, spsProviderError og spsCancelled, og å kollapse dem koster deg evnen til å handle riktig. En signatur som er kryptografisk feil (spsInvalid) er en sikkerhetshendelse. En algoritme leverandøren ikke implementerer (spsUnsupported) er en utrullingslucka. Et transport-svikt (spsProviderError) er verdt å prøve på nytt, og et brukerkansellert token-spørsmål (spsCancelled) er ikke verdt å prøve på nytt i det hele tatt

Regelen for signering er snever: en signeringsleverandør returnerer spsValid bare med en ikke-tom signatur. Verifiseringsleverandører returnerer spsValid eller spsInvalid, og de andre fire forblir adskilte på begge stier. Hvis du skriver en leverandør, motstå fristelsen til å avbilde alt du ikke gjenkjenner over på spsInvalid — det forvandler en manglende DLL til en rapport om at kundens signatur er forfalsket

Hvor signaturen faktisk lander i filen

To funksjoner kobler leverandører til ekte PDF-bytes. HPDFCMSBuildSignedDataWithProvider bygger frakoblet CMS fra et dokument-SHA-256-digest, som er det rette inngangspunktet når din arbeidsflyt beregner digest-et et annet sted. HPDFCMSSignPDFStreamWithProvider signerer en eksisterende signatur-plassholder i en PDF-strøm og bevarer den standardiserte /ByteRange-pipelinen, som er det rette inngangspunktet når HotPDF la ut plassholderen selv

Å bevare den pipelinen betyr mer enn det høres ut som. /ByteRange-konvensjonen — to intervaller som hopper over det heksadesimale signaturvinduet — er det enhver validator sjekker først, og en leverandør-basert sti som skrev den om, ville ødelagt PAdES-konformans uansett hvor sunn kryptografien var. HotPDF holder layouten identisk med den innebygde signeringsstien, slik at et dokument signert gjennom en PKCS#11-token verifiserer med den samme signaturverifiseringskoden som ett signert fra en PFX-fil. For profilreglene som sitter over algoritmevalget, se gjennomgangen av PAdES-grunnlinjesignaturer i Delphi, og for de ECDSA-spesifikke kodingfellene som er eldre enn denne leverandørmodellen, notatene om ECDSA CMS-verifisering og P1363-signaturformater

En migrasjonsorden som ikke etterlater dokumentene dine i stikken

Post-kvantum-beredskap er et tidsplaneproblem, ikke en bryter. Nesten ingen utrullet PDF-leser validerer ML-DSA i dag, så et dokument signert med kun den er, fra leserens ståsted, et dokument med en uverifiserbar signatur. Den ordenen som overlever kontakt med virkelige arkiver er: behold RSA eller ECDSA som signaturen en validator vil bedømme, legg til utvidelseserklæringen og en andre ML-DSA-signatur der en policy krever kvantemotstandsdyktig bevis, og flytt primærsignaturen først når konsumerende systemer har tatt igjen

Det HotPDF gir deg i dag er evnen til å skrive og verifisere begge, fra den samme koden, med algoritmen registrert ærlig i filen og i verifiseringsresultatet. HotPDF er en VCL-PDF-komponent for Delphi og C++Builder uten noen ekstern PDF-runtime, slik at signerings- og verifiseringsstiene leveres inne i din kjørbare i stedet for ved siden av den — se HotPDF Delphi PDF-komponentsiden for den fullstendige funksjonslisten og prøvenedlastingen