Tekninen artikkeli

PDF MAC -revisioketjun validointi Delphissä (ISO 32004)

HotPDF validoi ISO/TS 32004 PDF MAC -koodin revisiokohtaisesti eikä tiedostokohtaisesti. THotPDF.ValidatePDFMACChain kulkee jokaisen inkrementaalisen päivityksen läpi ketjun ankkurista eteenpäin ja varmistaa jokaisen MACin vain luku -oikeuden omaavaa etuliitevirtaa vasten, joka päättyy kyseisen revisioin omaan startxref-arvoon ja %%EOF-merkkiin. Uusimman revisioin kelvollinen MAC ei todista mitään sen alla olevista revisioista

Tässä on tilanne, joka motivoi kaikkea tätä. Toimitat AES-256-salatun PDF-tiedoston, jossa on PDF MAC. Joku avaa tiedoston heksaeditorissa, kääntää yhden tavun ensimmäisen MAC-suojatun revision sisällä ja liittää sitten kokonaan uuden revision, joka sisältää täysin kelvollisen oman MACin. Jokainen katselin avaa tiedoston valittamatta, ja naiivi tarkistin, joka tiivistää nykyisen tavualueen aktiivisen trailerin MACia vasten, raportoi onnistumisen — koska kyseinen MAC on oikeasti oikein kattamilleen tavuille. Vahinko istuu kaksi revisiota alempana, alueella, jota kukaan ei tarkistanut uudelleen

Miksi kelvollinen ylimmän tason MAC ei todista, että tiedosto on ehjä?

Koska PDF MAC kattaa etuliitteen, ei dokumenttia. Inkrementaalinen päivitys on muodon ensiluokkainen osa: jokainen tallennus liittää uuden rungon, uuden ristiviittausosion ja uuden trailerin, vanhemmat tavut pysyvät täsmälleen paikoillaan. ISO/TS 32004 nojaa tähän malliin, joten jokainen revisio kantaa omaa /AuthCode-sanakirjaansa, joka todentaa tiedoston sellaisena kuin se oli sillä hetkellä, ja vain uusimman varmistaminen jättää jokaisen aiemman revision tutkimatta. HotPDF näyttää siksi kaksi kysymystä kahtena kutsuna, ja niiden välinen ero on koko tämän artikkelin ydin. ValidatePDFMAC vastaa kysymykseen "onko nykyinen revisio aito" täyttämällä THPDFPDFMACValidationInfo-tietueen; ValidatePDFMACChain vastaa kysymykseen "onko jokainen tämän tiedoston MAC-suojattu revisio aito" täyttämällä THPDFPDFMACChainValidationInfo-tietueen revisikohtaisella taulukolla ja koneluettavalla vian syyllä. Yllä olevalla peukaloituneella ja uudelleen MACatulla tiedostolla ensimmäinen kutsu palauttaa True ja toinen palauttaa False revisioindeksille 1

var
  Pdf: THotPDF;
  Chain: THPDFPDFMACChainValidationInfo;
  I: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if not Pdf.ValidatePDFMACChain('incoming.pdf', 'user', Chain) then
    begin
      // Vika on jokin seuraavista: 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;

Jokainen MAC varmistetaan omalla etuliitevirrallaan, ei koskaan lopullisen tiedoston pituudella

Kallein virhe tällä alueella on lopullisen tiedostokoon käyttäminen ylärajana vanhempaa revisiota uudelleen tiivistettäessä, mikä taittaa lopputavut jokaiseen tiivisteeseen paitsi uusimpaan ja raportoi peukaloinnin terveessä tiedostossa. HotPDF rakentaa sen sijaan jokaiselle revisiolle rajatun vain luku -virran, joka päättyy kyseisen revisioin omaan startxref-arvoon ja sen jälkeiseen %%EOF-merkkiin, ja tiivistää vain sen. Rajan löytäminen on hankalampaa kuin näyttää: kirjaimellinen %%EOF voi esiintyä sisältövirran tai merkkijonon sisällä, joten ehdokas hyväksytään vain, kun sitä välittömästi edeltävä startxref jäsentyy luvuksi, joka on yhtä suuri kuin validoitavan osion ristiviittausoffset, eikä niiden välillä ole mitään muuta kuin tyhjämerkkejä. Revisio omaksuu sitten täsmälleen yhden rivinloppusarjan merkin jälkeen — yhden CR:n, yhden LF:n tai yhden CRLF-parin — eikä mitään muuta. Tämä viimeinen sääntö purraa käytännössä, koska kirjoittaja, joka tuottaa ylimääräisen tyhjän rivin kahden revision väliin, on tuottanut seuraavaan revisioon kuuluvia tavuja, ja kaiken lopputyhjämerkin nieleminen edelliseen muuttaa hiljaisesti molempia tiivisteitä. Osion luettelointi noudattaa samaa kuria: HotPDF kulkee ristiviittausosiot vanhimmasta uusimpaan täsmälleen kerran, toistaen vapaat, suorat ja objektivirtamerkinnät niin, että myöhemmät osiot ylikirjoittavat aiemman tilan, mikä on päinvastainen kuin aktiivisen xref-jäsentäjän ensin nähty voittaa -semantiikka

