Article technique

Signature PAdES distante PDFium VCL : HSM et cloud

PDFiumPas scinde la signature PAdES en deux appels afin que la clé privée n'ait jamais besoin d'être dans votre processus. PreparePadesRemoteSignature écrit une mise à jour incrémentielle avec un espace réservé /Contents vide de largeur fixe et renvoie un enregistrement de requête portant le condensé SHA-256 du document, le ByteRange exact et une empreinte du fichier préparé. CompletePadesRemoteSignature prend le CMS détaché que renvoie votre service de signature et le dépose dans cet emplacement réservé

Entre ces deux appels, des minutes ou des heures peuvent s'écouler, le processus peut redémarrer, et le travail peut se déplacer vers une autre machine. Cet intervalle est toute la raison pour laquelle l'API est conçue ainsi

Pourquoi une clé distante ne peut-elle pas utiliser l'appel de signature ordinaire ?

Parce que SignPadesBytes suppose que l'opération de signature se déroule à l'intérieur de l'appel. Elle construit la mise à jour incrémentielle, calcule le condensé sur le ByteRange, le signe, et écrit le résultat, le tout avant de retourner. C'est exactement ce qu'il faut quand la clé réside dans le magasin de certificats Windows ou dans un fichier PKCS#12 que vous avez chargé

C'est impossible quand la clé réside dans un HSM réseau, un dispositif de création de signature qualifié exploité par un prestataire de services de confiance, ou une API de signature cloud qui exige que l'utilisateur confirme sur un téléphone. Dans ces cas, la séquence n'est pas un appel de fonction, c'est une conversation : vous envoyez un condensé, autre chose authentifie un humain, et un CMS revient plus tard. Une API synchrone ne peut pas exprimer « plus tard » sans bloquer un thread sur une opération qui peut nécessiter un second facteur

Le protocole en deux phases

La phase un prépare le document. PDFiumPas ajoute le champ de signature et le dictionnaire de valeur, réserve ContentsSize octets d'espace encodé en hexadécimal dans /Contents, calcule le ByteRange autour de cette réservation, et produit un TPadesRemoteSigningRequest contenant FormatVersion, PreparedFingerprint, DocumentDigest, le ByteRange à quatre éléments, ContentsHexOffset et ContentsSize

La seule valeur dont votre service de signature a besoin est DocumentDigest : le SHA-256 que le SignedData CAdES renvoyé doit porter comme condensé de message. Tout le reste dans l'enregistrement existe pour que la phase deux puisse prouver que le fichier qu'elle complète est bien le fichier à partir duquel ce condensé a été calculé

uses
  FPdfPades;

var
  Options: TPadesRemoteSignOptions;
  Request: TPadesRemoteSigningRequest;
  Source, Prepared, Session: TFileStream;
begin
  Options := TPadesRemoteSignOptions.Default;
  Options.Reason := 'Approved by finance';
  Options.Location := 'Lisbon';
  Options.Name := 'A. Moreira';
  Options.SigningTimeUtc := NowUtc;
  Options.ContentsSize := 16384;   // octets hexadécimaux réservés pour le CMS

  Source := TFileStream.Create('contract.pdf', fmOpenRead or fmShareDenyWrite);
  Prepared := TFileStream.Create('contract.prepared.pdf', fmCreate);
  try
    PreparePadesRemoteSignature(Source, Prepared, Options, Request);
  finally
    Prepared.Free;
    Source.Free;
  end;

  // Persister la session pour qu'une exécution ultérieure - ou une autre machine - puisse la terminer
  Session := TFileStream.Create('contract.signreq', fmCreate);
  try
    SavePadesRemoteSigningRequest(Session, Request);
  finally
    Session.Free;
  end;

  SendDigestToSigningService(Request.DocumentDigest);
end;

Que refuse Complete, et pourquoi chaque contrôle existe-t-il ?

La complétion est l'endroit où une conception de signature distante échoue habituellement, si bien que la validation est délibérément intransigeante. CompletePadesRemoteSignature rejette un PDF préparé dont l'empreinte ne correspond plus à la requête, un ByteRange qui ne correspond pas aux coordonnées d'espace réservé enregistrées, des délimiteurs /Contents modifiés, un espace réservé qui n'est plus vide, un CMS plus grand que la réservation, un CMS qui n'est pas exactement une seule valeur DER, une forme de SignedData non prise en charge, un attribut signing-certificate-v2 manquant, et un CMS dont le condensé de message n'est pas égal au condensé du document préparé

