Műszaki cikk

Nem lehet csendben javítani egy titkosított PDF-et Delphiben

Vegyél egy számla-PDF-et, amely már AES-256 titkosítást hordoz, és kérd meg a Delphihez és C++Builderhez készült PDFium Componentet (PDFiumPas), hogy pecsételje meg PDF/A-ként archiválási megőrzéshez, vagy írja alá PAdES-szel, egy inkrementális frissítésen keresztül, teljes újraírás helyett. A könyvtár nem fog odáig jutni a titkosított bájtok közvetlen javításával: hat megfelelőség-jelölő injektora felismeri a meglévő /Encrypt bejegyzést, és bájtról bájtra változatlanul átengedi a forrást, PAdES-aláírója pedig kivételt dob ahelyett hogy egy olyan aláírást bocsátana ki, amit egyetlen validátor sem fogad el

Ez más kérdés, mint egy olyan PDF-et auditálni rejtett kockázatért, amit nem te hoztál létre, ami saját, csak-olvasható gyakorlat. Ez a cikk ugyanannak a bizalmi határnak az írási oldaláról szól: mit szabad a saját kódodnak tennie egy olyan fájllal, amelynek bájtjai már valaki más jelszava mögé vannak zárva, abban a pillanatban, amikor az a kód megpróbál bármit is hozzáadni ahhoz utólag

Mit követel meg az ISO 32000-1, amikor egy titkosított PDF-et frissítesz?

Az ISO 32000-1 §7.5.6 megköveteli, hogy egy inkrementális frissítés lezárója megismételjen minden bejegyzést az előző lezáróból a /Prev kivételével, és a 15. táblázat felsorolja a /Encrypt-et azon bejegyzések között, amelyeket egy lezáró hordozhat. Hagyd ki az új lezáróból, és egy szabványkövető olvasónak nincs oka kételkedni a kihagyásban: a legújabb lezáró az irányadó, így egy olvasó, amely nem talál ott /Encrypt-et, úgy dönt, hogy az egész fájl titkosítatlan, és megpróbálja elemezni a régebbi, még mindig titkosított testet sima bájtokként. Tartsd meg a /Encrypt-et az új lezáróban, de a frissítés saját objektumait írd sima szövegként, és a hiba csak egy lépéssel később mozdul: az olvasó helyesen felismeri a titkosítást, minden objektumot, amit érint, átfuttat a fájl titkosítóján, beleértve az újakat, amelyeket soha nem titkosítottak eleve, és zajt kap vissza olyan tartalomhoz, amely tökéletesen olvasható volt, mielőtt a visszafejtés hozzáért volna. Bármelyik hiba egy olyan fájlt eredményez, amely bájtszinten normál, jólformázott inkrementális frissítésnek néz ki, egészen addig, amíg egy szabványkövető olvasó meg nem nyitja

Hat jelölő-injektor, egy v2.14.2-es titkosítási kapu

A PDFiumPas hat bájt-szintű jelölő-injektort szállít, egyet minden ISO PDF-részhalmazhoz, amit megjelölni tud: PDF/A (ISO 19005), PDF/X (ISO 15930), PDF/UA (ISO 14289-1), PDF/E-1 (ISO 24517-1), PDF/R-1 (ISO 23504-1), és PDF/VT-1 (ISO 16612-2). Mindegyik veszi a bájtokat, amiket a PDFium saját FPDF_SaveAsCopy-ja már megírt, és egy második, kisebb inkrementális frissítést rétegez azok tetejére: egy új XMP metaadat-streamet, egy katalógus-szótár-szerkesztést, amely rámutat, és a nyomtatás-orientált részhalmazoknál egy OutputIntentet és ICC-profilt. v2.14.2-től kezdve mind az InjectPdfAMarkers, InjectPdfXMarkers, InjectPdfUaMarkers, InjectPdfEMarkers, InjectPdfRMarkers, és InjectPdfVTMarkers előbb beolvassa a forrás-lezárót, és ha az egy meglévő /Encrypt bejegyzést jelent, változatlanul másolja át a forrást a célstreamre, és azonnal visszatér. Nincs XMP, nincs OutputIntent, nincs katalógus-szerkesztés — a hívó visszakapja az eredeti fájlt, bájtról bájtra

var
  Src, Dst: TFileStream;
  Opts: TPdfXSaveOptions;
