Article technique

Signature PDF Post-Quantique et EdDSA avec HotPDF en Delphi

HotPDF vérifie les signatures CMS ML-DSA-44, ML-DSA-65, ML-DSA-87, Ed25519 et Ed448 dans les documents PDF chargés, et signe via des fournisseurs enfichables afin que la clé privée n'ait jamais à vivre à l'intérieur de votre processus Delphi. Cette seconde moitié est la partie dont la plupart des équipes ont besoin en premier. Une clé matérielle sur jeton, un service de signature distant et une carte eID nationale refusent toutes de livrer une clé, et tant que le pipeline de signature n'est pas séparé du magasin de clés, aucune ne peut être utilisée du tout

La séparation est le propos de THPDFSignatureProvider. HotPDF garde les parties qu'il devrait posséder — analyse de CMS, construction de SignedData, disposition du /ByteRange — et délègue l'unique opération qu'il ne peut pas posséder, à savoir transformer un condensé en signature avec une clé qu'il n'a pas le droit de voir. Tout ce qui suit découle de cette division

Pourquoi une signature ML-DSA valide échoue-t-elle à la vérification ?

Parce que HotPDF refuse ML-DSA sur un document chargé qui ne déclare pas l'extension pour cela. ML-DSA — le schéma de signature sur réseau de treillis standardisé comme FIPS 204, et la raison pour laquelle on dit « PDF post-quantique » — n'a pas encore d'enregistrement ISO 32000-2. Un PDF qui en porte un utilise un algorithme que la norme de base ne nomme pas, et un fichier qui utilise silencieusement un algorithme non nommé est un fichier dont le verdict ne peut être reproduit par personne d'autre

Donc HotPDF rend la réclamation explicite. EnsureMLDSAExtensions élève le document à PDF 2.0 quand c'est permis et écrit /Extensions /HotPDF << /BaseVersion /2.0 /ExtensionLevel 1 >> dans le Catalog. Côté lecture, LoadedDocumentDeclaresMLDSAExtension signale si cette déclaration a survécu, et VerifyLoadedSignatureWithOptions applique le même test avant d'honorer Options.AllowMLDSA. Activez l'option sur un document non déclaré et elle reste éteinte — l'option peut desserrer la politique, jamais l'exigence structurelle

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;

Appelez-le avant l'enregistrement, pas après. La déclaration fait partie de la plage d'octets signée, et un Catalog patché ensuite est soit un changement non signé sur un fichier signé, soit une seconde révision qu'un validateur signalera comme modification

Trois familles d'algorithmes, un point d'entrée de vérification

Les trois familles arrivent toutes via VerifyLoadedSignatureWithOptions, qui prend un index de signature, le flux source, un enregistrement THPDFCMSVerifyOptions et un paramètre out pour les détails de la signature. L enregistrement a exactement trois champs, et chacun répond à une question qui exigeait autrefois une reconstruction

SignatureProvider substitue votre propre fournisseur à celui de plateforme intégré. OpenSSLLibraryPath sélectionne une bibliothèque OpenSSL 3, ce qui fournit la vérification Ed25519 et Ed448 en mode pur que Windows CNG n'offre pas partout. AllowMLDSA opte pour les algorithmes sur réseau de treillis, sous réserve du contrôle d'extension ci-dessus. L OID d'algorithme exact qui a été reconnu revient dans THPDFSignatureInfo.SignatureAlgorithmOID, donc un journal d'audit peut enregistrer ce qui a été vérifié plutôt que ce qui a été demandé

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 et Ed448 n'ont besoin d'aucune déclaration d'extension, car ISO 32000-2 les admet déjà. Ils ont besoin d'un fournisseur qui les implémente, ce qui sur la plupart des déploiements Windows signifie pointer OpenSSLLibraryPath vers une bibliothèque que vous livrez et contrôlez plutôt que vers ce qui se trouve sur la machine

Que promet réellement un fournisseur de signature ?

Un fournisseur promet une seule chose : étant donné une requête, renvoyer un état et, lors de la signature, des octets. THPDFSignatureProviderRequest porte l'algorithme et son OID, l'OID du condensé, la longueur du sel PSS, si l'entrée est un message ou un condensé déjà calculé, l'entrée elle-même, la clé publique ou le certificat, un identifiant de clé et un identifiant d'opération. Rien dans cet enregistrement n'est spécifique à HotPDF — c'est le vocabulaire qu'un pilote de jeton ou qu'un service de signature parle déjà

