PDFium komponentas pasirašo PDF failą PAdES B-B skaitmeniniu parašu per SignPades metodą: jis įkelia dokumentą, sukuria pasirašomo baitų diapazono maišą, sukuria CAdES CMS struktūrą bei prideda parašą kaip prieaugio atnaujinimą. Kriptografinė dalis skirta tik „Windows“ sistemai, todėl prieš pasirašydami apsaugokite kiekvieną iškvietimą PadesCryptoAvailable tikrinimu
Situacija pažįstama. Sutarties PDF failas patenka ant jūsų stalo, teisininkai nori, kad jis būtų pasirašytas skaitmeniniu būdu prieš jį išsiunčiant, ir jūs griebiatės tos pačios PDFium versijos, kurią jau naudojate dokumentams atvaizduoti bei tikrinti, tik tam, kad sužinotumėte, jog PDFium išvis negali rašyti parašo. Jo parašų API yra griežtai tik skaitomas. PDFium komponentas užpildo šią spragą valdydamas visą pasirašymo procesą Pascal kalba — nuo maišos funkcijos iki baitų lygio įterpimo, ir šiame straipsnyje šis procesas apžvelgiamas nuo pradžios iki galo
Kodėl PDFium negali įrašyti skaitmeninio parašo?
PDFium pateikia parašus kaip tik skaitomus objektus bei nesiūlo nieko parašo sukūrimui. FPDFSignatureObj_* šeima leidžia enumerate esamą parašą, skaityti jo /Contents bei tikrinti jo /ByteRange, tačiau nėra atitikmens, kuris sukurtų parašo žodyną, rezervuotų vietą /Contents laukui ar įrašytų baitų diapazoną; prieaugio išsaugojimas egzistuoja (FPDF_SaveAsCopy su FPDF_INCREMENTAL), tačiau neturi pasirašymo sąsajos. Todėl bet kuris komponentas, pasirašantis PDF failą virš PDFium, turi pats sugeneruoti kiekvieną parašo baitą; būtent todėl PDFium komponentas sukuria šį mechanizmą iš trijų gryno Pascal modulių. FPC 3.2.2 turi md5 bei sha1, bet neturi SHA-2 išvis, o Delphi System.Hash SHA-256 API nėra suderinamas su FPC šaltinio kodo lygmeniu, todėl FPdfSha256 yra savarankiškas FIPS 180-4 standartą atitinkantis modulis, kuris išlaiko visus CMS kodo kelius viename TSHA256Digest tipe be kompiliatoriaus išsišakojimų. FPdfAsn1 pateikia DER kodavimo bei skaitymo funkcijas, kurių reikia CMS struktūroms, o FPdfCms surenka CAdES SignedData virš abiejų
Kaip skaitmeniniu būdu pasirašyti PDF Delphi aplinkoje?
Įkelkite dokumentą, tada iškvieskite SignPades nurodydami sertifikato atspaudą. PDFium komponentas suranda šį atspaudą Current User „MY“ sertifikatų saugykloje, paima atitinkamą sertifikatą bei jo privatųjį raktą bei įrašo pasirašytą kopiją jūsų nurodytu keliu
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 yra patikrinimas, kurį atliekate pirmiausia kiekvieną kartą. „Windows“ sistemoje jis grąžina True bei crypt32/ncrypt posistemė yra aktyvi; bet kurioje kitoje platformoje jis grąžina False bei pasirašymo iškvietimas sukeltų EPadesCrypto klaidą. Ši privaloma patikra neleidžia Linux ar macOS programos versijai sugesti vykdymo metu kelyje, kuris ten negali veikti. Pats atspaudas yra sertifikato SHA-1 maiša — ta pati reikšmė, kurią Windows sertifikatų tvarkyklė rodo skiltyje „Details“, ir jis nurodo konkretų pasirašantįjį, niekada neįkeliant rakto duomenų į jūsų šaltinio kodą
Kas įeina į CMS: pasirašyti atributai bei RFC 5652
PAdES bazinis parašas nėra paprastas RSA parašas virš failo; tai yra CAdES CMS SignedData struktūra, pernešanti privalomą pasirašytų atributų rinkinį, o FPdfCms.BuildSignedData išveda būtent šį rinkinį: content-type, message-digest bei signing-certificate-v2 — ESS atributą, kuris maiša susieja parašą su pasirašiusiojo sertifikatu. Viena detalė čia sužlugdo bei pralenkia beveik kiekvieną savadarbį CMS diegimą. RFC 5652 standarto 5.4 punktas reikalauja, kad pasirašytų atributų maiša būtų skaičiuojama virš DER SET OF kodavimo, žymos 0x31, o tie patys atributai keliauja SignerInfo viduje po IMPLICIT [0] žyma, 0xA0. PDFium komponentas vieną kartą užkoduoja atributų rinkinį, apskaičiuoja 0x31 formos maišą, o tada pakeičia tik pirmąjį žymos baitą į 0xA0 išvedimui, todėl vienas buferis tarnauja abiem vaidmenims be antro perėjimo per medį
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;
Savybių perkrova prideda parašo žodyno metaduomenis, kuriuos apibrėžia ISO 32000-1 standarto 12.8.1 punktas: Reason, Location, ContactInfo bei Name — visi neprivalomi bei visi įrašomi į parašo reikšmės žodyną. Vieno apribojimo verta nepamiršti. Jei nustatote CommitmentTypeOid, kad pridėtumėte CAdES commitment-type-indication pasirašytą atributą, nenustatykite Reason; ETSI EN 319 142-1 standarto 6.3 punktas draudžia naudoti abu kartu, nes jie išreiškia tą patį tikslą skirtingomis priemonėmis
Kaip dera ByteRange bei /Contents laukas?
Parašas turi apimti visą failą, išskyrus baitus, kuriuose yra pats parašas, ir PAdES išsprendžia šį cikliškumą naudodamas fiksuoto pločio vietos rezervuarą, kurį SignPadesBytes valdo tiksliai. Jis rezervuoja /Contents šešioliktainę eilutę iš ContentsSize baitų (16384 pagal numatytuosius nustatymus, patogiai didesnė nei tipiniai CMS SignedData duomenys), serijuoja prieaugio atnaujinimą, kad surastų tikslų lauko poslinkį, tada apskaičiuoja /ByteRange kaip du intervalus, kurie apgaubia šį lauką: viską prieš šešioliktainės eilutės atidarymo skyriklį bei viską po jos uždarymo skyriklio. SHA-256 skaičiuojamas tik virš šių dviejų intervalų. Baigta CMS yra užkoduojama šešioliktainiu formatu į rezervuotą vietą, užpildoma nuliais iki fiksuoto pločio, bei pridedamas kryžminių nuorodų atnaujinimas. Kadangi plotis nustatomas iš anksto, lauko užpildymas nepasislenka nei vieno baito toliau, būtent todėl baitų diapazonas išlieka galiojantis; originalūs dokumento baitai išsaugomi pažodžiui, zodžiu, ankstesnis parašas tame pačiame faile išlieka nepaliestas, būtent taip, kaip reikalauja ISO 32000-1 standarto 12.8.1 punkto prieaugio pasirašymas
Windows CNG dalis bei jos apribojimai
PDFium komponentas pasirašo tik „Windows“ aplinkoje, ir ši riba yra sąmoninga. FPdfCryptoWin dinamiškai susieja crypt32.dll bei ncrypt.dll, nepridėdamas jokių DLL priklausomybių kompiliavimo metu, o pasirašymo grandinė yra standartinė CNG: atidaro MY saugyklą, suranda sertifikatą pagal maišą, paima jo privačiojo rakto nuorodą per CryptAcquireCertificatePrivateKey bei iškviečia NCryptSignHash. Palaikomi RSA su PKCS#1 v1.5, RSA-PSS bei ECDSA. ECDSA reikalauja vienos korekcijos, kurios nereikia kitiems, nes NCryptSignHash grąžina neapdorotą IEEE P1363 r bei s reikšmių porą, o CMS tikisi DER ECDSA-Sig-Value sekos, todėl posistemė ją perkoduoja pagal 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;
Praktinė pasekmė yra ta, kad privatusis raktas turi būti Windows sertifikatų saugykloje. Sertifikatas, esantis PFX faile, veikia tik po to, kai jį importuojate į Current User saugyklą, ir tada jo atspaudas yra ta reikšmė, kurią perduodate į SignPades. Ši versija neturi PKCS#11 ar HSM kelio bei programinės įrangos raktų failų posistemės, todėl kai PadesCryptoAvailable grąžina False, pasirašyti šioje mašinoje tiesiog neįmanoma
Kur sustoja PAdES B-B lygis
PAdES B-B yra bazinis lygis, žemiausia iš keturių PAdES pakopų: jis įrodo, kas pasirašė, bei kad baitai nebuvo pakeisti nuo to laiko, ir nieko daugiau. B-B parašas neturi patikimos laiko žymos, todėl negali įrodyti, kada įvyko pasirašymas, bei jame nėra įterptų atšaukimo duomenų, todėl po kelerių metų tikrintuvas turės pats gauti sertifikatų grandinę bei jos būseną. Šias spragas užpildo aukštesni lygiai. Kai jums reikia pasirašymo laiko, kurį priimtų auditorius, RFC 3161 laiko žymos bei DSS pridėjimas ilgalaikiam patvirtinimui perkelia parašą į B-T bei aukštesnį lygį; kai norite perskaityti baigtą parašą bei patvirtinti, kokį lygį jis pasiekė, PDF parašo bei jo PAdES lygio tikrinimas yra tinkamas įrankis; o prieš pasirašant bet ką, PDF saugumo rizikų auditas parodo, po kuo ruošiatės pasirašyti
Čia parodyti SignPades metodai platinami kartu su PDFium komponentu, skirtu Delphi bei C++Builder, kartu su tik skaitomu parašų tikrinimu, kurį PDFium suteikia iškart