PDFiumPas deli PAdES potpisivanje na dva poziva, tako da privatni ključ nikada ne mora da bude u vašem procesu. PreparePadesRemoteSignature upisuje inkrementalno ažuriranje sa praznim rezervisanim mestom fiksne širine /Contents i vraća zapis zahteva koji nosi SHA-256 digest dokumenta, tačan ByteRange i otisak (fingerprint) pripremljenog fajla. CompletePadesRemoteSignature uzima odvojen (detached) CMS koji vraća vaš servis za potpisivanje i ubacuje ga u to rezervisano mesto
Između ta dva poziva mogu proći minuti ili sati, proces se može ponovo pokrenuti, a posao se može premestiti na drugu mašinu. Baš ta praznina je čitav razlog zbog kog je API oblikovan na ovaj način
Zašto udaljen ključ ne može da koristi obični poziv za potpisivanje?
Zato što SignPadesBytes pretpostavlja da se operacija potpisivanja dešava unutar samog poziva. Izgrađuje inkrementalno ažuriranje, izračunava digest nad ByteRange-om, potpisuje ga i upisuje rezultat, sve pre nego što se vrati. To je potpuno u redu kada ključ živi u Windows skladištu sertifikata ili u PKCS#12 fajlu koji ste učitali
Nemoguće je kada ključ živi u mrežnom HSM-u, kvalifikovanom uređaju za kreiranje potpisa kojim upravlja pružalac usluga poverenja, ili u cloud API-ju za potpisivanje koji zahteva da korisnik potvrdi na telefonu. U tim slučajevima sekvenca nije poziv funkcije, već razgovor: pošaljete digest, nešto drugo autentifikuje čoveka, a CMS stiže kasnije. Sinhroni API ne može da izrazi „kasnije” bez blokiranja niti na operaciji kojoj možda treba drugi faktor
Dvofazni protokol
Prva faza priprema dokument. PDFiumPas dodaje polje potpisa i rečnik vrednosti, rezerviše ContentsSize bajtova hex-kodovanog prostora u /Contents, izračunava ByteRange oko te rezervacije, i proizvodi TPadesRemoteSigningRequest koji sadrži FormatVersion, PreparedFingerprint, DocumentDigest, četvoroelementni ByteRange, ContentsHexOffset i ContentsSize
Jedina vrednost koja je potrebna vašem servisu za potpisivanje je DocumentDigest: SHA-256 koji vraćen CAdES SignedData mora da nosi kao svoj message digest. Sve ostalo u zapisu postoji da bi druga faza mogla da dokaže da je fajl koji dovršava upravo onaj fajl za koji je taj digest izračunat
uses
FPdfPades;
var
Options: TPadesRemoteSignOptions;
Request: TPadesRemoteSigningRequest;
Source, Prepared, Session: TFileStream;
begin
Options := TPadesRemoteSignOptions.Default;
Options.Reason := 'Approved by finance';
Options.Location := 'Lisbon';
Options.Name := 'A. Moreira';
Options.SigningTimeUtc := NowUtc;
Options.ContentsSize := 16384; // hex bajtova rezervisano za CMS
Source := TFileStream.Create('contract.pdf', fmOpenRead or fmShareDenyWrite);
Prepared := TFileStream.Create('contract.prepared.pdf', fmCreate);
try
PreparePadesRemoteSignature(Source, Prepared, Options, Request);
finally
Prepared.Free;
Source.Free;
end;
// Sačuvajte sesiju tako da kasnije pokretanje - ili druga mašina - može da je dovrši
Session := TFileStream.Create('contract.signreq', fmCreate);
try
SavePadesRemoteSigningRequest(Session, Request);
finally
Session.Free;
end;
SendDigestToSigningService(Request.DocumentDigest);
end;
Šta Complete odbija, i zašto svaka provera postoji?
Dovršavanje je mesto gde dizajn udaljenog potpisivanja obično krene po zlu, pa je validacija namerno nemilosrdna. CompletePadesRemoteSignature odbija pripremljen PDF čiji se otisak više ne poklapa sa zahtevom, ByteRange koji se ne poklapa sa zabeleženim koordinatama rezervisanog mesta, izmenjene graničnike /Contents, rezervisano mesto koje više nije prazno, CMS veći od rezervacije, CMS koji nije tačno jedna DER vrednost, nepodržan oblik SignedData, nedostajući atribut signing-certificate-v2, i CMS čiji se message digest ne poklapa sa pripremljenim digest-om dokumenta
Svaka od njih odgovara stvarnom otkazu. Provere otiska i ByteRange-a hvataju slučaj kada je neko regenerisao pripremljen fajl između faza, što bi proizvelo potpis koji se verifikuje nad bajtovima koje niko nema. Provera praznog rezervisanog mesta hvata duplo dovršavanje, gde se drugi CMS upisuje preko potpisa koji već postoji. Provera message digest-a hvata najopasniji slučaj od svih: ispravno formiran CMS potpisan nad drugačijim dokumentom, što dobijete kada red pomeša dve istovremene sesije potpisivanja. Bez nje biste proizveli fajl koji izgleda potpisan, a svuda ne prolazi validaciju, ili gore, koji nosi tuđe odobrenje
Zahtev za signing-certificate-v2 je pitanje PAdES usaglašenosti, a ne integriteta. ETSI EN 319 142 zahteva da sertifikat za potpisivanje bude vezan u potpisane atribute, a CMS bez tog atributa nije PAdES potpis čak i ako se kriptografski verifikuje. Odbijanje toga pri dovršavanju znači da to saznajete ovde, a ne u izveštaju validatora od klijenta, tema dalje istražena u zašto validatori odbijaju PAdES potpise
var
Request: TPadesRemoteSigningRequest;
Session, Prepared, Dest: TFileStream;
CmsDer: TBytes;
begin
Session := TFileStream.Create('contract.signreq', fmOpenRead);
try
Request := LoadPadesRemoteSigningRequest(Session);
finally
Session.Free;
end;
CmsDer := FetchDetachedCmsFromService; // vraćeno od strane HSM-a ili TSP-a
Prepared := TFileStream.Create('contract.prepared.pdf', fmOpenRead);
Dest := TFileStream.Create('contract.signed.pdf', fmCreate);
try
try
CompletePadesRemoteSignature(Prepared, Dest, Request, CmsDer);
except
on E: EPadesCrypto do
// Svako odbijanje nosi konkretan razlog; logujte ga bukvalno
FailSession(E.Message);
end;
finally
Dest.Free;
Prepared.Free;
end;
end;
Prelazak granica procesa i mašina
SavePadesRemoteSigningRequest i LoadPadesRemoteSigningRequest serijalizuju sesiju kroz stabilan verzionisan binarni format, i baš to čini dizajn praktičnim, a ne samo ispravnim. Web aplikacija može da pripremi dokument u jednom zahtevu, sačuva pripremljen PDF i blob sesije, vrati digest pregledaču za potpis pametnom karticom, i dovrši fajl u potpuno drugačijem rukovaocu zahteva
Polje FormatVersion je ono što to čuva bezbednim kroz nadogradnje. Sesija napisana od strane starijeg build-a i učitana od strane novijeg prepoznaje se ili eksplicitno odbija, umesto da bude pogrešno pročitana kao drugačije oblikovan zapis. Ako vaš red može da drži sesije danima, tretirajte verziju formata kao operativnu činjenicu koju vredi logovati, a ne kao detalj implementacije
Određivanje veličine rezervisanog mesta
ContentsSize je jedini parametar o kome morate razmišljati, jer je fiksiran pre nego što CMS uopšte postoji. Broji hex-kodovanu rezervaciju, tako da 6 KB DER CMS zahteva najmanje 12 KB prostora, a implementacija ograničava rezervaciju na 64 MiB
Rezervišite premalo i dovršavanje otkazuje sa greškom prevelikog CMS-a nakon što je vaš servis za potpisivanje već obavio svoj posao, što na naplativom kvalifikovanom servisu za potpise znači protraćenu operaciju. Rezervišite previše i svaki potpisan dokument zauvek nosi tu popunu. Razuman pristup je meriti: potpišite jedan dokument sa svojim stvarnim lancem sertifikata, pogledajte dužinu DER-a, udvostručite je za hex, a zatim dodajte velikodušan prostor za token vremenske oznake ako nameravate da nadogradite na potpis T-nivoa. Lanci sa nekoliko posrednika i dugim OCSP odgovorom rastu brže nego što ljudi očekuju
Šta dolazi posle potpisa
Dovršen udaljen potpis je PAdES B-B. Dugoročna validacija zahteva vremensku oznaku i materijal za validaciju, što je odvojeno inkrementalno ažuriranje koje dodaje DSS i njegove VRI rečnike po potpisu, opisano u dugoročnim potpisima sa RFC 3161 vremenskim oznakama i DSS-om. Taj korak je lokalan: dodaje sertifikate, OCSP odgovore i CRL-ove, od kojih nijednom nije potreban privatni ključ
Pre isporuke, proverite ono što ste proizveli istom putanjom koda kakvu bi koristila strana koja se oslanja na potpis, pokriveno u inspekciji digitalnih potpisa i PAdES nivoa. Potpisivanje i verifikacija su drugačiji kod, a pipeline za udaljeno potpisivanje je baš mesto gde ta dva mogu da se razmimoiđu bez da iko primeti, dok to ne kaže spoljni validator
PDFiumPas je Delphi i Lazarus komponenta oko PDFium mehanizma sa izvornim Pascal PAdES slojem, tako da potpisivanje, vremensko označavanje i validacija rade bez spoljnih alata komandne linije. Potpuna API dokumentacija i probna verzija nalaze se na stranici PDFium Delphi komponente