HotPDF preverja PDF MAC po ISO/TS 32004 na revizijo in ne na datoteko. THotPDF.ValidatePDFMACChain obhodi vsako inkrementalno posodobitev od sidra verige naprej in preveri vsak MAC proti toku predpone, ki je samo za branje in se konča pri lastnem startxref in %%EOF te revizije. En veljaven MAC na najnovejši reviziji ne dokazuje nič o revizijah pod njo
Tu je scenarij, ki vse to motivira. Dobavite AES-256 šifriran PDF s PDF MAC na njem. Nekdo odpre datoteko v šestnajstiškem urejevalniku, obrne bajt znotraj prve revizije, zaščitene z MAC, nato pa pripne povsem novo revizijo, ki nosi povsem veljaven MAC po lastni meri. Vsak pregledovalnik odpre datoteko brez pritožb in naivni preverjevalnik, ki zgosti trenutni bajtni razpon proti MAC v dejavnem trailerju, sporoči uspeh — ker je ta MAC res pravilen za bajte, ki jih pokriva. Škoda sede dve reviziji nižje, na območju, ki ga nihče ni znova preveril
Zakaj veljaven MAC najvišje ravni ne dokazuje, da je datoteka nedotaknjena?
Ker PDF MAC pokriva predpono in ne dokumenta. Inkrementalna posodobitev je enakovreden del formata: vsako shranjevanje pripne novo telo, nov odsek navzkrižnih referenc in nov trailer, starejši bajti pa ostanejo točno tam, kjer so bili. ISO/TS 32004 je peljan na tem modelu, zato vsaka revizija nosi svoj slovar /AuthCode, ki overi datoteko, kot je stala v tistem trenutku, preverjanje le najnovejše pa pusti vsako prejšnjo revizijo nepregledano. HotPDF zato izpostavi dve vprašanji kot dva klica, razlika med njima pa je celoten smisel tega članka. ValidatePDFMAC odgovori na »ali je trenutna revizija pristna« in zapolni zapis THPDFPDFMACValidationInfo; ValidatePDFMACChain odgovori na »ali je vsaka revizija, zaščitena z MAC, v tej datoteki pristna« in zapolni THPDFPDFMACChainValidationInfo s tabelo po revizijah ter strojem berljivim razlogom odpovedi. Na zgoraj opisani datoteki, ki je bila poškodovana in znova opremljena z MAC, prvi klic vrne True, drugi pa False pri indeksu revizije 1
var
Pdf: THotPDF;
Chain: THPDFPDFMACChainValidationInfo;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if not Pdf.ValidatePDFMACChain('incoming.pdf', 'user', Chain) then
begin
// Odpoved je ena od pmcfRevisionBoundary, pmcfNoPDFMAC,
// pmcfRequiredRevisionMissing, pmcfRevisionInvalid,
// pmcfKDFSaltChanged, pmcfDigestDowngrade, pmcfPermissionDowngrade
Writeln('chain rejected: ', Chain.Message);
Writeln('revision ', Chain.FailureRevisionIndex,
' at xref offset ', Chain.FailureXRefOffset);
Exit;
end;
for I := 0 to High(Chain.Revisions) do
Writeln(I, ' len=', Chain.Revisions[I].RevisionLength,
' mac=', Chain.Revisions[I].HasPDFMAC,
' perms=', Chain.Revisions[I].PermissionsAuthenticated);
finally
Pdf.Free;
end;
end;
Vsak MAC se preveri na svojem predponskem toku, nikoli na končni dolžini datoteke
Najdražja napaka na tem področju je uporaba končne velikosti datoteke kot zgornje meje pri ponovni zgostitvi starejše revizije, kar zvije končne bajte v vsako zgostitev razen najnovejše in sporoča poškodbo na zdravi datoteki. HotPDF namesto tega za vsako revizijo rekonstruira omejen tok samo za branje, ki se konča pri lastni vrednosti startxref te revizije, za katero sledi njen %%EOF, in zgosti le tega. Najti mejo je bolj zahtevno, kot se zdi: dobesedni %%EOF se lahko pojavi znotraj toka vsebine ali niza, zato se kandidat sprejme le, ko se neposredno predhodni startxref razčleni v število, enako odmiku navzkrižne reference odseka, ki se preverja, med njima pa ni ničesar drugega kot beli prostor. Revizija nato vsrka točno eno zaporedje konca vrstice za markerjem — en sam CR, en sam LF ali en par CRLF — in nič več. To zadnje pravilo se v praksi pozna, ker zapisovalnik, ki izda dodatno prazno vrstico med dvema revizijama, proizvede bajte, ki pripadajo naslednji reviziji; pogoltniti ves končni beli prostor v prejšnjo pa tiho spremeni obe zgostitvi. Številčenje odsekov sledi isti disciplini: HotPDF obhodi odseke navzkrižnih referenc od najstarejše do najnovejše točno enkrat, ponovno predvaja vnose free, direct in object-stream, da poznejši odseki prepišejo prejšnje stanje, kar je nasprotje semantiki first-seen-wins, ki jo uporablja razčlenjevalnik dejavnih xref
Kje se veriga zasidra in kaj jo prelomi?
Prva revizija z veljavnim /AuthCode je sidro, FirstMACRevisionIndex pa poroča, kjer se zaščita začne; vse pred njo je po zasnovi nezaščiteno, kar je normalno. Vse po njej mora biti zaščiteno z MAC, zato pripenjanje ene same običajne inkrementalne posodobitve k datoteki, zaščiteni z MAC, odpove s pmcfRequiredRevisionMissing in indeksom krive revizije — dopustiti vrzel bi napadalcu dovolila odstraniti zaščito preprosto z enim dodatnim shranjevanjem. Trije dodatni invarianti veljajo čez celo verigo, vsak s svojo kodo odpovedi
pmcfKDFSaltChanged—/KDFSaltmora ostati stabilen od sidra naprej, saj bi vrteča sol pustila ponarejalcu, da znova izpelje ključe pod parametri po lastni izbiripmcfDigestDowngrade— moč zgostitve se primerja z zadnjim preverjenim MAC in ne z neposredno predhodno revizijo, zato veriga, ki se začne pod profilom Modern pri SHA-384, ne more tiho nadaljevati s SHA-256pmcfPermissionDowngrade— revizija ne sme izbrisati zahteve po PDF MAC, ki jo je overila prejšnja revizija
Posledica, vredna notranje usvojitve, je, da se zgodovinski MAC-ji preverjajo neodvisno, tudi ko niso več dejavni trailer. Zato napad »popravi staro revizijo, nato pripni svež MAC« iz uvoda ne preživi: najnovejši MAC preveri sam po sebi, ValidatePDFMAC je zadovoljen, veriga pa še vedno pristane na reviziji 1 s pmcfRevisionInvalid
Vrstni red podpisov: najprej ključi trailerja, signatureDigest nazadnje
Ko je MAC pripet podpisu CMS in ne stoji sam, vrstni red pisanja preneha biti stilsko vprašanje. HotPDF zahteva, da se /AuthCode, /KDFSalt, razvijalčeva razširitev ISO 32004 in /SigObjRef zapišejo v isto revizijo preden se izračuna /ByteRange podpisa; karkoli od tega pripnete pozneje, tiste bajte pristanejo zunaj razpona, ki ga podpis pokriva, in nastane datoteka, katere podpis se preveri, vezava MAC pa je nepodpisana. Dve zgostitvi nato tečeta obratno, kar se na prvi pogled zdi krožno in ni. PDF MAC signatureDigest veže surove vsebinske oktete CMS SignerInfo.signature OCTET STRING — ne celotnega CMS DER in ne podpisanih atributov — zato se sestavi, ko surova vrednost podpisa že obstaja, in vbrizga kot nepodpisan atribut id-attr-pdfMacData. Ker je /Contents izključen iz ByteRange podpisa in nepodpisani atributi nikoli ne vstopajo v izračun podpisa, se zaporedje ustvari podpis, zgradi MAC, ovij CMS lepo zapre brez kriptografske zanke. Sledita dve korolarji: čuvaj /ByteRange in vstavek /Contents morata ostati v čistem besedilu in zunaj tokov predmetov, tudi v šifrirani datoteki, sicer jih popravilec fiksne širine ne najde; ko je tudi MAC zgostitev SHA-256, se podpisna zgostitev povsem ponovno uporabi, sicer pa se oba konteksta zgostitev posodobita v enem samem prehodu čez izhodni tok
var
Pdf: THotPDF;
Options: THPDFPDFMACOptions;
Info: THPDFPDFMACValidationInfo;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.FileName := 'unsigned.pdf';
Pdf.ActivateProtection := True;
Pdf.CryptKeyLength := aesgcm;
Pdf.OwnerPassword := 'owner';
Pdf.UserPassword := 'user';
Pdf.ProtectOptions := [prPrint, prExtractContent];
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 12);
Pdf.CurrentPage.AddSignedSignatureField('Approval',
Rect(72, 120, 280, 160), 16384);
Pdf.EndDoc;
Options := THPDFPDFMACOptions.Modern; // zgostitev dokumenta SHA-384
if THotPDF.SignPDFWithPFXAndAttachedPDFMAC('unsigned.pdf',
'signed.pdf', 'signer.pfx', 'pfx-secret', 'user', Options) then
if Pdf.ValidatePDFMAC('signed.pdf', 'user', Options, Info) then
begin
// Location = pmlAttachedToSignature, obe zgostitvi pa
// sta sporočeni ločeno
Writeln('signature object : ', Info.SignatureObjectNumber);
Writeln('signature digest : ', Info.SignatureDigestMatched);
Writeln('full file coverage: ', Info.FullFileCoverage);
Writeln('perms authentic : ', Info.PermissionsAuthenticated);
end;
finally
Pdf.Free;
end;
end;
Preverjanje ponovi isto pot od drugega konca: prebere neposredni /AuthCode iz trenutno dejavnega klasičnega trailerja navzkrižnih referenc, sledi posrednemu /SigObjRef, ki upošteva generacije, potrdi, da veže /V enega samega podpisnega polja, in sporoči odpoved dokumentne zgostitve ločeno od odpovedi podpisne zgostitve. To sta dve različni diagnozi, zrušiti ju v en sam boolean pa zavrže edino informacijo, ki pove, ali se je dotaknila vsebine strani ali vrednosti podpisa. Če se že ukvarjate z CMS, to stoji ob članku o podpisovanju PAdES in vodniku o preverjanju podpisov v naloženih dokumentih
Nikoli ne zaupajte /P: najprej dešifrirajte 16-bajtni /Perms
ISO/TS 32004 sporoča »ta dokument zahteva PDF MAC« prek dovolilnega bita 13, očiten način branja pa je napačen, ker je celo število /P v šifrirnem slovarju čisto besedilo in ni overjeno — kdorkoli lahko ta bit obrne v urejevalniku besedil in zniža zahtevo. ISO 32000-2 §7.6 prinese odgovor v vnosu /Perms, HotPDF pa ga uporabi: dešifrirajte 16-bajtni niz /Perms s šifrirnim ključem datoteke pod AES-256 CBC, ničelnim IV, brez blazinjenja, nato pa preverite vsako polje čistega besedila, preden čemu verjamete. Bajti 1 do 4 hranijo dovolilno vrednost v vrstnem redu little-endian in morajo točno enako celemu številu /P; bajti 5 do 8 so 0xFF; bajt 9 je zastavica šifriranja metapodatkov T ali F; bajti 10 do 12 so dobesedni marker adb. Šele ko vse to velja, PermissionsAuthenticated postane True in se prebere bit 13 — pazljivo bodite pri njegovi polarnosti, saj je zahteva po MAC uveljavljena, ko je bit 0x1000 izklopljen. Neskladje med /P in dešifriranimi dovoljenji ni opozorilo za zapis in mimo; to je ponarejen nabor dovoljenj, pravi odziv pa je odpoved varno
Okretnost algoritmov se ustavi pri zgostitvi
ISO/TS 32004 vam pusti izbrati dokumentno zgostitev in samo dokumentno zgostitev. HotPDF obdrži HMAC-SHA-256 za overjanje, HKDF-SHA-256 po RFC 5869 za izpeljavo ključev in AES-256 key wrap po RFC 3394, fiksirana pod spremenljivim THPDFPDFMACDigestAlgorithm, ki sega od pmdaSHA256 do pmdaSHA3_512, ker je naravna napaka obravnavati »profil SHA3-512« kot licenco za zamenjavo tudi HMAC, kar da datoteko, ki v nobenem interoperabilnem smislu ni več PDF MAC. Ena podrobnost implementacije je vredna kopiranja, če pišete svoj preverjevalnik: preberite OID zgostitve iz CMS AuthenticatedData preden zgostite bajtni razpon, saj trdo kodiranje SHA-256 in usklajevanje potem spreta okretnost v oznako in pusti sovražni datoteki, da vas spravi do toka celotnega dokumenta, preden odkrijete, da algoritem nikoli ni bil podprt. CMSAlgorithmProtection, algoritem zgostitve AuthenticatedData, messageDigest integritetnih informacij in zgostitev bajtnega razpona morajo vsi imenovati en algoritem, vsako nesoglasje pa odpove varno
var
Options: THPDFPDFMACOptions;
begin
Options := THPDFPDFMACOptions.Compatibility; // SHA-256, sprejme vseh šest
Options := THPDFPDFMACOptions.Modern; // SHA-384, zavrne 256-bitne
Options := THPDFPDFMACOptions.HighAssurance; // le SHA3-512, AES-GCM
// Profil po meri je dovoljen, a algoritem, s katerim generira,
// mora biti tudi na dovolilnem seznamu preverjanja, sicer je
// nastavitev zavrnjena, preden se zapiše en sam bajt
Options.Profile := pmppCustom;
Options.DigestAlgorithm := pmdaSHA512;
Options.AllowedDigestAlgorithms := [pmdaSHA512, pmdaSHA3_512];
Options.RequireAESGCM := True;
end;
Kaj PDF MAC dokazuje in kaj ne
Preverjena veriga PDF MAC dokazuje, da je vsaka zaščitena revizija bajtno enaka tistemu, kar je zapisal nekdo s šifrirnim ključem datoteke, da nobena zaščitena revizija ni bila odstranjena ali prerazporejena in da za sidrom ni bila pripeta nobena nezaščitena revizija — točno razred napadov, ki ga odprto pušča navadno šifriranje AES-256, saj zaupnost ne pove nič o celovitosti in se šifriran PDF z vzidano revizijo dešifrira enako radodarno kot nedotaknjen. Kar ne dokazuje, je avtorstvo. Ključ MAC se izpelje iz šifrirnega ključa datoteke, zato lahko kdorkoli, ki zna odpreti dokument, naredi tudi veljaven MAC nad spremenjeno različico, vsak zakoniti prejemnik vključno; to je simetrična sestavina, simetrične sestavine pa ne znajo pripisati avtorstva. Če morate vedeti kdo je kaj spremenil, potrebujete digitalni podpis s certifikatom za njim, PDF MAC pa ga nato dopolni z zaščito inkrementalne strukture, ki je podpis sam ne pokriva. Obravnavajte ju kot plasti in naj se dve sodbi sporočita neodvisno in ne zrušita v eno ikono stanja
Vstopne točke PDF MAC, opisane tukaj — AddStandalonePDFMAC, SignPDFWithPFXAndAttachedPDFMAC, ValidatePDFMAC in ValidatePDFMACChain — prihajajo s standardno komponento HotPDF Delphi Component za Delphi in C++Builder, kjer stran izdelka nosi celotno referenco za zapis možnosti, enumeracije stanj in tabelo preverjanja po revizijah