A PDFium Component a SignPades metódusán keresztül látja el a PDF-et PAdES B-B digitális aláírással: betölti a dokumentumot, hasheli az aláírt bájttartományt, felépíti a CAdES CMS struktúrát, és inkrementális frissítésként hozzáfűzi az aláírást. A kriptográfiai háttér csak Windows-on érhető el, ezért az aláírás előtt minden hívást lásson el a PadesCryptoAvailable ellenőrzéssel
A helyzet ismerős. Egy szerződés PDF landol az asztalán, a jogi osztály digitálisan aláírva szeretné kiküldeni, Ön pedig ugyanahhoz a PDFium buildhez nyúl, amelyet a dokumentumok rendereléséhez és vizsgálatához használ, csak hogy felfedezze: a PDFium egyáltalán nem tud aláírást írni. Aláírási API-ja szigorúan írásvédett. A PDFium Component ezt a rést úgy zárja be, hogy a teljes aláírási csatornát Pascalban birtokolja a hash-függvénytől kezdve egészen a bájtszintű beillesztésig, és ez a cikk végigvezeti ezen a csatornán az elejétől a végéig
Miért nem képes a PDFium digitális aláírást írni?
A PDFium írásvédett objektumként teszi közzé az aláírásokat, és nem kínál semmit azok létrehozásához. Az FPDFSignatureObj_* család lehetővé teszi a meglévő aláírások felsorolását, a /Contents beolvasását és a /ByteRange vizsgálatát, de nincs olyan megfelelője, amely aláírási szótárt építene, lefoglalná a /Contents helyét, vagy bájttartományt írna; az inkrementális mentés létezik (FPDF_SaveAsCopy a FPDF_INCREMENTAL használatával), mégsem hordoz aláírási hookot. Bármely olyan komponensnek, amely a PDFium felett ír alá egy PDF-et, magának kell előállítania az aláírás minden bájtját, ezért a PDFium Component három tiszta Pascal egységből építi fel ezt a gépezetet. Az FPC 3.2.2 az md5-öt és a sha1-et tartalmazza, de SHA-2-t egyáltalán nem, a Delphi System.Hash SHA-256 API-ja pedig forráskódszinten nem kompatibilis az FPC-vel, így az FPdfSha256 egy önálló FIPS 180-4 implementáció, amely minden CMS kódútvonalat egyetlen TSHA256Digest típuson tart, fordítói elágazások nélkül. Az FPdfAsn1 biztosítja a CMS struktúrák által megkövetelt DER kódolót és olvasót, az FPdfCms pedig mindkettő tetejére felépíti a CAdES SignedData-t
Hogyan lehet digitálisan aláírni egy PDF-et Delphiben?
Töltse be a dokumentumot, majd hívja meg a SignPades metódust a tanúsítvány ujjlenyomatával (thumbprint). A PDFium Component feloldja ezt az ujjlenyomatot az aktuális felhasználó „MY” tanúsítványtárában, lekéri a hozzá tartozó tanúsítványt és annak privát kulcsát, majd egy aláírt másolatot ír az Ön által megadott útvonalra
uses
PDFium, FPdfCrypto;
procedure SignContract(const AThumbprint: string);
var
Pdf: TPdf;
begin
if not PadesCryptoAvailable then
raise Exception.Create('PAdES signing requires the Windows CNG backend');
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'contract.pdf'; // the document to sign
Pdf.Active := True;
// Second argument: SHA-1 thumbprint of a certificate in the Current
// User "MY" store. First argument: destination for the signed copy.
if not Pdf.SignPades('contract-signed.pdf', AThumbprint) then
raise Exception.Create('Signing failed');
finally
Pdf.Free;
end;
end;
PadesCryptoAvailable is the probe you check first, every time. On Windows it returns True and the crypt32/ncrypt backend is live; on any other platform it returns False and a signing call would raise EPadesCrypto. Treating the guard as mandatory keeps a Linux or macOS build from failing at run time on a path that cannot work there. The thumbprint itself is the SHA-1 hash of the certificate, the same value the Windows certificate manager shows on its Details tab, and it names a specific signer without ever placing key material in your source
Mi kerül a CMS-be: aláírt attribútumok és az RFC 5652
A PAdES alapvonali aláírás nem a fájl feletti nyers RSA aláírás; ez egy CAdES CMS SignedData struktúra, amely az előírt aláírt attribútumokat hordozza, az FPdfCms.BuildSignedData pedig pontosan ezt a készletet bocsátja ki: content-type, message-digest és a signing-certificate-v2, az ESS attribútumot, amely hash alapján köti az aláírást az aláíró tanúsítványához. Egy részlet itt szinte minden házilag barkácsolt CMS implementációt meghiúsít. Az RFC 5652 §5.4 megköveteli, hogy az aláírt attribútumok kivonata a DER SET OF kódoláson, a 0x31 tagon alapuljon, miközben ugyanezek az attribútumok a SignerInfo-n belül az IMPLICIT [0] tag, a 0xA0 alatt utaznak. A PDFium Component egyszer kódolja az attribútumkészletet, kiszámítja a 0x31 formátum kivonatát, majd kibocsátáskor csak a vezető tagbájtot írja át 0xA0-ra, így egyetlen puffer kiszolgálja mindkét szerepet a fa feletti második menet nélkül
var
Pdf: TPdf;
Opts: TPadesSignOptions;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'contract.pdf';
Pdf.Active := True;
Opts := TPadesSignOptions.Default;
Opts.CertificateThumbprint := 'a1b2c3d4e5f6...'; // signer in the MY store
Opts.Reason := 'I approve this agreement';
Opts.Location := 'Berlin, DE';
Opts.ContentsSize := 16384; // hex width of /Contents
if not Pdf.SignPades('contract-signed.pdf', Opts) then
raise Exception.Create('Signing failed');
finally
Pdf.Free;
end;
end;
Az opciós túlterhelés hozzáadja az ISO 32000-1 §12.8.1 által meghatározott aláírási szótár metaadatokat: a Reason, Location, ContactInfo és Name mezőket, amelyek mindegyike opcionális, és mind bekerülnek az aláírási értékek szótárába. Egy korlátot könnyű átlépni. Ha a CommitmentTypeOid beállításával CAdES commitment-type-indication aláírt attribútumot ad hozzá, ne állítsa be a Reason mezőt is; az ETSI EN 319 142-1 §6.3 tiltja mindkettő hordozását, mivel a kettő ugyanazt a szándékot fejezi ki más-más módon
Hogyan illeszkedik össze a ByteRange és a /Contents hely?
Az aláírásnak le kell fednie a teljes fájlt, kivéve azokat a bájtokat, amelyek magát az aláírást hordozzák, a PAdES pedig ezt a körkörösséget egy fix szélességű helyőrzővel oldja meg, amelyet a SignPadesBytes kezel precízen. Lefoglal egy ContentsSize bájtnyi (alapértelmezés szerint 16384, ami kényelmesen nagyobb, mint egy tipikus CMS SignedData) /Contents hexadecimális karakterláncot, szerializálja az inkrementális frissítést a pontos helyzetének meghatározásához, majd kiszámítja a /ByteRange-et két olyan szakaszként, amelyek közrefogják ezt a helyet: mindent a hexadecimális karakterlánc nyitó határolója előtt és mindent a záró határolója után. A SHA-256 csak ezen a két szakaszon fut le. A kész CMS hexadecimálisan kódolva kerül a lefoglalt helyre, a fix szélességhez igazodva nullákkal feltöltve, végül pedig a kereszthivatkozás-frissítés (cross-reference update) hozzáfűzésre kerül. Mivel a szélesség már az elején rögzített, a hely kitöltése egyetlen lejjebb lévő bájtot sem csúsztat el, és ez az oka annak, hogy a bájttartomány érvényes marad; az eredeti dokumentumbájtok szó szerint megőrződnek, így az ugyanazon a fájlon lévő korábbi aláírás érintetlenül túléli, pontosan úgy, ahogy azt az ISO 32000-1 §12.8.1 inkrementális aláírás előírja
A Windows CNG háttér és korlátai
A PDFium Component csak Windows rendszeren végez aláírást, és ez a határ szándékos. Az FPdfCryptoWin dinamikusan köti a crypt32.dll-t és az ncrypt.dll-t, nem adva hozzá compile-time DLL függőséget, az aláírási lánc pedig szabványos CNG: megnyitja a MY tárat, hash alapján megkeresi a tanúsítványt, megszerzi a privát kulcsának handle-jét a CryptAcquireCertificatePrivateKey segítségével, majd meghívja az NCryptSignHash metódust. Az RSA a PKCS#1 v1.5-tel, a RSA-PSS és az ECDSA mind támogatottak. Az ECDSA-nak olyan korrekcióra van szüksége, amelyre a többinek nincs, mert az NCryptSignHash a nyers IEEE P1363 r-és-s párt adja vissza, míg a CMS DER ECDSA-Sig-Value SEQUENCE-t vár, így a háttér újra-kódolja azt az RFC 5480 szerint
var
Pdf: TPdf;
Opts: TPadesSignOptions;
Output: TFileStream;
begin
if not PadesCryptoAvailable then
Exit; // no signing backend on this platform
Opts := TPadesSignOptions.Default;
Opts.CertificateThumbprint := ReadThumbprintFromConfig;
Pdf := TPdf.Create(nil);
Output := TFileStream.Create('contract-signed.pdf', fmCreate);
try
Pdf.FileName := 'contract.pdf';
Pdf.Active := True;
Pdf.SignPadesToStream(Output, Opts);
finally
Output.Free;
Pdf.Free;
end;
end;
A gyakorlati következmény az, hogy a privát kulcsnak a Windows tanúsítványtárában kell élnie. A PFX fájlban lévő tanúsítvány csak azután működik, miután importálta azt az aktuális felhasználó (Current User) tárába, és ekkor az ujjlenyomata lesz az az érték, amelyet átad a SignPades metódusnak. Ez a kiadás nem tartalmaz PKCS#11 vagy HSM útvonalat, sem szoftveres kulcsfájl hátteret, így amikor a PadesCryptoAvailable False értéket ad vissza, azon a gépen egyszerűen nincs mód az aláírásra
Ahol a PAdES B-B megáll
A PAdES B-B az alapvonal, a PAdES négy szintjének a minimuma: bizonyítja, hogy ki írta alá, és hogy a bájtok azóta nem változtak, és semmi mást ezen kívül. A B-B aláírás nem hordoz megbízható időbélyeget, így nem tudja bizonyítani, hogy mikor történt az aláírás, és nem ágyaz be visszavonási adatokat sem, így a vizsgáló évekkel később maga kénytelen lekérni a tanúsítványláncot és annak állapotát. Ezeket a réseket pontosan a magasabb szintek zárják be. Ha olyan aláírási időre van szüksége, amelyet egy auditor is elfogad, az RFC 3161 időbélyegző és a DSS hozzáadása a hosszú távú ellenőrzéshez a B-T és afeletti szintekre emeli az aláírást; ha egy elkészült aláírást szeretne visszaolvasni és megállapítani, melyik szintet érte el, a PDF aláírás és PAdES szintjének vizsgálata a kísérő eszköz; és még mielőtt bármit is aláírna, a PDF auditálása biztonsági kockázatokra megmondja, mihez adja a nevét
Az itt bemutatott SignPades metódusok a Delphihez és C++Builderhez készült PDFium Component részei, a PDFium által gyárilag biztosított írásvédett aláírás-vizsgálat mellett