HotPDF varmistaa jokaisen ISO 32004 PDF MACin etuliitevirtaa vasten, joka päättyy kyseisen revisioin omaan startxref-arvoon ja tiedoston lopun merkkiin, joten revisioon 1 käännetty tavu kaataa ketjun vaikka uusin MAC varmistuisi vielä puhtaasti
Jokaisen revision MAC tiivistetään uudelleen omalla rajatulla etuliitteellään, joten revisioin 1 muokkaaminen ja tuoreella MACilla varustetun revision liittäminen tyydyttää edelleen ValidatePDFMACin, kun taas ValidatePDFMACChain pysähtyy revisioon 1

Mihin ketju ankkuroituu, ja mikä rikkoo sen?

Ensimmäinen kelvollisen /AuthCode-arvon kantava revisio on ankkuri, ja FirstMACRevisionIndex kertoo, missä suojaus alkaa; kaikki sen edessä on rakenteeltaan suojaamatonta, mikä on normaalia. Kaiken sen jälkeen on oltava MAC-suojattua, joten yhden tavallisen inkrementaalisen päivityksen liittäminen MAC-suojattuun tiedostoon epäonnistuu koodilla pmcfRequiredRevisionMissing ja syyllisen revision indeksillä — aukon sietäminen antaisi hyökkääjän riisua suojan yksinkertaisesti tallentamalla vielä kerran. Kolme muuta invariantia pätee koko ketjussa, jokaisella on oma vikakoodinsa

  • pmcfKDFSaltChanged/KDFSalt-arvon on pysyttävä vakaana ankkurista eteenpäin, koska kiertävä suola antaisi väärentäjän johtaa avaimet uudelleen itse valitsemillaan parametreilla
  • pmcfDigestDowngrade — tiivisteen vahvuutta verrataan viimeiseen varmistettuun MACiin eikä välittömästi edeltävään revisioon, joten Modern-profiilissa SHA-384:llä alkava ketju ei voi hiljaa jatkaa SHA-256:lla
  • pmcfPermissionDowngrade — revisio ei saa tyhjentää PDF MAC -vaatimusta, jonka aiempi revisio on todentanut

Opittavana seurauksena on, että historialliset MACit varmistetaan itsenäisesti silloinkin, kun ne eivät ole enää aktiivinen traileri. Siksi alun muokkaa-vanha-revisio-ja-liitä-tuore-MAC -hyökkäys ei selviä hengissä: uusin MAC läpäisee tarkistuksensa omallaan, ValidatePDFMAC on tyytyväinen, ja ketju pysähtyy silti revisioon 1 koodilla pmcfRevisionInvalid

Allekirjoitusjärjestys: trailerin avaimet ensin, signatureDigest viimeisenä

Kun MAC kiinnitetään CMS-allekirjoitukseen seisomisen sijaan yksin, kirjoitusjärjestys lakaa olemasta tyylikysymys. HotPDF vaatii, että /AuthCode, /KDFSalt, ISO 32004 -kehittäjälaajennus ja /SigObjRef kirjoitetaan samaan revisioon ennen kuin allekirjoituksen /ByteRange lasketaan; liitä mikä tahansa niistä jälkeenpäin, ja ne tavut osuvat allekirjoituksen kattaman alueen ulkopuolelle tuottaen tiedoston, jonka allekirjoitus varmistuu vaikka MAC-sidonta on allekirjoittamatta. Kaksi tiivistettä kulkevat sitten toiseen suuntaan, mikä näyttää ensi silmäyksellä kehältä eikä ole. PDF MACin signatureDigest sitoo CMS:n SignerInfo.signature OCTET STRINGin raakasisältöoktetit — ei koko CMS DERiä eikä allekirjoitettuja attribuutteja — joten se rakennetaan raakan allekirjoitusarvon olon jälkeen ja ruiskutetaan id-attr-pdfMacData-allekirjoittamattomana attribuuttina. Koska /Contents on suljettu allekirjoituksen ByteRangesta ja allekirjoittamattomat attribuutit eivät koskaan syötä allekirjoituslaskentaa, sarja tuota-allekirjoitus, rakenna-MAC, kääri-CMS sulkeutuu puhtaasti ilman kryptografista silmukkaa. Kaksi seurausta: /ByteRange-sentinelin ja /Contents-paikkamerkin on pysyttävä selväkielisinä ja objektivirtojen ulkopuolella salatussakin tiedostossa, muuten kiinteän leveyden korjaaja ei löydä niitä; ja kun MACin tiiviste on myös SHA-256, allekirjoitustiivistettä käytetään uudelleen sellaisenaan, muuten molemmat tiivistekontekstit päivitetään yhdellä läpiviennillä tulostusvirran yli

