HotPDF, komponenta PDF za Delphi, zdaj odkloni ovijanje podpisov: od v2.759.0 tako VerifyLoadedSignatureEx kot paketni validator zahtevata, da je vrzel med dvema segmentoma /ByteRange točno šestnajstiški niz /Contents, vključno z ločili, v2.761.0 pa doda AddLoadedSignedSignatureField, tako da se lahko drugi podpis pripne že podpisanemu PDF kot čista priraščajoča revizija. Spremembi spadata skupaj, ker je pravilen drugi podpis točno tista postavitev, ki jo pričakuje strožji preverjevalnik
Situacija, ki je razkrila problem, je vsakdanja. Pogodbo podpiše prodajalec, nato odpotuje k odobravatelju, ki mora dopisati podpis, ne da bi motil prvega. Druga revizija se pripne za prvo, njen lastni /ByteRange se razteza čez celo zraslo datoteko, oba podpisa pa bi se morala preveriti. Priditi tja z roko je pomenilo napisati priraščajoči razdelek sami, preizkusna fiksna datoteka, ki je točno to storila, pa se je izkazala za učbeniško strukturo ovijanja podpisov, ki jo je stari preverjevalnik srečno sprejel. Če še niste gledali API preverjanja, vodnik o preverjanju digitalnih podpisov PDF s HotPDF pokrije osnove, na katerih ta članek gradi
Kaj točno spada v vrzel ByteRange?
Vrzel mora vsebovati celotno vrednost /Contents in nič drugega: ISO 32000-1 §12.8.3.3 pravi, da se šestnajstiški niz, s svojima ločiloma < in >, natanko prilega prostoru med obema bajtnima obsegoma, ISO 32000-2 §12.8.1 pa pravilo nosi naprej. Tabela 252 in dokumenti PAdES pravijo samo, da povzetek izključuje vrednost Contents, kar je lahko prebrati kot izključevanje samo šestnajstiških števk. Zgodnejše izdaje HotPDF so to prebrale tako: PreparePDFForSigning in tekoča priprava CMS sta hesirali tudi oglate oklepaje, s komentarjem v izvorni kodi, ki je vztrajal, da morajo biti oklepaji pokriti. Validatorji, ki primerjajo vrzel s podpisno vrednostjo, to postavitev označijo kot neveljaven bajtni obseg, zato v2.759.0 preseli oba ločila izven podpisanih obsegov. Hitri neodvisni preizkus na kateri koli podpisani datoteki je pogledati dva bajta: bajt pri odmiku ByteRange[1] mora biti < in bajt pri odmiku ByteRange[2] - 1 mora biti >
Zakaj preizkus ne-prazne vrzeli spregleda ovijanje podpisov?
Preizkus ne-prazne vrzeli dokazuje samo, da je bilo kaj izpuščeno iz povzetka, ne kaj, in to je celotna napadalna površina. Ogradero /Contents si rezervira s tisoči ničelnih števk, pravi vsebnik CMS pa jo redko napolni. Napadalec lahko zapre šestnajstiški niz zgodaj znotraj tega ničelnega oblazinjenja z >, zapiše nove objekte ali ponarejeno revizijo v preostanek rezerviranega prostora in pusti bajtne obsege pri miru. Podpis CMS se še vedno preveri, ker je vsak podpisani bajt nespremenjen, obsegi se še vedno začnejo pri 0 in končajo pri velikosti datoteke, stari preverjevalnik HotPDF pa je poročal svValid z CoversWholeDocument nastavljenim na True. Bralnik PDF medtem razčleni kar koli, kar sedi v tej nepodpisani luknji
HotPDF zdaj ravna z vrzeljo kot s podatki, ki jih je treba preveriti bajt za bajtom. Preverjevalnik prebere vrzel, odstrani ločila, sprejme samo šestnajstiške števke plus beli prostor PDF (tabulator, pomik v novo vrstico, pomik na novo stran, povratni voziček, presledek), dekodira števke in zahteva, da je rezultat točno enak /Contents slovarja podpisa. Karkoli drugega razvrsti rezultat na svInvalidByteRange. Preizkus teče tako v poti enojnega podpisa kot v ValidateLoadedSignatureBatch, ki je obdržal lastno logiko pokritosti in potreboval isti popravek. Datoteke, izdelane s HotPDF pred v2.759.0, katerih vrzel je držala samo števke z oklepaji, sedajočimi prav znotraj obsegov, se še vedno preverijo, zato arhivirani dokumenti ne postanejo nenadoma rdeči
var
Pdf: THotPDF;
Info: THPDFSignatureInfo;
Status: THPDFSignatureVerifyStatus;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('SignedTwice.pdf');
for I := 0 to Pdf.GetLoadedSignatureFieldCount - 1 do
begin
Status := Pdf.VerifyLoadedSignatureEx(I, Info);
case Status of
svValid:
if Info.CoversWholeDocument then
Writeln(Info.FieldName, ': valid, covers the whole file')
else
Writeln(Info.FieldName, ': valid, ',
Info.UnsignedTrailingBytes, ' bytes appended later');
svInvalidByteRange:
Writeln(Info.FieldName, ': ByteRange gap rejected (wrapping?)');
else
Writeln(Info.FieldName, ': failed, status ', Ord(Status));
end;
end;
finally
Pdf.Free;
end;
end;
Kako dodate drugi podpis že podpisanemu PDF?
Odprite podpisano datoteko z BeginIncrementalUpdate, pokličite AddLoadedSignedSignatureField, shranite s SaveIncrementalUpdate, pripravljeno datoteko pa podpišite s razredno funkcijo THotPDF.SignPDFWithPFX. Pred v2.761.0 dokumentirani recept klicanja THPDFPage.AddSignedSignatureField po BeginIncrementalUpdate ni mogel delovati, ker je CurrentPage v priraščajočem načinu nil in nič ni moglo pripeti ograderke /V k polju na naloženem dokumentu. Nova metoda ustvari gradnik na naloženi strani in obesi isti slovar ograderk, ki ga uporablja pot novega dokumenta, pod /V, zato si obe poti podpisovanja delita eno serializacijo. Za prvi podpis sam članek o ustvarjanju digitalnih podpisov PAdES v Delphiju prehodi cevovod PFX
var
Pdf: THotPDF;
FieldIndex: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.BeginIncrementalUpdate('Signed.pdf');
// Stran 0, pravokotnik gradnika v točkah, 8192 bajtov rezerviranih za CMS
FieldIndex := Pdf.AddLoadedSignedSignatureField(0, 320, 60, 520, 120,
'ApproverSignature', 8192);
if FieldIndex < 0 then
raise Exception.Create('Page index out of range');
Pdf.SaveIncrementalUpdate('Prepared.pdf');
finally
Pdf.Free;
end;
if not THotPDF.SignPDFWithPFX('Prepared.pdf', 'SignedTwice.pdf',
'approver.pfx', 'pfx-password') then
raise Exception.Create('Second signature failed');
end;
AddLoadedSignedSignatureField je namerno tišji od svojih sorojencev. Ostali ustvarjalci polj AddLoaded* nastavijo /NeedAppearances true na AcroForm, kar pove pregledovalniku, naj znova izdela videze polj; na podpisanem dokumentu ta ponovna izdelava lahko prepiše podpisano vsebino, zato nova metoda zastavico spet odstrani, razen če jo je izvirnik že nosil. /SigFlags obdrži svojo izvirno vrednost ALI 3 (SignaturesExist plus AppendOnly, ISO 32000-1 tabela 219). Tudi klicanja MarkDirty na strani ne potrebujete: dodajanje k /Annots in /Fields razširi umazano zastavico na lastniški posredni objekt, izrecna oznaka strani pa bi samo povlekla nespremenjen slovar strani v novo revizijo, ki jo analiza revizij nato poroča kot spremembo strani. Nazadnje ogradero zapiše /ByteRange pred /Contents, ker zalezovalec najprej najde čuvajoči /ByteRange in naprej išče ujemajoči šestnajstiški niz
Kaj se spremeni, ko CMS izdela zunanji podpisnik ali HSM?
V poteku dela se nič ne spremeni, odmiki pa zdaj pomenijo tisto, kar pravi specifikacija. PreparePDFForSigning vrne dva obsega od nič, katerih vrzel je celoten niz /Contents, ContentsHexStart pa je eno-osnovno kazalo prve šestnajstiške števke v AnsiString. Krajši CMS se oblazini z 0 na koncu, pred zaključnim >. Ker PreparePDFForSigning zakrpi prvi nezakrpani čuvajoči, ki ga najde, pripravite točno eno ogradero na revizijo in dajte prednost InsertSignatureHexAt z vrnjenimi odmiki pred iskanjem osnovanim InsertSignatureHex, kadar prejšnji podpisi že obstajajo v datoteki
var
Bytes, ToSign, CmsHex: AnsiString;
R1Start, R1Len, R2Start, R2Len, HexStart, HexLen: Integer;
begin
Bytes := LoadFileAsAnsiString('Prepared.pdf'); // vaš pomožnik
if not THotPDF.PreparePDFForSigning(Bytes, R1Start, R1Len,
R2Start, R2Len, HexStart, HexLen) then
raise Exception.Create('No signature placeholder found');
// Vrzel je celoten šestnajstiški niz: '<' konča obseg 1, '>' stoji pred obsegom 2
Assert(Bytes[R1Start + R1Len + 1] = '<');
Assert(Bytes[R2Start] = '>');
ToSign := Copy(Bytes, R1Start + 1, R1Len) + Copy(Bytes, R2Start + 1, R2Len);
CmsHex := SignDetachedWithHsm(ToSign); // vaš podpisnik CMS, šestnajstiški DER
if not THotPDF.InsertSignatureHexAt(Bytes, HexStart, HexLen, CmsHex) then
raise Exception.Create('CMS does not fit the reserved space');
SaveAnsiStringToFile(Bytes, 'SignedTwice.pdf'); // vaš pomožnik
end;
Kje so meje novih preizkusov?
Preizkus vrzeli zapre eno specifično luknjo in se ga ne smete predajati pretirano. svValid še vedno pomeni bajtno celovitost plus ključ, ki se ujema z vdelanim certifikatom; zaupanje temu certifikatu je ločena odločitev. Vrzel se preveri samo, kadar preverjevalnik ima izvorne bajte, ki jih VerifyLoadedSignatureEx bere iz naložene datoteke, preobremenitve TStream pa jih vzamejo od vas. Za prvi podpis v datoteki z dopisanim podpisom je CoversWholeDocument pravilno False, vprašanje, ali je pripeta revizija samo dodala podpis ali pa spremenila tudi strani, pa je za analizo DocMDP, FieldMDP in revizij v HotPDF. Upoštevajte tudi, da preizkus priloženega PDF MAC primerja odmike s položajema < in >, zato sprejme tako staro kot novo postavitev; vsako orodje po meri, ki trdo kodira odmike pred v2.759.0, bo odpovedalo najprej, ko sreča sveže podpisano datoteko
Če vaša aplikacija Delphi ali C++Builder podpisuje, dopisuje podpise ali revizira PDF, je najvarnejša pot pustiti eni knjižnici, da izdela in preveri isto postavitev. HotPDF, domorodna komponenta PDF za Delphi nosi strožjo validacijo vrzeli, priraščajoče druge podpise in kljuke za zunanje podpisnike, prikazane zgoraj, v eni sami komponenti