Műszaki cikk

Poszt-kvantum és EdDSA PDF-aláírás HotPDF-szel Delphiben

A HotPDF ellenőrzi az ML-DSA-44, ML-DSA-65, ML-DSA-87, Ed25519 és Ed448 CMS-aláírásokat a betöltött PDF-dokumentumokban, és bedugható szolgáltatókon keresztül ír alá, így a privát kulcsnak sosem kell a Delphi-folyamatunkon belül élnie. Ez a második fel az a rész, amelyet a legtöbb csapat először akar. Egy hardveres token, egy távoli aláírási szolgáltatás és egy nemzeti eID-kártya mind vonakodik átadni a kulcsot, és amíg az aláíró futószalag nincs elválasztva a kulcstárolótól, egyikkel sem lehet dolgozni

Ez az elválasztás a THPDFSignatureProvider lényege. A HotPDF megtartja azokat a részeket, amelyeket birtokolnia kell — a CMS elemzése, a SignedData felépítése, a /ByteRange kiosztása —, és átruházza azt az egyetlen műveletet, amelyet nem birtokolhat: egy digest aláírássá alakítását egy olyan kulccsal, amelyet nem láthat. Minden, ami lent következik, ebből a felosztásból fakad

Miért bukik meg egy érvényes ML-DSA-aláírás ellenőrzése?

Mert a HotPDF visszautasítja az ML-DSA-t egy olyan betöltött dokumentumon, amely nem deklarálja hozzá a kiterjesztést. Az ML-DSA — a FIPS 204 szabványosította rácsos aláírási séma, és az oka annak, hogy az emberek „poszt-kvantum PDF”-ről beszélnek — még nem rendelkezik ISO 32000-2 regisztrációval. Egy olyan PDF, amely ilyet hordoz, egy olyan algoritmust használ, amelyet az alapszabvány nem nevez meg, és egy fájl, amely csendben névtelen algoritmust használ, olyan fájl, amelynek ítéletét senki más nem tudja reprodukálni

Ezért teszi a HotPDF a hivatkozást kifejezetté. Az EnsureMLDSAExtensions ahol lehetséges, PDF 2.0-ra emeli a dokumentumot, és beírja a /Extensions /HotPDF << /BaseVersion /2.0 /ExtensionLevel 1 >> elemet a katalógusba. Olvasási oldalon a LoadedDocumentDeclaresMLDSAExtension jelzi, hogy a deklaráció túlélte-e, és a VerifyLoadedSignatureWithOptions ugyanezt a tesztet alkalmazza, mielőtt figyelembe venné az Options.AllowMLDSA-t. Állítsuk be a jelzőt egy nem deklarált dokumentumon, és kikapcsolva marad — az opció csak lazíthatja a házirendet, sosem a strukturális követelményt

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;

Mentsés előtt hívjuk, nem utána. A deklaráció az aláírt bájttartomány része, és egy utólag javított katalógus vagy egy aláírt fájl aláíratlan változása, vagy egy második revízió, amelyet egy validátor módosításként jelent

Három algoritmuscsalád, egy ellenőrzési belépési pont

Mind a három család a VerifyLoadedSignatureWithOptions-ön át érkezik, amely átvesz egy aláírás-indexet, a forrásfolyamot, egy THPDFCMSVerifyOptions rekordot és egy out paramétert az aláírás részleteihez. A rekordnak pontosan három mezője van, és mindegyik egy olyan kérdésre válaszol, amely korábban újraépítést igényelt

A SignatureProvider a beépített platform szerintit saját szolgáltatóra cseréli. Az OpenSSLLibraryPath egy OpenSSL 3 könyvtárat választ, amely szolgáltatja azt a pure-mód Ed25519 és Ed448 ellenőrzést, amelyet a Windows CNG nem mindenhol kínál. Az AllowMLDSA a fenti kiterjesztés-ellenőrzés alárendelve engedélyezi a rácsos algoritmusokat. A felismert pontos algoritmus-OID a THPDFSignatureInfo.SignatureAlgorithmOID-ben jön vissza, így egy auditnapló rögzítheti, mit ellenőriztek, nem azt, mit kértek

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;

Az Ed25519 és Ed448 nem igényel kiterjesztés-deklarációt, mert az ISO 32000-2 már felveszi őket. Viszont olyan szolgáltatóra van szükségük, amely megvalósítja őket, ami a legtöbb Windows-telepítésen azt jelenti, hogy az OpenSSLLibraryPath-et egy olyan könyvtárra mutatjuk, amelyet mi szállítunk és szabályozunk, nem pedig arra, ami épp a gépen van

Mit ígér valójában egy aláíró szolgáltató?

Egy szolgáltató egy dolgot ígér: egy kérésre visszaad egy állapotot és, aláíráskor, bájtokat. A THPDFSignatureProviderRequest hordozza az algoritmust és az OID-jét, a digest OID-jét, a PSS sóhosszt, hogy a bemenet üzenet vagy már kiszámított digest-e, magát a bemenetet, a nyilvános kulcsot vagy tanúsítványt, egy kulcsazonosítót és egy művelet-azonosítót. Semmi sem ebben a rekordban HotPDF-specifikus — ez az a szókincs, amelyet egy token-meghajtó vagy egy aláírási szolgáltatás már beszél

