Articolo tecnico

Firma PDF post-quantum e EdDSA con HotPDF in Delphi

HotPDF verifica firme CMS ML-DSA-44, ML-DSA-65, ML-DSA-87, Ed25519 ed Ed448 nei documenti PDF caricati, e firma attraverso provider innestabili così la chiave privata non deve mai vivere dentro il tuo processo Delphi. Questa seconda metà è la parte che la maggior parte dei team richiede per prima. Un token hardware, un servizio di firma remoto e una carta eID nazionale si rifiutano tutti di consegnare una chiave, e finché la pipeline di firma non è separata dal key store, nessuno di loro può essere usato affatto

La separazione è il punto di THPDFSignatureProvider. HotPDF tiene le parti che dovrebbe gestire — analisi CMS, costruzione del SignedData, disposizione del /ByteRange — e delega l'unica operazione che non può gestire, che è trasformare un digest in una firma con una chiave che non gli è permesso vedere. Tutto quello che segue deriva da quella divisione

Perché una firma ML-DSA valida fallisce la verifica?

Perché HotPDF rifiuta ML-DSA su un documento caricato che non dichiara l'estensione per esso. ML-DSA — lo schema di firma su reticolo standardizzato come FIPS 204, e il motivo per cui si dice «PDF post-quantum» — non ha ancora una registrazione in ISO 32000-2. Un PDF che ne porta una sta usando un algoritmo che lo standard base non nomina, e un file che usa silenziosamente un algoritmo non nominato è un file il cui verdetto non può essere riprodotto da nessun altro

Così HotPDF rende la rivendicazione esplicita. EnsureMLDSAExtensions innalza il documento a PDF 2.0 dove permesso e scrive /Extensions /HotPDF << /BaseVersion /2.0 /ExtensionLevel 1 >> nel Catalog. Lato lettura, LoadedDocumentDeclaresMLDSAExtension segnala se quella dichiarazione è sopravvissuta, e VerifyLoadedSignatureWithOptions applica lo stesso test prima di onorare Options.AllowMLDSA. Imposta il flag su un documento non dichiarato e resta spento — l'opzione può allentare la policy, mai il requisito strutturale

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;

Chiamala prima del salvataggio, non dopo. La dichiarazione fa parte dell'intervallo di byte firmato, e un Catalog rattoppato in seguito è o una modifica non firmata a un file firmato o una seconda revisione che un validatore riporterà come modifica

Tre famiglie di algoritmi, un punto di ingresso per la verifica

Tutte e tre le famiglie arrivano attraverso VerifyLoadedSignatureWithOptions, che prende un indice di firma, lo stream sorgente, un record THPDFCMSVerifyOptions e un parametro out per i dettagli della firma. Il record ha esattamente tre campi, e ciascuno risponde a una domanda che prima richiedeva una ricostruzione

SignatureProvider sostituisce il tuo provider personale a quello platform integrato. OpenSSLLibraryPath seleziona una libreria OpenSSL 3, che è ciò che fornisce la verifica Ed25519 ed Ed448 in modalità pura che Windows CNG non offre ovunque. AllowMLDSA attiva gli algoritmi su reticolo, soggetti al controllo estensione sopra. L'OID esatto dell'algoritmo che è stato riconosciuto torna in THPDFSignatureInfo.SignatureAlgorithmOID, così un log di audit può registrare cosa è stato verificato anziché cosa era stato richiesto

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 ed Ed448 non hanno bisogno di dichiarazione di estensione, perché ISO 32000-2 le ammette già. Hanno però bisogno di un provider che le implementi, il che sulla maggior parte delle distribuzioni Windows significa puntare OpenSSLLibraryPath a una libreria che distribuisci e controlli anziché a quella che casualmente si trova sulla macchina

Cosa promette davvero un provider di firma?

Un provider promette una cosa: data una richiesta, restituisce uno stato e, quando firma, dei byte. THPDFSignatureProviderRequest porta l'algoritmo e il suo OID, l'OID del digest, la lunghezza del salt PSS, se l'input è un messaggio o un digest già calcolato, l'input stesso, la chiave pubblica o il certificato, un identificatore di chiave e un identificatore di operazione. Niente in quel record è specifico di HotPDF — è il vocabolario che un driver di token o un servizio di firma già parla