begin
  Src := TFileStream.Create('signed-encrypted-proof.pdf', fmOpenRead);
  Dst := TFileStream.Create('pdfx-attempt.pdf', fmCreate);
  try
    Opts.Conformance := pxc4;
    InjectPdfXMarkers(Src, Dst, Opts);
    // pdfx-attempt.pdf is byte-identical to the source: still encrypted,
    // no /GTS_PDFXVersion, no OutputIntent. Nothing was written, and
    // nothing was corrupted either
  finally
    Dst.Free;
    Src.Free;
  end;
end;

A titkosítás megengedettsége nem ugyanaz, mint az injektálás biztonságossága

A PDF/E-1 és a PDF/R-1 mindkettő kifejezetten megengedi, hogy a gazdadokumentum titkosítva legyen a specifikáció szintjén, ami kivételnek tűnik, amíg meg nem nézed, mi is kell ténylegesen történjen lemezen. Az ISO 24517-1 §6.3 megengedi a titkosítást PDF/E-1-hez, és az ISO 23504-1 §6.2.3 megengedi PDF/R-1-hez, feltéve hogy a fejléc %PDF-2.0-t deklarál. Egyik záradék sem mond semmit arról, hogy egy bájt-szintű utófeldolgozó biztonságosan hozzáadhat-e egy sima szöveges objektumot ahhoz a titkosított konténerhez, és nem tehet, ugyanazon §7.5.6 okokból, amelyek minden más részhalmazra vonatkoznak. A PDFiumPas saját megfelelőség-érvényesítői ehhez a két profilhoz, a ValidatePdfECompliance és a ValidatePdfRCompliance, rögzítik a /Encrypt jelenlétét, szándékosan hiba nélkül jelölve, ami helyes egy csak-olvasható validátorhoz, amely soha egyetlen bájtot sem ír. Ez egyben egy olyan minta is, amit könnyű átfutni, és feltételezni, hogy a testvér-injektornak nincs szüksége külön őrre, pedig az injektor az a függvény a párban, amelynek ténylegesen meg kell tagadnia

Csendben visszafejti a SaveAsPdfX a dokumentumodat?

Igen, valahányszor a nyilvános kényelmi metódusokon keresztül mész, ahelyett hogy közvetlenül egy injektort hívnál. A TPdf.SaveAsPdfA, a SaveAsPdfX, a SaveAsPdfUa, a SaveAsPdfE, a SaveAsPdfR, és a SaveAsPdfVT mindegyike egy ideiglenes streambe rendereli az aktuális dokumentumot SaveAs(Tmp, saRemoveSecurity)-vel, mielőtt átadná azokat a bájtokat a hozzá tartozó injektornak. A saRemoveSecurity a PDFium saját FPDF_REMOVE_SECURITY jelzőjére képződik le, így az ideiglenes másolat, amit az injektor kap, soha nem is volt titkosítva eleve, és az injektor /Encrypt őre soha nem kap okot a kiváltásra. A kimenet hordozza a PDF/A, PDF/X, PDF/UA, PDF/E-1, PDF/R-1, vagy PDF/VT-1 jelölőidet, de már nem védi az a jelszó, amivel a forrás megnyílt

Ez a kompromisszum láthatatlan, amíg valaki downstream meg nem nyitja a "védett" archivális másolatot jelszó nélkül, és nem veszi észre, hogy egyszerűen működik. A javítás nem egy másik metódushívás; a PDFiumPasnak nincs saAddSecurity párja, amely a saRemoveSecurity-vel párosodna, mert az alapul szolgáló PDFium motort soha nem építették új titkosítás írására, csak annak eltávolítására. Ha mindkét tulajdonság számít egy fájlnál, a titkosításnak külön lépésnek kell lennie, amit te birtokolsz, a megfelelőség-jelölők után alkalmazva, nem pedig ugyanabba a SaveAsPdfA hívásba beolvasztva

var
  Pdf: TPdf;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.Password := 'open-secret';           // needed to open the source at all
    Pdf.FileName := 'signed-encrypted-invoice.pdf';
    Pdf.LoadDocument;
    Pdf.SaveAsPdfA('invoice-pdfa.pdf', pac2b);
    // invoice-pdfa.pdf now declares PDF/A-2b, but SaveAs(saRemoveSecurity)
    // ran first inside SaveAsPdfA: the output opens without a password
  finally
    Pdf.Free;
  end;