Három megvalósítás érkezik a könyvtárral. A THPDFCallbackSignatureProvider névtelen metódusokat csomagol, ami a legrövidebb út egy meglévő házon belüli aláíró rutintól egy működő PDF-aláírásig. A THPDFRemoteSignatureProvider egy átviteli callbacket csomagol újrapróbálkozási korláttal, egy megszakításjegyzékkel és korlátokkal a bemenet és az aláírás méretén, így egy lefagyott HSM nem válik lefagyott alkalmazássá. A THPDFPKCS11SignatureProvider RSA-műveleteket sorosít egy hívó által birtokolt, már hitelesített PKCS#11-munkamenet és privátkulcs-leíró ellen — a HotPDF sosem lép be, sosem lát PIN-t, és sosem zár egy olyan munkamenetet, amelyet nem nyitott

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;

Miért hat értéke van a státuszenumnek egy boolean helyett?

A THPDFSignatureProviderStatus megkülönböztet spsValid, spsInvalid, spsUnsupported, spsMalformed, spsProviderError és spsCancelled értékeket, és ezek összevonása elveszi a helyes cselekvés képességét. Egy kriptográfiailag hibás aláírás (spsInvalid) biztonsági esemény. Egy olyan algoritmus, amelyet a szolgáltató nem valósít meg (spsUnsupported) telepítési hiány. Egy átviteli hiba (spsProviderError) érdemes újrapróbálni, egy felhasználó által megszakított token-promt (spsCancelled) egyáltalán nem érdemes

Az aláírási szabály szűk: egy aláíró szolgáltató csak nem üres aláírással ad vissza spsValid-ot. Az ellenőrző szolgáltatók spsValid vagy spsInvalid értéket adnak vissza, a többi négy mindkét úton külön marad. Ha szolgáltatót írunk, álljunk ellent annak a kísértésnek, hogy mindent, amit nem ismersz, spsInvalid-ra képezz le — ez egy hiányzó DLL-t egy olyan jelentéssé alakít, amely szerint az ügyfél alírása hamisítvány

Hol landol az aláírás valójában a fájlban

Két függvény köti össze a szolgáltatókat a valódi PDF-bájtokkal. A HPDFCMSBuildSignedDataWithProvider detached CMS-t épít egy dokumentum SHA-256 digestjéből, ami a megfelelő belépési pont, amikor a workflow a digestet máshol számolja. A HPDFCMSSignPDFStreamWithProvider egy meglévő aláírás-placeholderre ír alá egy PDF-folyamban, és megőrzi a szabványos /ByteRange futószalagot, ami a megfelelő belépési pont, amikor a HotPDF maga fektette le a placeholdert

A futószalag megőrzése többet számít, mint amennyinek hangzik. A /ByteRange konvenció — két tartomány, amelyek átugorják a hexadecimális aláírás-ablakot — az, amit minden validátor először ellenőriz, és egy szolgáltató-alapú út, amely újraírta volna, megtörné a PAdES-megfelelőséget, bármilyen helyes is a kriptográfia. A HotPDF az elrendezést azonosnak tartja a beépített aláíró úttal, így egy PKCS#11-tokenen keresztül aláírt dokumentum ugyanazzal az aláírás-ellenőrző kóddal ellenőrizhető, mint egy PFX-fájlból aláírt. Az algoritmusválasztás felett álló profil-szabályokhoz lásd a PAdES alapterv-aláírások Delphiben átjárását, az ezt a szolgáltatómodellt megelőző ECDSA-specifikus kódolási csapdákhoz pedig az ECDSA CMS-ellenőrzés és P1363-aláírás-formátumok jegyzeteit

Egy olyan migrációs sorrend, amely nem hagyja cserben a dokumentumaidat

A poszt-kvantum felkészültség ütemterv-probléma, nem kapcsoló. Ma szinte egyetlen telepített PDF-megjelenítő sem validál ML-DSA-t, tehát egy csak azzal aláírt dokumentum az olvasó szempontjából egy olyan dokumentum, amelynek aláírása nem ellenőrizhető. Az a sorrend, amely túléli a valódi archívumokkal való találkozást: tartsuk meg az RSA vagy ECDSA aláírást, amelyet egy validátor ítélni fog, adjuk hozzá a kiterjesztés-deklarációt és egy második ML-DSA-aláírást, ahol egy házirend kvantum-ellenálló bizonyítékot követel, és csak akkor mozgassuk át az elsődleges aláírást, amikor a fogyasztó rendszerek felzárkóztak

Amit a HotPDF ma ad, az a képesség, hogy mindkettőt megírjuk és ellenőrizzük, ugyanabból a kódból, az algoritmus őszintén rögzítve a fájlban és az ellenőrzés eredményében. A HotPDF egy natív VCL PDF komponens Delphihez és C++Builderhez külső PDF futtatókörnyezet nélkül, így az aláíró és ellenőrző utak a saját futtatható állományunkon belül élnek, nem mellette — a teljes funkciólistát és a próbaverziót a HotPDF Delphi PDF komponens oldal tartalmazza