HotPDF effectue l'accord de clés sur courbes elliptiques et la vérification de signatures pour le PDF en Object Pascal pur, sans liaison OpenSSL et sans fournisseur cryptographique de plateforme dans le chemin. Cela couvre cinq courbes : P-256, P-384 et P-521 pour les familles de nombres premiers NIST, plus X25519 et X448 pour l'accord de clés sur courbe de Montgomery. La raison d'écrire ce code plutôt que de le lier est le déploiement, pas la pureté. Une application Delphi ou Free Pascal qui livre un exécutable unique et aucune DLL cryptographique n'a aucun écart de version à gérer, aucun fournisseur par plateforme à détecter, et rien qui change de comportement quand un client corrige ses bibliothèques système
Le coût est que vous possédez maintenant l'arithmétique. La multiplication modulaire sur grands entiers est un code impitoyable : elle produit soit des résultats identiques à l'octet près contre les vecteurs de test publiés, soit des déchets d'apparence plausible, et la distance entre ces deux états peut être une seule comparaison. C'est l'histoire de cette comparaison, car la forme du bug se généralise à tout portage Pascal d'arithmétique de corps
Pourquoi une bibliothèque PDF a-t-elle besoin d'arithmétique de courbes ?
Deux fonctionnalités l'y amènent. La première est le chiffrement à clé publique de documents : le gestionnaire de liste de destinataires de l'ISO 32000 enveloppe une clé par document pour des certificats nommés, et quand un destinataire détient une clé EC, l'enveloppement passe par un accord de clés plutôt que par un transport de clé RSA. Sans ECDH, il n'y a aucun moyen d'ouvrir un tel document. La seconde est la validation de signatures. Vérifier une signature ECDSA sur les octets /ByteRange exige une multiplication de points sur la courbe du signataire, et P-384 est courant dans les profils gouvernementaux et de signature qualifiée où P-256 est considéré comme le plancher plutôt que la cible. HotPDF expose les résultats de ce travail par la voie de vérification ECDSA et CMS et par le modèle de fournisseur de signatures enfichable
CIOS, et la soustraction unique à la fin
La multiplication de Montgomery évite la division en travaillant dans un domaine transformé où la réduction est un décalage. La variante qu'utilise HotPDF est Coarsely Integrated Operand Scanning, qui entrelace la multiplication et la réduction limb par limb pour que l'intermédiaire ne dépasse jamais la largeur du module plus un limb. Le corps de boucle est direct et facile à tester. La queue ne l'est pas : après les passes entrelacées, l'accumulateur peut être n'importe où dans la plage jusqu'à deux fois le module, donc l'algorithme se termine par une soustraction conditionnelle qui retire une copie du premier si et seulement si l'accumulateur lui est supérieur ou égal
Comparer deux nombres multi-limbs signifie descendre depuis le limb le plus significatif en portant une retenue. La façon évidente de l'écrire est de comparer le limb de l'accumulateur au limb du module plus la retenue entrante. Cette expression est fausse, et elle est fausse d'une manière que la plupart des courbes cachent
// Faux : P[I] + Borrow peut boucler quand P[I] vaut $FFFFFFFFFFFFFFFF
if T[I] < P[I] + Borrow then
begin
Borrow := 1;
Break;
end;
// Juste : comparer sans jamais additionner à un limb
if (T[I] < P[I]) or ((T[I] = P[I]) and (Borrow = 1)) then
begin
Borrow := 1;
Break;
end;
À quoi ressemble réellement un bouclage de retenue ?
Cela ressemble à une courbe qui fonctionne partout sauf en production. Les premiers pour P-384 et P-521 contiennent des limbs entièrement à un, donc P[I] égale $FFFFFFFFFFFFFFFF. Ajoutez-y la retenue entrante de un et un non signé 64 bits boucle à zéro. La comparaison demande alors si le limb de l'accumulateur est inférieur à zéro, décide que non, et conclut qu'aucune retenue n'est nécessaire. Un limb du résultat est faux de un
P-256 échappe parce qu'aucun de ses limbs n'est tout à un, donc l'addition ne déborde jamais et l'expression boguée se trouve d'accord avec la correcte. C'est le pire résultat possible pour une suite de tests : la courbe la plus testée passe, les moins testées échouent par intermittence selon les valeurs des opérandes, et l'échec fait surface comme un résultat de vérification « signature invalide » sur des documents parfaitement valides. HotPDF portait un verrou explicite sur P-384 précisément pour cette raison, renvoyant un statut indisponible plutôt qu'une réponse fausse, jusqu'à ce que l'arithmétique soit prouvée contre des vecteurs de référence
Comment le bug a réellement été localisé
Pas en lisant le code. La séquence productive était mécanique, et elle est réutilisable. D'abord, éliminer les constantes : chaque limb de p, de R et de R^2 a été régénéré indépendamment et comparé limb par limb, ce qui écarte la source la plus courante de bugs de courbes. Ensuite, instrumenter l'arithmétique plutôt que l'API : une procédure de vidage temporaire imprimait les valeurs intermédiaires de la multiplication de Montgomery de R^2, de x^3 et de y^2 pour un point connu, pour qu'elles puissent être confrontées à une vérité calculée indépendamment
Cette comparaison pointait droit sur le coupable. La chaîne x était correcte de bout en bout, tandis que y^2 différait d'exactement un limb d'exactement un. Une différence d'un limb unique de un n'est ni un bug de multiplication, ni un bug de propagation de retenue, ni un bug de constante ; c'est un bug de chaîne de retenue, et la seule chaîne de retenue de la routine est la soustraction conditionnelle finale. Un détail a failli faire dérailler ceci : la constante de référence utilisée pour le vidage était elle-même écrite dans le mauvais ordre des octets à la première tentative, ce qui produisait une divergence sur la valeur y et suggérait brièvement un second défaut inexistant. Vérifiez le boutisme de votre vérité de terrain avant de la laisser accuser votre code
Les pièges voisins dans la même routine
Trois autres modes de défaillance vivent à quelques lignes de cette comparaison, et les trois ont été vivants à un moment du développement
// 1. L'accumulateur a un limb au-dessus de la largeur du module. Ne comparer
// que les L limbs bas rate le cas où T égale exactement p plus 2^(64*L),
// qui arrive pour une part significative des entrées aléatoires car 2p
// dépasse 2^256 pour P-256 et 2^384 pour P-384
if (T[L] <> 0) or NotLessThanModulus(T, P, L) then
SubtractModulus(T, P, L);
// 2. Une soustraction multi-limbs générique a le même risque de bouclage : quand
// Y[I] vaut $FFFFFFFFFFFFFFFF, Y[I] + Borrow boucle à zéro et la
// retenue doit survivre dans le limb suivant plutôt qu'être effacée
Diff := X[I] - Y[I] - Borrow;
NextBorrow := Ord((X[I] < Y[I]) or ((X[I] = Y[I]) and (Borrow = 1)));
Le troisième n'est pas du code, c'est la provenance. Le premier pour P-521 a d'abord été transcrit avec 130 chiffres hexadécimaux au lieu de 131, un F manquant, et les constantes de Montgomery ont ensuite été calculées à partir de ce premier faux, donc les constantes étaient auto-cohérentes et fausses ensemble. Les paramètres de courbe doivent être dérivés, jamais tapés : calculez R comme (1 shl (64 * L)) mod p à partir du premier que vous utilisez réellement, puis contre-vérifiez R * R mod p contre la valeur que votre constante R^2 prétend. Une paire de constantes qui s'accordent entre elles ne prouve rien sur aucune
Une stratégie de vérification qui passe à l'échelle au-delà d'une courbe
La technique qui a rendu X25519 et X448 maîtrisables était d'écrire une implémentation miroir dans un langage à entiers non bornés et de transcrire le flux de contrôle Pascal dedans ligne par ligne. Quand le miroir produit la bonne réponse et le Pascal non, le défaut est une erreur de transcription et sonder la même valeur intermédiaire dans les deux implémentations le trouve en quelques secondes. Les trois erreurs classiques de l'échelle RFC 7748 ont été attrapées ainsi : un échange à temps constant dont la seconde ligne réutilisait la valeur déjà échangée, une inversion finale qui renvoyait z à la puissance moins un au lieu de le multiplier dans X, et une multiplication par petite constante qui assemblait des produits de demi-mots avec un ou bit à bit et perdait la retenue
Pour le matériel de test, prenez les vecteurs en octets plutôt qu'en texte. Extraire une clé privée avec un motif de texte est la façon dont une implémentation correcte se fait accuser d'une erreur d'un octet qui vit entièrement dans l'étape d'extraction. Découpez l'hex dans l'encodage DER à des décalages connus et comparez des tableaux d'octets
Une fois la chaîne de retenue corrigée, les cinq courbes correspondent aux vecteurs de référence publiés octet pour octet, et HotPDF ne verrouille plus aucune d'elles. Si vous intégrez une signature à base de certificats ou un chiffrement par liste de destinataires, le constat pratique est que le choix de courbe est maintenant une décision de politique plutôt qu'une question de capacité ; les profils et les pièges d'ordre des octets du côté signature sont couverts dans le guide de signature PAdES. Les détails du composant et la matrice des algorithmes pris en charge sont sur la page produit du HotPDF Delphi PDF component