Technisch artikel

NIST-curve-rekenkunde in puur Pascal voor PDF-ondertekening

HotPDF voert elliptische-curve-sleutelovereenkomst en handtekeningverificatie voor PDF uit in puur Object Pascal, zonder OpenSSL-binding en zonder crypto-provider van het platform in het pad. Dat omvat vijf curven: P-256, P-384 en P-521 voor de NIST-primefamilies, plus X25519 en X448 voor sleutelovereenkomst op Montgomery-curven. De reden om die code te schrijven in plaats van te linken is uitrol, niet zuiverheid. Een Delphi- of Free Pascal-applicatie die één uitvoerbaar bestand verscheept en geen cryptografische DLL heeft, heeft geen versie-scheefstand te beheren, geen provider per platform te detecteren, en niets dat gedrag verandert wanneer een klant zijn systeembibliotheken patcht

De prijs is dat u nu de rekenkunde bezit. Modulaire vermenigvuldiging van big integers is genadeloze code: die levert ofwel byte-identieke resultaten tegen gepubliceerde testvectoren, ofwel plausibel ogende rommel, en de afstand tussen die twee toestanden kan één enkele vergelijking zijn. Dit is het verhaal van die vergelijking, want de vorm van de bug veralgemeent naar elke Pascal-portering van veldrekenkunde

Waarom heeft een PDF-bibliotheek eigenlijk curve-rekenkunde nodig?

Twee functies trekken die erin. De eerste is openbare-sleutelversleuteling van documenten: de recipient-list-handler van ISO 32000 verpakt een sleutel per document voor benoemde certificaten, en wanneer een ontvanger een EC-sleutel houdt verloopt het verpakken via sleutelovereenkomst in plaats van RSA-sleuteltransport. Zonder ECDH is er geen manier om zo'n document te openen. De tweede is handtekeningvalidatie. Het verifiëren van een ECDSA-handtekening over de /ByteRange-bytes vraagt een puntvermenigvuldiging op de ondertekenaarscurve, en P-384 is gangbaar in overheids- en gekwalificeerde-handtekeningprofielen waar P-256 als het vloerniveau geldt in plaats van het doel. HotPDF stelt de resultaten van dat werk beschikbaar via het ECDSA- en CMS-verificatiepad en via het pluggable signature-provider-model

Diagram waar HotPDF pure Pascal curve-rekenkunde wordt gebruikt: recipient-list ECDH-versleuteling en ECDSA-handtekeningverificatie over ByteRange
Sleutelovereenkomst opent EC-versleutelde documenten voor benoemde ontvangers, terwijl handtekeningvalidatie puntvermenigvuldiging op de ondertekenaarscurve vraagt

CIOS, en de ene aftrekking aan het eind

Montgomery-vermenigvuldiging vermijdt deling door te werken in een getransformeerd domein waar reductie een shift is. De variant die HotPDF gebruikt is Coarsely Integrated Operand Scanning, die vermenigvuldiging en reductie limb voor limb verweeft zodat de tussenwaarde nooit verder groeit dan de breedte van de modulus plus één limb. De lusbody is eenvoudig en makkelijk te testen. De staart niet: na de verweven passen kan de accumulator overal in het bereik tot tweemaal de modulus liggen, dus het algoritme eindigt met een conditionele aftrekking die één exemplaar van de priem weghaalt als en alleen als de accumulator groter of gelijk eraan is

Twee getallen met meerdere limbs vergelijken betekent vanaf de meest significante limb naar beneden wandelen met een borrow onderweg. De voor de hand liggende schrijfwijze is de accumulator-limb vergelijken met de modulus-limb plus de binnenkomende borrow. Die uitdrukking is fout, en fout op een manier die de meeste curven verbergen

// Fout: P[I] + Borrow kan omslaan wanneer P[I] $FFFFFFFFFFFFFFFF is
if T[I] < P[I] + Borrow then
begin
  Borrow := 1;
  Break;
end;

// Correct: vergelijk zonder ooit bij een limb op te tellen
if (T[I] < P[I]) or ((T[I] = P[I]) and (Borrow = 1)) then
begin
  Borrow := 1;
  Break;
end;

Hoe ziet een borrow-omslag er eigenlijk uit?

Het ziet eruit als een curve die overal werkt behalve in productie. De priems voor P-384 en P-521 bevatten limbs die volledig enen zijn, dus P[I] is gelijk aan $FFFFFFFFFFFFFFFF. Tel daar de binnenkomende borrow van één bij op en een 64-bit unsigned slaat om naar nul. De vergelijking vraagt zich dan af of de accumulator-limb kleiner dan nul is, besluit van niet, en concludeert dat geen borrow nodig is. Eén limb van het resultaat zit er één naast

Diagram van borrow-omslag in Montgomery-reductie die de verkeerde limb-vergelijking afzet tegen correcte borrow-propagatie in HotPDF P-384-rekenkunde
De borrow bij een limb van volledige enen optellen slaat naar nul, dus P-384 en P-521 krijgen geen aftrekking terwijl P-256 het defect verbergt

P-256 ontsnapt omdat geen van zijn limbs volledig enen is, dus de optelling loopt nooit over en de foute uitdrukking valt toevallig samen met de juiste. Dat is de slechtst denkbare uitkomst voor een testsuite: de meest geteste curve slaagt, de minder geteste falen intermitterend afhankelijk van operandwaarden, en de fout komt naar boven als een verificatieresultaat "ongeldige handtekening" op documenten die volkomen geldig zijn. HotPDF droeg om precies deze reden een expliciete afsluiting op P-384, die een status niet-beschikbaar teruggaf in plaats van een verkeerd antwoord, tot de rekenkunde tegen referentievectoren was bewezen

