Tekninen artikkeli

Post-kvantti- ja EdDSA-PDF-allekirjoitus HotPDF:llä Delphissä

HotPDF varmentaa ML-DSA-44-, ML-DSA-65-, ML-DSA-87-, Ed25519- ja Ed448-CMS-allekirjoitukset ladatuissa PDF-asiakirjoissa, ja se allekirjoittaa liitettävien tarjoajien läpi niin että yksityisen avaimen ei koskaan tarvitse elää Delphi-prosessisi sisällä. Jälkimmäinen puolikas on se osa jota useimmat tiimit tarvitsevat ensimmäisenä. Laitteistotoken, etäallekirjoituspalvelu ja kansallinen eID-kortti kaikki kieltäytyvät luovuttamasta avainta, ja kunnes allekirjoitusputki on erotettu avainsäilöstä, mitään niistä ei voi käyttää lainkaan

Tuo jako on koko THPDFSignatureProvider:n pointti. HotPDF pitää ne osat joita sen tulisi omistaa — CMS:n jäsennyksen, SignedDatan rakentamisen, /ByteRange:n asettelun — ja delegoi sen yhden operaation jota se ei voi omistaa, eli tiivisteen muuttamisen allekirjoitukseksi avaimella jota sen ei sallita nähdä. Kaikki alla seuraava seuraa tuosta jaosta

Miksi kelvollinen ML-DSA-allekirjoitus epäonnistuu varmentuessa?

Koska HotPDF kieltäytyy ML-DSA:sta ladatussa asiakirjassa joka ei julista sille tehtyä laajennusta. ML-DSA — hilsitalgoritmi joka standardoitiin FIPS 204:ssä, ja syy miksi ihmiset sanovat "post-kvantti-PDF" — ei ole vielä ISO 32000-2-rekisteröity. PDF joka kantaa sitä käyttää algoritmia jota perusstandardi ei nimeä, ja tiedosto joka hiljaa käyttää nimeämätöntä algoritmia on tiedosto jonka tuomiota kukaan muu ei voi toistaa

Niinpä HotPDF tekee vaatimuksen nimenomaiseksi. EnsureMLDSAExtensions nostaa asiakirjan PDF 2.0:een kun se sallitaan ja kirjoittaa /Extensions /HotPDF << /BaseVersion /2.0 /ExtensionLevel 1 >> Katalogiin. Lukemispuolella LoadedDocumentDeclaresMLDSAExtension raportoi säilyikö tuo julistus, ja VerifyLoadedSignatureWithOptions soveltaa samaa testiä ennen kuin se kunnioittaa Options.AllowMLDSA:a. Aseta lippu julistamattomalle asiakirjalle ja se pysyy pois — optio voi löysätä käytäntöä, ei koskaan rakenteellista vaatimusta

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;

Kutsu sitä ennen tallennusta, ei jälkeen. Julistus on osa allekirjoitettua tavu-aluetta, ja jälkikäteen paikattu Katalogi on joko allekirjoittamaton muutos allekirjoitettuun tiedostoon tai toinen revisio jonka validaattori raportoi muutoksena

Kolme algoritmiperhettä, yksi varmennustulopaikka

Kaikki kolme perhettä saapuvat VerifyLoadedSignatureWithOptions:n läpi, joka ottaa allekirjoitusindeksin, lähdevirran, THPDFCMSVerifyOptions-tietueen ja out-parametrin allekirjoitustiedoille. Tietueessa on tasan kolme kenttää, ja jokainen vastaa kysymykseen joka aiemmin vaati uudelleenkäännöksen

SignatureProvider korvaa sisäänrakennetun alustakohtaisen oman tarjoajasi kanssa. OpenSSLLibraryPath valitsee OpenSSL 3 -kirjaston, joka on se mikä toimittaa puhtaassa tilassa olevan Ed25519- ja Ed448-varmennuksen jota Windows CNG ei tarjoa kaikkialla. AllowMLDSA:a optaa hilikkualgoritmeihin yllä olevan laajennustarkistuksen alaisena. Tunnistettu tarkka algoritmi-OID palautuu kentässä THPDFSignatureInfo.SignatureAlgorithmOID, jotta auditointiloki voi tallentaa mikä varmennettiin eikä sitä mitä pyydettiin

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 ja Ed448 eivät vaadi laajennusjulistusta, koska ISO 32000-2 jo hyväksyy ne. Ne vaativat kuitenkin tarjoajan joka toteuttaa ne, mikä useimmissa Windows-käyttöönoissa tarkoittaa OpenSSLLibraryPath:n osoittamista kirjastoon jonka toimitat ja hallitset sen sijaan että se olisi koneelta sattumalta löytyvä

Mitä allekirjoitustarjoaja oikein lupaa?

Tarjoaja lupaa yhden asian: annettua pyyntöä kohden palauta tila ja, allekirjoitettaessa, tavut. THPDFSignatureProviderRequest kantaa algoritmin ja sen OID:n, tiiste-OID:n, PSS-suolapituuden, onko syöte viesti vai jo laskettu tiiste, itse syötteen, julkisen avaimen tai sertifikaatin, avaimen tunnisteen ja operaation tunnisteen. Mikään tuossa tietueessa ei ole HotPDF-kohtaista — se on sanasto jota token-ajuri tai allekirjoituspalvelu jo puhuu

