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