Hoe de bug werkelijk werd gelokaliseerd

Niet door de code te lezen. De productieve reeks was mechanisch en is herbruikbaar. Ten eerste: elimineer de constanten: elke limb van p, R en R^2 werd onafhankelijk geregenereerd en limb voor limb vergeleken, wat de meest voorkomende bron van curve-bugs uitsluit. Ten tweede: instrumenteer de rekenkunde in plaats van de API: een tijdelijke dump-procedure printte de tussenwaarden van de Montgomery-vermenigvuldiging van R^2, van x^3 en van y^2 voor een bekend punt, zodat die tegen onafhankelijk berekende waarheid gecontroleerd konden worden

Die vergelijking wees rechtstreeks naar de dader. De x-keten was van begin tot eind correct, terwijl y^2 in precies één limb met precies één verscheelde. Een verschil van één in één enkele limb is geen vermenigvuldigingsbug, geen carry-propagatiebug en geen constantenbug; het is een borrow-ketenbug, en de enige borrow-keten in de routine is de definitieve conditionele aftrekking. Eén detail haalde dit bijna uit de rails: de referentieconstante die voor de dump werd gebruikt was zelf bij de eerste poging in de verkeerde bytevolgorde opgeschreven, wat een mismatch in de y-waarde opleverde en kortstondig een tweede, niet-bestaand defect deed vermoeden. Controleer de endianness van uw grondwaarheid voordat u die uw code laat beschuldigen

Stroomdiagram van hoe de HotPDF-curvebug werd gelokaliseerd door constanten te hergenereren, Montgomery-tussenwaarden te dumpen en te diffen tegen mirror-waarheid
Een verschil van precies één in één limb wees rechtstreeks naar de enige borrow-keten in de routine, en een byte-gewisselde referentie stuurde de jacht bijna de verkeerde kant op

De buurvalkuilen in dezelfde routine

Nog drie faalmodussen wonen binnen een paar regels van die vergelijking, en alle drie waren ergens tijdens de ontwikkeling actief

// 1. De accumulator heeft één limb boven de breedte van de modulus. Alleen
//    de lage L limbs vergelijken mist het geval waarin T exact p plus 2^(64*L)
//    is, wat bij een wezenlijk deel van willekeurige inputs gebeurt omdat 2p
//    voor P-256 boven 2^256 en voor P-384 boven 2^384 uitkomt
if (T[L] <> 0) or NotLessThanModulus(T, P, L) then
  SubtractModulus(T, P, L);

// 2. Een generieke aftrekking met meerdere limbs heeft hetzelfde omslagrisico: wanneer
//    Y[I] $FFFFFFFFFFFFFFFF is, slaat Y[I] + Borrow naar nul en moet de
//    borrow naar de volgende limb overleven in plaats van gewist te worden
Diff := X[I] - Y[I] - Borrow;
NextBorrow := Ord((X[I] < Y[I]) or ((X[I] = Y[I]) and (Borrow = 1)));

De derde is geen code, het is herkomst. De priem voor P-521 werd aanvankelijk met 130 hexadecimale cijfers overgeschreven in plaats van 131, één F te weinig, en de Montgomery-constanten werden daarna uit die verkeerde priem berekend, dus de constanten waren zelfconsistent en gezamenlijk fout. Curveparameters moeten worden afgeleid, nooit getypt: bereken R als (1 shl (64 * L)) mod p vanuit de priem die u werkelijk gebruikt, en toets daarna R * R mod p tegen de waarde die uw R^2-constante beweert. Een paar constanten dat met elkaar overeenkomt bewijst niets over elk ervan

Verificatiestrategie die verder reikt dan één curve

De techniek die X25519 en X448 hanteerbaar maakte was het schrijven van een mirror-implementatie in een taal met onbegrensde integers en het regel voor regel transcriberen van de Pascal-controlestroom erin. Wanneer de mirror het juiste antwoord produceert en de Pascal niet, is het defect een transscriptieuitglijer en vindt het doorvragen van dezelfde tussenwaarde in beide implementaties die binnen seconden. Alle drie de klassieke RFC 7748-ladderfouten werden zo gevangen: een constant-time swap waarvan de tweede regel de al verwisselde waarde hergebruikte, een definitieve inversie die z tot de macht min één teruggaf in plaats van die in X te vermenigvuldigen, en een vermenigvuldiging met een kleine constante die halfwoordproducten met een bitwise or samenstelde en de carry verloor

Neem voor testmateriaal vectoren als bytes in plaats van als tekst. Een privésleutel met een tekstpatroon extraheren is precies hoe een correcte implementatie wordt beschuldigd van een off-by-one-byte-fout die volledig in de extractiestap leeft. Snijd de hex uit de DER-codering op bekende offsets en vergelijk byte-arrays

Met de borrow-keten gecorrigeerd komen alle vijf curven byte voor byte overeen met gepubliceerde referentievectoren, en HotPDF sluit er geen enkele meer af. Als u certificaatgebaseerde ondertekening of recipient-list-versleuteling integreert, is de praktische conclusie dat de curvekeuze nu een beleidsbeslissing is in plaats van een capaciteitsvraag; de profielen en bytevolgorde-valkuilen van de ondertekenkant worden behandeld in de PAdES-ondertekenwalkthrough. Componentdetails en de ondersteunde algoritmematrix staan op de productpagina van de HotPDF Delphi PDF component