Komponenta PDFium Component podpiše PDF z digitalnim podpisom PAdES B-B prek svoje metode SignPades: naloži dokument, zgosti podpisani razpon bajtov, zgradi strukturo CAdES CMS in doda podpis kot inkrementalno posodobitev. Kriptografsko ozadje je na voljo le za Windows, zato vsak klic pred podpisovanjem varujte s PadesCryptoAvailable
Položaj je znan. Pogodba PDF pristane na vaši mizi, pravna služba želi, da jo digitalno podpišete, preden gre ven, vi pa sežete po isti različici PDFium, ki jo déjà uporabljate za izris in pregled dokumentov, le da ugotovite, da PDFium sploh ne zna zapisati podpisa. Njegov podpisni API je strogo le za branje. Komponenta PDFium Component zapre to vrzel tako, da ima v lasti celoten podpisni cevovod v Pascalu, od funkcije zgoščevanja do vbrizgavanja na ravni bajtov, ta članek pa ta cevovod obravnava od začetka do konca
Zakaj PDFium ne more zapisati digitalnega podpisa?
PDFium razkriva podpise kot objekte le za branje in ne ponuja ničesar, kar bi podpis ustvarilo. Družina FPDFSignatureObj_* vam omogoča naštevanje obstoječega podpisa, branje njegovega /Contents in pregled njegovega /ByteRange, vendar ni ustreznika, ki bi zgradil slovar podpisov, rezerviral režo /Contents ali zapisal razpon bajtov; inkrementalno shranjevanje sicer obstaja (FPDF_SaveAsCopy z FPDF_INCREMENTAL), vendar ne prenaša kavlja za podpisovanje. Vsaka komponenta, ki podpiše PDF na vrhu PDFium, mora zato vsak bajt podpisa ustvariti sama, zato komponenta PDFium Component gradi mehanizem iz treh čistih enot Pascal. FPC 3.2.2 dobavlja md5 in sha1, sploh pa ne SHA-2, Delphi API System.Hash SHA-256 pa ni izvorno združljiv s FPC, zato je FPdfSha256 samostojna implementacija FIPS 180-4, ki ohranja vsako pot kode CMS na enem tipu TSHA256Digest brez prevajalniškega vejenja. FPdfAsn1 zagotavlja kodirnik in bralnik DER, ki ju strukture CMS potrebujejo, FPdfCms pa na vrhu obeh sestavi CAdES SignedData
Kako digitalno podpisati PDF v Delphi?
Naložite dokument, nato pa pokličete SignPades s prstnim odtisom potrdila. Komponenta PDFium Component razreši ta prstni odtis v shrambi potrdil trenutnega uporabnika "MY", potegne ustrezno potrdilo in njegov zasebni ključ ter zapiše podpisano kopijo na pot, ki jo poimenujete
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 je preizkus, ki ga vsakič preverite najprej. V sistemu Windows vrne True in ozadje crypt32/ncrypt je živo; na kateri koli drugi platformi vrne False, klic za podpisovanje pa bi sprožil EPadesCrypto. Obravnava te zaščite kot obvezne preprečuje, da bi gradnja za Linux ali macOS odpovedala med delovanjem na poti, ki tam ne more delovati. Sam prstni odtis je zgoščena vrednost SHA-1 potrdila, enaka vrednost, kot jo upravitelj potrdil Windows prikazuje na svojem zavihku Podrobnosti (Details), in poimenuje določenega podpisnika, ne da bi ključno gradivo sploh postavil v vaš vir
Kaj gre v CMS: podpisani atributi in RFC 5652
Izhodiščni podpis PAdES ni surov podpis RSA čez datoteko; je struktura CAdES CMS SignedData, ki prenaša predpisan nabor podpisanih atributov, FPdfCms.BuildSignedData pa odda natanko ta nabor: content-type, message-digest in signing-certificate-v2, atribut ESS, ki povezuje podpis s potrdilom podpisnika prek zgoščevanja. Ena podrobnost tukaj premaga skoraj vsako ročno izdelano implementacijo CMS. RFC 5652 §5.4 zahteva, da se izvleček podpisanih atributov izračuna čez kodiranje DER SET OF, oznaka 0x31, medtem ko isti atributi potujejo znotraj SignerInfo pod oznako IMPLICIT [0], 0xA0. Komponenta PDFium Component enkrat kodira nabor atributov, zgosti obliko 0x31, nato pa prepiše le vodilni bajt oznake na 0xA0 za oddajanje, tako da en odložišče služi obema vlogama brez drugega prehoda čez drevo
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;
Preobremenitev z možnostmi doda metapodatke slovarja podpisov, ki jih definira ISO 32000-1 §12.8.1: Reason, Location, ContactInfo in Name, vsi neobvezni in vsi zapisani v slovar vrednosti podpisa. Eno omejitev je enostavno spregledati. Če nastavite CommitmentTypeOid, da dodate podpisani atribut CAdES commitment-type-indication, ne nastavite tudi Reason; ETSI EN 319 142-1 §6.3 prepoveduje prenašanje obeh, saj oba izražata isti namen z različnimi sredstvi
Kako se ByteRange in reža /Contents prilegata skupaj?
Podpis mora pokrivati celotno datoteko razen bajtov, ki vsebujejo sam podpis, PAdES va to krožnost razreši s placeholderjem fiksne širine, ki ga SignPadesBytes natančno upravlja. Rezervira heksadecimalni niz /Contents velikosti ContentsSize bajtov (privzeto 16384, udobno večji od tipičnega CMS SignedData), serijalizira inkrementalno posodobitev, da poišče natančen odmik reže, nato pa izračuna /ByteRange as dva razpona, ki oklepata režo, vse pred odpiralnim ločilom heksadecimalnega niza in vse po njegovem zapiralnem ločilu. SHA-256 teče le čez ta dva razpona. Končni CMS se heksadecimalno kodira v rezervirano režo, se dopolni z ničlami do fiksne širine, doda pa se posodobitev navzkrižnih referenc. Ker je širina določena vnaprej, polnjenje reže ne premakne nobenega spodnjega bajta, kar je celoten razlog, da razpon bajtov ostane veljaven; prvotni bajti dokumenta se ohranijo dobesedno, tako da prejšnji podpis na isti datoteki preživi nedotaknjen natanko tako, kot zahteva inkrementalno podpisovanje po ISO 32000-1 §12.8.1
Windows CNG ozadje in njegove omejitve
Komponenta PDFium Component podpisuje le v sistemu Windows in ta meja je namerna. FPdfCryptoWin dinamično povezuje crypt32.dll in ncrypt.dll, s čimer ne dodaja odvisnosti od DLL-jev v času prevajanja, podpisna veriga pa je standardni CNG: odpre shrambo MY, najde potrdilo po zgoščeni vrednosti, pridobi ročaj njegovega zasebnega ključa prek CryptAcquireCertificatePrivateKey in pokliče NCryptSignHash. Podprti so RSA s PKCS#1 v1.5, RSA-PSS in ECDSA. ECDSA potrebuje en popravek, ki ga drugi ne potrebujejo, saj NCryptSignHash vrne surov par r-in-s po IEEE P1363, medtem ko CMS pričakuje DER ECDSA-Sig-Value SEQUENCE, zato ga ozadje ponovno kodira v skladu z RFC 5480
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;
Praktična posledica je, da mora zasebni ključ živeti v shrambi potrdil Windows. Potrdilo v datoteki PFX deluje le, ko ga uvozite v shrambo trenutnega uporabnika, na kateri točki je njegov prstni odtis vrednost, ki jo prenesete v SignPades. Ta izdaja nima poti PKCS#11 ali HSM in nima ozadja datotek programskega ključa, zato, ko PadesCryptoAvailable vrne False, na tej napravi preprosto ni podpisovanja
Kje se PAdES B-B ustavi
PAdES B-B je izhodišče, tla štirih ravni PAdES: dokazuje, kdo je podpisal, in da se bajti od takrat niso spremenili, in nič več od tega. Podpis B-B ne prenaša zaupanja vrednega časovnega žiga, zato ne more dokazati, kdaj se je podpisovanje zgodilo, in ne vgrajuje podatkov o preklicu, zato mora preveritelj čez leta sam pridobiti verigo potrdil in njeno stanje. Te vrline so natanko tisto, kar višje ravni zaprejo. Ko potrebujete čas podpisovanja, ki ga bo revizor sprejel, dodajanje časovnega žiga RFC 3161 in DSS za dolgoročno preverjanje premakne podpis na B-T in višje; ko želite prebrati končan podpis in potrditi, katero raven je dosegel, je spremno orodje pregled podpisa PDF in njegove ravni PAdES; pred podpisovanjem česar koli pa vam revizija varnostnih tveganj PDF pove, na kaj boste postavili svoje ime
Tukaj prikazane metode SignPades so priložene komponenti PDFium Component za Delphi in C++Builder, skupaj s pregledom podpisov le za branje, ki ga PDFium ponuja takoj po namestitvi