Trois implémentations sont livrées avec la bibliothèque. THPDFCallbackSignatureProvider encapsule des méthodes anonymes, ce qui est le plus court chemin d'une routine de signature interne existante vers une signature PDF fonctionnelle. THPDFRemoteSignatureProvider encapsule un rappel de transport avec une limite de tentatives, un registre d'annulation et des bornes sur la taille d'entrée et de signature, donc un HSM bloqué ne peut pas devenir une application bloquée. THPDFPKCS11SignatureProvider sérialise des opérations RSA contre une session PKCS#11 possédée par l'appelant et déjà authentifiée et un handle de clé privée — HotPDF ne se connecte jamais, ne voit jamais de PIN et ne ferme jamais une session qu'il n'a pas ouverte

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;

Pourquoi l'enum d'état a six valeurs au lieu d'un booléen

THPDFSignatureProviderStatus distingue spsValid, spsInvalid, spsUnsupported, spsMalformed, spsProviderError et spsCancelled, et les fusionner vous coûte la capacité d'agir correctement. Une signature cryptographiquement fausse (spsInvalid) est un événement de sécurité. Un algorithme que le fournisseur n'implémente pas (spsUnsupported) est une lacune de déploiement. Une défaillance de transport (spsProviderError) mérite une nouvelle tentative, et une invite de jeton annulée par l'utilisateur (spsCancelled) ne mérite aucune nouvelle tentative du tout

La règle pour la signature est étroite : un fournisseur de signature ne renvoie spsValid qu'avec une signature non vide. Les fournisseurs de vérification renvoient spsValid ou spsInvalid, et les quatre autres restent distincts sur les deux chemins. Si vous écrivez un fournisseur, résistez à la tentation de tout mapper sur spsInvalid ce que vous ne reconnaissez pas — cela transforme une DLL manquante en un rapport selon lequel la signature du client est forgée

Où la signature atterrit réellement dans le fichier

Deux fonctions relient les fournisseurs aux octets PDF réels. HPDFCMSBuildSignedDataWithProvider construit un CMS détaché depuis un condensé SHA-256 du document, ce qui est le bon point d'entrée quand votre flux calcule le condensé ailleurs. HPDFCMSSignPDFStreamWithProvider signe un emplacement de signature existant dans un flux PDF et préserve le pipeline /ByteRange standard, ce qui est le bon point d'entrée quand HotPDF a disposé l'emplacement lui-même

Préserver ce pipeline compte plus qu'il n'y paraît. La convention /ByteRange — deux plages qui sautent la fenêtre de signature hexadécimale — est ce que tout validateur vérifie en premier, et un chemin à base de fournisseur qui la réécrirait casserait la conformité PAdES quelle que soit la solidité de la cryptographie. HotPDF garde la disposition identique au chemin de signature intégré, donc un document signé via un jeton PKCS#11 se vérifie avec le même code de vérification de signature qu'un signé depuis un fichier PFX. Pour les règles de profil qui se trouvent au-dessus du choix d'algorithme, voir la visite guidée des signatures PAdES de référence en Delphi, et pour les pièges d'encodage spécifiques à ECDSA qui précèdent ce modèle de fournisseur, les notes sur vérification CMS ECDSA et formats de signature P1363

Un ordre de migration qui ne laisse pas vos documents en plan

La préparation post-quantique est un problème de calendrier, pas un commutateur. Pratiquement aucun lecteur PDF déployé ne valide ML-DSA aujourd hui, donc un document signé avec lui seul est, du point de vue du lecteur, un document à signature invérifiable. L ordre qui survit au contact des archives réelles est : garder RSA ou ECDSA comme signature qu'un validateur jugera, ajouter la déclaration d'extension et une seconde signature ML-DSA là où une politique exige une preuve résistante au quantique, et ne déplacer la signature principale que lorsque les systèmes consommateurs auront rattrapé leur retard

Ce que HotPDF vous donne aujourd hui, c'est la capacité d'écrire et de vérifier les deux, depuis le même code, avec l'algorithme enregistré honnêtement dans le fichier et dans le résultat de vérification. HotPDF est un composant PDF VCL natif pour Delphi et C++Builder sans aucun moteur PDF externe, donc les chemins de signature et de vérification sont livrés à l'intérieur de votre exécutable plutôt qu'à côté — voir la page composant PDF Delphi HotPDF pour la liste complète des fonctionnalités et l'essai téléchargeable