HotPDF:n kirjoitusjärjestys CMS-allekirjoitukseen kiinnitetylle PDF MACille: MACin avaimet tulevat revisioon ennen kuin ByteRange mitataan, ja allekirjoitiiviste rakennetaan sen jälkeen raaoista SignerInfo-allekirjoitusokteteista
AuthCode, KDFSalt, SigObjRef ja kehittäjälaajennuksen kirjoittaminen ennen kuin ByteRange mitataan pitää MAC-sidongan allekirjoituksen kattaman alueen sisällä
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;      // dokumenttiitiiviste 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, ja kaksi tiivistettä
        // raportoidaan erikseen
        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;

Validointi kulkee saman polun toisesta päästä: lue suora /AuthCode tällä hetkellä aktiivisesta klassisesta ristiviittaustrailerialta, seuraa sukupolvetietoista epäsuoraa /SigObjRef-arvoa, vahvista, että se sitoo ainoan allekirjoituskentän /V-arvon, ja raportoi dokumenttiitiivisteen vika erikseen allekirjoitiivisteen viasta. Nämä ovat eri diagnooseja, ja niiden kutistaminen yhdeksi totuusarvoksi heittää pois ainoan tiedon, joka kertoo, kosketettiinko sivun sisältöä vai allekirjoitusarvoa. Jos teet jo CMS-työtä, tämä istuu rinnakkain PAdES-allekirjoitusartikkelin ja ladattujen dokumenttien allekirjoitusten varmistamisen oppaan kanssa

Älä koskaan luota /P-arvoon: pura 16-tavuinen /Perms ensin

ISO/TS 32004 ilmoittaa "tämä dokumentti vaatii PDF MACin" käyttöoikeusbitin 13 kautta, ja ilmeinen tapa lukea se on väärä, koska salaus-sanakirjan /P-kokonaisluku on selväkielistä ja todentamatonta — kuka tahansa voi kääntää tuon bitin tekstieditorissa ja alentaa vaatimusta. ISO 32000-2 §7.6 tarjoaa vastauksen /Perms-merkinnässä, ja HotPDF käyttää sitä: pura 16-tavuinen /Perms-merkkijono tiedostonsalausavaimella AES-256 CBC:n alla, nolla-IV:llä, ei täytettä, ja tarkista sitten jokainen selkitekstin kenttä ennen kuin uskot mitään. Tavut 1–4 sisältävät käyttöoikeusarvon little-endian-järjestyksessä ja on oltava täsmälleen yhtä suuri kuin /P-kokonaisluku; tavut 5–8 ovat 0xFF; tavu 9 on T- tai F-metadatasalauslippu; tavut 10–12 ovat kirjaimellinen merkki adb. Vasta kun kaikki tämä pätee, PermissionsAuthenticated muuttuu Trueksi ja bitti 13 luetaan — ja huomaa sen polariteetti, koska MAC-vaatimus esitetään kun 0x1000-bitti on nollassa. Ristiriita /P:n ja purettujen käyttöoikeuksien välillä ei ole varoitus, jonka lokittaa ja ohittaa; se on väärennetty käyttöoikeusjoukko, ja oikea vastaus on epäonnistua suljettuun tilaan

HotPDF todentaa PDF-käyttöoikeudet puramalla 16-tavuisen Perms-merkkijonon tiedostonsalausavaimella ja tarkistamalla little-endian-käyttöoikeusarvon, FF-täytetavut, metadatalipun ja adb-merkin ennen bitin 13 lukemista
Selväkielinen /P-kokonaisluku on todentamaton, joten PDF MAC -vaatimus luetaan vasta, kun puretun /Perms:n jokainen kenttä on tarkistettu