end;

Mi történik, amikor PAdES-szel írsz alá egy titkosított PDF-et?

A PDFiumPas egyenesen megtagadja, ahelyett hogy csendben eldobná a kérést úgy, ahogyan egy jelölő-injektor teszi. A TPdf.SignPades és a SignPadesToStream mindkettő egy belső SignPadesBytes-en keresztül halad, és az első dolog, amit tesz a forrás-lezáró elemzése után, hogy ellenőrzi a /Encrypt-et. Ha a bejegyzés jelen van, EPadesCrypto-t dob "SignPadesBytes: the source document is encrypted; remove encryption before signing" üzenettel, ahelyett hogy tovább folytatná. Az InjectPadesDssMarkers, a függvény, amely tanúsítványokat, OCSP-válaszokat, és CRL-eket ágyaz be hosszú távú érvényesítéshez, ugyanazt az ellenőrzést alkalmazza ugyanazon okból, saját üzenetével: "InjectPadesDssMarkers: the source document is encrypted; remove encryption before embedding DSS validation material"

Az itteni indoklás szigorúbb, mint a jelölő-injektorok átengedése, és ez szándékos. Egy csendes átengedés biztonságos egy PDF/A-pecsétnél, mert annak kihagyása ugyanazzal az érvényes PDF-fel hagy téged, amivel kezdtél, csak felirat nélkül. Az aláírás nem bukhat el ilyen csendesen: egy aláírás, amelyet csendben soha nem adtak hozzá, pontosan úgy néz ki bármely hívó kód számára, amely csak egy logikai eredményt ellenőriz, mint egy aláírás, amelyet sikeresen hozzáadtak. Az EPadesCrypto a közönséges Exception osztályból származik, így elkapása normál kivételkezelés, nem egy speciális vezérlésfolyam-konvenció, amit meg kell tanulnod

var
  Pdf: TPdf;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.Password := 'open-secret';
    Pdf.FileName := 'encrypted-contract.pdf';
    Pdf.LoadDocument;
    try
      Pdf.SignPades('encrypted-contract-signed.pdf', 'A1B2C3D4E5F6...');
    except
      on E: EPadesCrypto do
        // E.Message: 'SignPadesBytes: the source document is encrypted;
        // remove encryption before signing'
        raise Exception.Create('Decrypt the contract before signing: '+ E.Message);
    end;
  finally
    Pdf.Free;
  end;
end;

Megfelelőségi pecsétek, aláírások, és titkosítás sorrendezése

A gyakorlati javítás sorrendezés, nem egy másik könyvtár. Alkalmazd a PDF/A, PDF/X, PDF/UA, PDF/E-1, PDF/R-1, vagy PDF/VT-1 jelölőket először, add hozzá bármely PAdES-aláírást ezután, és csak azután futtasd, bármi is az a lépés a csővezetékedben, amely ténylegesen birtokolja a titkosítást, legyen az egy dedikált PDF-író, egy aláíró berendezés, vagy a saját AES-megvalósításod. A PDFiumPas inkrementális-frissítési rétege természetesen illeszkedik e sorozat közepébe, kicsi, célzott objektumokat fűzve hozzá egy egyébként kész fájlhoz, és a titkosítás a végén tartozik, pontosan azért, mert ez az egyetlen művelet a láncban, amelyet maga a PDFiumPas nem tud elvégezni vagy visszafordítani

Ebből semmi nem változtatja meg, hogyan olvassa a PDFiumPas a lezáró- és kereszthivatkozás-adatokat, amelyektől minden inkrementális frissítés függ, ami a maga finomságainak forrása, amint az xref-streamek képbe kerülnek; egy PDF objektum- és xref-streamjeinek érvényesítése tárgyalja, hogyan kezeli ugyanaz a lezáró-olvasó útvonal a PDF 1.5+ tömörített struktúrákat. És amint egy dokumentum kész valami erősebbre egy megfelelőségi pecsétnél, egy PDF aláírása PAdES B-B aláírással Delphiben az, ahol a SignPades átveszi pontosan onnan, ahol ez a cikk abbahagyja

Az itt leírt jelölő-injektorok és SignPades metódusok a Delphihez és C++Builderhez készült PDFium Komponens részeként érkeznek, a renderelés és a csak-olvasható vizsgálat mellett, amit a PDFium natívan biztosít