Kolme toteutusta toimitetaan kirjaston mukana. THPDFCallbackSignatureProvider kietoo anonyymit metodit, mikä on lyhin polku olemassa olevasta talon sisäisestä allekirjoitusrutiinistä toimivaan PDF-allekirjoitukseen. THPDFRemoteSignatureProvider kietoo kuljetus-callbackin uudelleenyritysrajalla, peruutusrekisterillä ja rajoilla syöte- ja allekirjoituskoolle, jottei jumiutunut HSM voi muuttua jumiutuneeksi sovellukseksi. THPDFPKCS11SignatureProvider sarjoi RSA-operaatiot kutsujan omistamaan, jo todennettuun PKCS#11-istuntoon ja yksityisen avaimen kahvaan — HotPDF ei koskaan kirjaudu sisään, ei näe PIN:iä eikä sulje istuntoa jota se ei avannut

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;

Miksi tilaenumeraatiossa on kuusi arvoa booleanin sijaan

THPDFSignatureProviderStatus erottaa spsValid, spsInvalid, spsUnsupported, spsMalformed, spsProviderError ja spsCancelled, ja niiden yhdistäminen maksaa sinulta kyvyn toimia oikein. Kryptografisesti väärä allekirjoitus (spsInvalid) on turvallisuustapahtuma. Algoritmi jota tarjoaja ei toteuta (spsUnsupported) on käyttöönottokuilu. Kuljetushäiriö (spsProviderError) on uudelleenyrityksen arvoinen, ja käyttäjän peruuttama token-kehote (spsCancelled) ei ole uudelleenyrityksen arvoinen lainkaan

Sääntö allekirjoittamiselle on kapea: allekirjoitustarjoaja palauttaa spsValid:n vain ei-tyhjällä allekirjoituksella. Varmennustarjoajat palauttavat spsValid:n tai spsInvalid:n, ja muut neljä pysyvät erillään molemmilla poluilla. Jos kirjoitat tarjoajan, vastusta kiusausta kartoittaa kaikki tunnistamasi spsInvalid:een — se muuttaa puuttuvan DLL:n raportiksi jonka mukaan asiakkaan allekirjoitus on väärennetty

Mihin allekirjoitus oikeasti päätyy tiedostossa

Kaksi funktiota yhdistävät tarjoajat oikeisiin PDF-tavuihin. HPDFCMSBuildSignedDataWithProvider rakentaa irrotetun CMS:n asiakirjan SHA-256-tiisteestä, mikä on oikea tulopaikka kun työnkulkusi laskee tiisteen muualla. HPDFCMSSignPDFStreamWithProvider allekirjoittaa olemassa olevan allekirjoituspaikkamerkin PDF-virrassa ja säilyttää standardin /ByteRange-putken, mikä on oikea tulopaikka kun HotPDF asetteli paikkamerkin itse

Tuo putken säilyttäminen merkitsee enemmän kuin miltä kuulostaa. /ByteRange-käytäntö — kaksi aluetta jotka ohittavat hex-allekirjoitusikkunan — on se minkä jokainen validaattori tarkistaa ensimmäisenä, ja tarjoajapohjainen polku joka kirjoittaisi sen uudelleen rikkoisi PAdES-vaatimustenvastaisuuden riippumatta siitä kuinka terve kryptografia oli. HotPDF pitää asettelun identtisenä sisäänrakennetun allekirjoituspolun kanssa, joten asiakirja joka allekirjoitettiin PKCS#11-tokenin läpi varmennetaan samalla allekirjoituksen varmennuskoodilla kuin PFX-tiedostosta allekirjoitettu. Profiilisäännöt jotka istuvat algoritmivalinnan yläpuolella on katso artikkeli PAdES-perustason allekirjoitukset Delphissä, ja ECDSA-kohtaiset koodausansat jotka edelsivät tätä tarvojmallia on muistiinpanoissa ECDSA CMS -varmennus ja P1363-allekirjoitusmuodot

Siirtymäjärjestys joka ei jätä asiakirjojasi rannalle

Post-kvanttivalmius on aikatauluongelma, ei kytkin. Lähes yksikään käytössä oleva PDF-katselin ei varmenna ML-DSA:ta tänään, joten asiakirja joka on allekirjoitettu yksinomaan sillä on lukijan näkökulmasta asiakirja jolla ei ole varmennettavaa allekirjoitusta. Järjestys joka selviää kontaktista todellisten arkistojen kanssa on: pidä RSA tai ECDSA allekirjoituksena jonka validaattori tuomitsee, lisää laajennusjulistus ja toinen ML-DSA-allekirjoitus missä käytäntö vaatii kvantti kestävää näyttöä, ja siirry ensisijaiseen allekirjoitukseen vasta kun käyttävät järjestelmät ovat saavuttaneet

Mitä HotPDF antaa sinulle tänään on kyky kirjoittaa ja varmentaa molemmat, samasta koodista, algoritmin tallennettuna rehellisesti tiedostoon ja varmennustulokseen. HotPDF on natiivi VCL PDF -komponentti Delphille ja C++Builderille ilman ulkoista PDF-ajonaikaa, joten allekirjoitus- ja varmennuspolut toimitetaan suoritettavan tiedostosi sisällä sen vierellä olemisen sijaan — katso HotPDF Delphi PDF -komponenttisivulta koko ominaisuusluettelo ja kokeiluversio