Tre implementazioni sono fornite con la libreria. THPDFCallbackSignatureProvider avvolge anonymous method, che è la via più breve da una routine di firma interna esistente a una firma PDF funzionante. THPDFRemoteSignatureProvider avvolge un callback di trasporto con un limite di retry, un registry di annullamento e limiti su input e dimensione della firma, così un HSM bloccato non può diventare un'applicazione bloccata. THPDFPKCS11SignatureProvider serializza operazioni RSA contro una sessione PKCS#11 di proprietà del chiamante e già autenticata e un handle di chiave privata — HotPDF non fa mai login, non vede mai un PIN e non chiude mai una sessione che non ha aperto

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;

Perché l'enum di stato ha sei valori invece di un booleano

THPDFSignatureProviderStatus distingue spsValid, spsInvalid, spsUnsupported, spsMalformed, spsProviderError e spsCancelled, e collassarli ti costa la capacità di agire correttamente. Una firma crittograficamente sbagliata (spsInvalid) è un evento di sicurezza. Un algoritmo che il provider non implementa (spsUnsupported) è una lacuna di distribuzione. Un fallimento di trasporto (spsProviderError) vale la pena ritentarlo, e un prompt di token annullato dall'utente (spsCancelled) non vale la pena ritentarlo affatto

La regola per la firma è ristretta: un provider di firma restituisce spsValid solo con una firma non vuota. I provider di verifica restituiscono spsValid o spsInvalid, e gli altri quattro restano distinti su entrambi i percorsi. Se scrivi un provider, resisti alla tentazione di mappare tutto ciò che non riconosci su spsInvalid — questo trasforma una DLL mancante in un report che la firma del cliente è contraffatta

Dove atterra davvero la firma nel file

Due funzioni connettono i provider a byte PDF reali. HPDFCMSBuildSignedDataWithProvider costruisce CMS detached da un digest SHA-256 del documento, che è il punto di ingresso giusto quando il tuo flusso calcola il digest altrove. HPDFCMSSignPDFStreamWithProvider firma un segnaposto di firma esistente in uno stream PDF e preserva la pipeline standard /ByteRange, che è il punto di ingresso giusto quando HotPDF ha disposto il segnaposto stesso

Preservare quella pipeline conta più di quanto sembri. La convenzione /ByteRange — due intervalli che saltano la finestra esadecimale della firma — è ciò che ogni validatore controlla per primo, e un percorso basato su provider che la riscrivesse romperebbe la conformità PAdES non importa quanto fosse solida la crittografia. HotPDF mantiene il layout identico al percorso di firma integrato, così un documento firmato attraverso un token PKCS#11 si verifica con lo stesso codice di verifica delle firme di uno firmato da un file PFX. Per le regole di profilo che stanno sopra la scelta dell'algoritmo, vedi la guida sulle firme baseline PAdES in Delphi, e per le trappole di codifica specifiche ECDSA che precedono questo modello di provider, le note sulla verifica CMS ECDSA e sui formati di firma P1363

Un ordine di migrazione che non lascia indietro i tuoi documenti

La preparazione post-quantum è un problema di calendario, non un interruttore. Quasi nessun visualizzatore PDF distribuito oggi valida ML-DSA, quindi un documento firmato solo con quello è, dal punto di vista del lettore, un documento con una firma non verificabile. L'ordine che sopravvive al contatto con archivi reali è: mantenere RSA o ECDSA come firma che un validatore giudicherà, aggiungere la dichiarazione di estensione e una seconda firma ML-DSA dove una policy richiede evidenza resistente ai quantistici, e spostare la firma primaria solo quando i sistemi consumer si saranno aggiornati

Cosa HotPDF ti dà oggi è la capacità di scrivere e verificare entrambe, dallo stesso codice, con l'algoritmo registrato onestamente nel file e nel risultato della verifica. HotPDF è un componente VCL PDF nativo per Delphi e C++Builder senza runtime PDF esterno, così i percorsi di firma e verifica girano dentro il tuo eseguibile anziché alongside — vedi la pagina del componente HotPDF Delphi per la lista completa delle funzionalità e il download di prova