Chacun de ces contrôles correspond à une défaillance réelle. Les contrôles d'empreinte et de ByteRange détectent le cas où quelqu'un a régénéré le fichier préparé entre les deux phases, ce qui produirait une signature qui se valide contre des octets que personne ne possède. Le contrôle d'espace réservé vide détecte la double complétion, où un second CMS est écrit par-dessus une signature qui existe déjà. Le contrôle de condensé de message détecte le cas le plus dangereux de tous : un CMS correctement formé mais signé sur un document différent, ce qu'on obtient quand une file mélange deux sessions de signature concurrentes. Sans lui, vous produiriez un fichier qui paraît signé et échoue à la validation partout, ou pire, qui porte l'approbation de quelqu'un d'autre

L'exigence signing-certificate-v2 relève de la conformité PAdES plutôt que de l'intégrité. ETSI EN 319 142 exige que le certificat de signature soit lié aux attributs signés, et un CMS dépourvu de cet attribut n'est pas une signature PAdES même s'il se vérifie cryptographiquement. Le rejeter à la complétion signifie que vous le découvrez ici, pas dans un rapport de validateur venant d'un client, un sujet approfondi dans pourquoi les validateurs rejettent les signatures PAdES

var
  Request: TPadesRemoteSigningRequest;
  Session, Prepared, Dest: TFileStream;
  CmsDer: TBytes;
begin
  Session := TFileStream.Create('contract.signreq', fmOpenRead);
  try
    Request := LoadPadesRemoteSigningRequest(Session);
  finally
    Session.Free;
  end;

  CmsDer := FetchDetachedCmsFromService;   // returned by the HSM or TSP

  Prepared := TFileStream.Create('contract.prepared.pdf', fmOpenRead);
  Dest := TFileStream.Create('contract.signed.pdf', fmCreate);
  try
    try
      CompletePadesRemoteSignature(Prepared, Dest, Request, CmsDer);
    except
      on E: EPadesCrypto do
        // Chaque rejet porte une raison précise ; journalisez-la mot pour mot
        FailSession(E.Message);
    end;
  finally
    Dest.Free;
    Prepared.Free;
  end;
end;

Franchir les frontières de processus et de machine

SavePadesRemoteSigningRequest et LoadPadesRemoteSigningRequest sérialisent la session via un format binaire versionné stable, ce qui rend la conception pratique et pas seulement correcte. Une application web peut préparer un document dans une requête, stocker le PDF préparé et le blob de session, renvoyer un condensé au navigateur pour une signature par carte à puce, et compléter le fichier dans un gestionnaire de requête complètement différent

Le champ FormatVersion est ce qui garde cela sûr à travers les mises à jour. Une session écrite par une version plus ancienne et chargée par une plus récente est reconnue ou rejetée explicitement, plutôt que d'être mal interprétée comme un enregistrement de forme différente. Si votre file peut conserver des sessions pendant des jours, traitez la version de format comme un fait opérationnel qui mérite d'être journalisé, pas comme un détail d'implémentation

Dimensionner l'espace réservé

ContentsSize est le seul paramètre auquel vous devez réfléchir, car il est fixé avant même que le CMS existe. Il compte la réservation encodée en hexadécimal, si bien qu'un CMS DER de 6 Ko nécessite au moins 12 Ko d'espace, et l'implémentation plafonne la réservation à 64 Mio

Réservez trop peu et la complétion échoue avec une erreur de CMS surdimensionné après que votre service de signature a déjà fait son travail, ce qui sur un service de signature qualifiée facturé à l'usage signifie une opération gaspillée. Réservez trop et chaque document signé porte le remplissage pour toujours. L'approche sensée consiste à mesurer : signez un document avec votre véritable chaîne de certificats, regardez la longueur DER, doublez-la pour l'hexadécimal, puis ajoutez une marge généreuse pour le jeton d'horodatage si vous comptez passer à une signature de niveau T. Les chaînes comportant plusieurs intermédiaires et une longue réponse OCSP grossissent plus vite qu'on ne l'imagine

Ce qui vient après la signature

Une signature distante complétée est PAdES B-B. La validation à long terme nécessite un horodatage et le matériel de validation, ce qui constitue une mise à jour incrémentielle distincte qui ajoute un DSS et ses dictionnaires VRI par signature, décrite dans les signatures à long terme avec horodatages RFC 3161 et DSS. Cette étape est locale : elle ajoute des certificats, des réponses OCSP et des CRL, dont aucun n'a besoin de la clé privée

Avant de livrer, vérifiez ce que vous avez produit avec le même chemin de code qu'utiliserait une partie utilisatrice, couvert dans l'inspection des signatures numériques et des niveaux PAdES. Signer et vérifier sont deux codes différents, et un pipeline de signature distante est exactement l'endroit où les deux peuvent diverger sans que personne ne le remarque jusqu'à ce qu'un validateur externe le signale

PDFiumPas est un composant Delphi et Lazarus autour du moteur PDFium avec une pile PAdES native en Pascal, si bien que la signature, l'horodatage et la validation fonctionnent sans outils en ligne de commande externes. La documentation API complète et une version d'essai se trouvent sur la page PDFium Delphi component