Algoritmin ketteryys pysähtyy tiivisteeseen

ISO/TS 32004 antaa valita dokumenttiitiivisteen, ja vain dokumenttiitiivisteen. HotPDF pitää HMAC-SHA-256:n todentamiseen, HKDF-SHA-256:n RFC 5869:n mukaisesti avainten johtamiseen ja AES-256-avaimenkääreen RFC 3394:n mukaisesti kiinnitettynä muuttuvan THPDFPDFMACDigestAlgorithmin alle, joka kattaa pmdaSHA256:sta pmdaSHA3_512:een, koska luonnollinen virhe on tulkita "SHA3-512-profiili" luvaksi vaihtaa myös HMAC, mikä tuottaa tiedoston, joka ei ole enää PDF MAC missään yhteentoimivassa mielessä. Yksi toteutuksen yksityiskohta on syytä kopioida, jos kirjoitat oman varmistajasi: lue tiiviste-OID CMS:n AuthenticatedDatasta ennen kuin tiivistät tavualueen, koska SHA-256:n kovakoodaaminen ja sovittelu jälkeenpäin muuttaa ketteryyden etiketiksi ja antaa vihamielisen tiedoston virrata koko dokumentin läpi ennen kuin huomaat, ettei algoritmia koskaan tuettu. CMSAlgorithmProtectionin, AuthenticatedDatan tiivistealgoritmin, integriteettitiedon messageDigestin ja tavualueen tiivisteen on kaikkien nimettävä yksi algoritmi, ja mikä tahansa erimielisyys epäonnistuu suljettuun tilaan

var
  Options: THPDFPDFMACOptions;
begin
  Options := THPDFPDFMACOptions.Compatibility;  // SHA-256, hyväksyy kaikki kuusi
  Options := THPDFPDFMACOptions.Modern;         // SHA-384, hylkää 256-bittiset
  Options := THPDFPDFMACOptions.HighAssurance;  // vain SHA3-512, AES-GCM

  // Mukautettu profiili on laillinen, mutta algoritmin, jolla se
  // generoi, on myös esiinnyttävä validoinnin sallimuslistassa,
  // muuten konfiguraatio hylätään ennen kuin yhtään tavua on kirjoitettu
  Options.Profile := pmppCustom;
  Options.DigestAlgorithm := pmdaSHA512;
  Options.AllowedDigestAlgorithms := [pmdaSHA512, pmdaSHA3_512];
  Options.RequireAESGCM := True;
end;

Mitä PDF MAC todistaa ja mitä ei

Varmistettu PDF MAC -ketju todistaa, että jokainen suojattu revisio on tavu identtinen sen kanssa, jonka joku tiedostonsalausavaimen haltija kirjoitti, ettei yhtään suojattua revisiota poistettu tai järjestetty uudelleen, eikä yhtään suojaamatonta revisiota liitetty ankkurin jälkeen — täsmälleen sen hyökkäysluokan, jonka pelkkä AES-256-salaus jättää auki, koska luottamuksellisuus ei sanon mitään eheydestä ja salattu PDF, jossa on liimattu revisio, purkautuu yhtä mielellään kuin ehjä. Se, mitä se ei todista, on authorship. MACin avain johtuu tiedostonsalausavaimesta, joten kuka tahansa, joka voi avata dokumentin, voi myös tuottaa kelvollisen MACin muunnetusta versiosta, jokainen laillinen vastaanottaja mukaan lukien; se on symmetrinen primitiivi, ja symmetriset primitiivit eivät voi attribuoida. Jos sinun on tiedettävä, kuka muutti jotain, tarvitset digitaalisen allekirjoituksen, jonka takana on sertifikaatti, ja PDF MAC täydentää sitä suojaamalla inkrementaalisen rakenteen, jota allekirjoitus yksin ei kata. Kohtele niitä kerroksina ja anna kahden tuomion raportoittaa itsenäisesti sen sijaan, että kutistisit ne yhdeksi tilakuvakkeeksi

Tässä kuvatut PDF MAC -sisääntulopisteet — AddStandalonePDFMAC, SignPDFWithPFXAndAttachedPDFMAC, ValidatePDFMAC ja ValidatePDFMACChain — toimitetaan standardin HotPDF Delphi Component mukana Delphille ja C++Builderille, jossa tuotesivu kantaa täyden referenssin optiotietueelle, tilaluetteloinneille ja revisiokohtaiselle validointitaulukolle