Potrjevanje enega podpisa PAdES pomeni preverjanje treh neodvisnih stvari, zelena kljukica v pregledovalniku pa vam pove le o tretji. Prvič, polje /ByteRange mora pokrivati prave bajte: razponi, ki jih poimenuje, morajo rekonstruirati natančen vhod, nad katerim je bil narejen CMS povzetek, brez podpisanih bajtov izven njih. Drugič, certifikat znotraj CMS se mora povezovati s korenskim certifikatom, ki mu zaupate, in vsebovati podpisani podpisni certifikatni atribut, ki ga zahteva PAdES. Tretjič, če profil zahteva časovni žig, mora žeton RFC 3161 povezati vrednost podpisa s časovno točko, preden je certifikat potekel. Acrobat združi vse tri v eno ikono; preverjalnik skladnosti jih ohranja ločene, enako pa bi morala storiti koda, ki te datoteke ustvarja. Knjižnica losLab PDF Library (PDFlibPas) vam ponuja podpisovalno stran tega procesa, ponovno vgradnjo časovnih žigov in revizijske klice za pregled ByteRange, preden mu zaupate.
Ena razlika povzroča težave skoraj vsaki prvi implementaciji PAdES, zato jo je dobro izpostaviti pred kodo. Podpis, zapisan s /SubFilter /adbe.pkcs7.detached, je popolnoma zdrav podpis po standardu ISO 32000-1 §12.8, ki ga bo Acrobat označil kot veljavnega. Vendar pa to ni podpis PAdES, saj ETSI EN 319 142-1 zahteva ETSI.CAdES.detached na vsaki izhodiščni ravni. Preverjalnik skladnosti eIDAS prvega zavrne in drugega sprejme, čeprav je kriptografija enaka. Profil je trditev, ki jo dokument postavi o sebi, pravilna nastavitev te trditve pa je v PDFlibPas stvar enega klica.
Kaj spremeni podpis PDF v podpis PAdES
Standard ETSI EN 319 142-1 opredeljuje štiri izhodiščne ravni (baseline levels), naložene na format CMS. PAdES-B-B is the entry point: a CAdES signature in a PDF signature field with the ETSI.CAdES.detached SubFilter and a signed signing-certificate attribute. PAdES-B-T doda časovni žig RFC 3161 nad vrednostjo podpisa, kar dokazuje, da je podpis obstajal pred časovno točko, ki je nihče ne more spremeniti za nazaj. PAdES-B-LT vgradi certifikate, CRL in odzive OCSP, potrebne za preverjanje, v shrambo Document Security Store, tako da datoteka ostane preverljiva tudi po tem, ko izdajateljski CA upokoji svojo infrastrukturo. PAdES-B-LTA zaključi sklad z dokumentnim časovnim žigom, ki ponovno zaščiti zbrane dokaze ob slabljenju algoritmov.
PDFlibPas te koncepte preslika v svoj API za podpisovanje. Oznaka profila je SetSignProcessCustomSubFilter. Če vaša politika potrebuje navedbo vrste zaveze (commitment type - dokaz o izvoru, odobritvi ali eden od drugih identifikatorjev ETSI, označenih od 1 do 6), to poteka prek SetSignProcessCommitmentType. Eksplicitna politika podpisa se priloži s SetSignProcessSignaturePolicy, ki sprejme OID politike in njen povzetek. Ena privzeta nastavitev si zasluži pozornost: če algoritem povzetka pustite na auto, knjižnica izbere SHA-256 za podpise ETSI in adbe.pkcs7.detached ter preklopi na SHA-1 le na stari poti adbe.pkcs7.sha1. Kljub temu ga nastavite eksplicitno. Revizorji sprašujejo, katero zgoščevalno funkcijo ste uporabili, eksplicitno vrednost v kodi pa je lažje zagovarjati kot privzeto vrednost, ki jo morate iskati v priročniku.
Ustvarjanje izhodiščnega podpisa
Ravni API vodi podpisovanje kot enostopenjski avtomat stanj: odprite proces na izvorni datoteki, ga konfigurirajte, zaključite v izhodno datoteko in preberite kodo rezultata. Spodnje zaporedje ustvari podpis PAdES-B-B s SHA-256. Vrstica, ki je najbolj pomembna, nima nobene zveze s samim podpisom. Gre za namerno preveliko rezervacijo /Contents, saj je to edina stvar, ki je pozneje ne morete spremeniti, če bo temu podpisu kdaj treba dodati časovni žig.
var
Pdf: TPDFlib;
SignId: Integer;
begin
Pdf := TPDFlib.Create;
try
SignId := Pdf.NewSignProcessFromFile('invoice.pdf', '');
if SignId = 0 then
raise Exception.Create('cannot open source PDF');
Pdf.SetSignProcessField(SignId, 'Sig1');
Pdf.SetSignProcessPFXFromFile(SignId, 'company.pfx', PfxPassword);
Pdf.SetSignProcessInfo(SignId, 'Approved', 'Vienna', 'billing@example.com');
Pdf.SetSignProcessCustomSubFilter(SignId, 'ETSI.CAdES.detached');
Pdf.SetSignProcessDigestAlgorithm(SignId, 2); // SHA-256
Pdf.SetSignProcessReserveContentsBytes(SignId, 8192); // room for a timestamp later
Pdf.EndSignProcessToFile(SignId, 'invoice-signed.pdf');
if Pdf.GetSignProcessResult(SignId) <> 1 then
raise Exception.CreateFmt('signing failed, code %d',
[Pdf.GetSignProcessResult(SignId)]);
Pdf.ReleaseSignProcess(SignId);
finally
Pdf.Free;
end;
end;
Metoda NewSignProcessFromFile vrne 0, ko vira sploh ni mogoče odpreti. Nato GetSignProcessResult loči načine napak, ki se dejansko pojavijo v produkciji: 4 pomeni napačno geslo PDF, 7 napačno geslo PFX, 9 datoteko certifikata brez zasebnega ključa, 10 nezapisljivo izhodno pot, 11 pa napako pri nanašanju bajtov podpisa. Beleženje številske kode poleg imena vhodne datoteke spremeni ohlapno podporno težavo v enominutno diagnozo.
Dodajanje časovnega žiga RFC 3161, ki ga knjižnica ne bo pridobila namesto vas
PDFlibPas ne vsebuje odjemalca TSA, kar je namerna meja in ne vrzel. Knjižnica izračuna zgoščeni naslov, ki ga mora avtoriteta časovnih žigov sopodpisati, in nato ponovno vgradi razširjeni CMS; izmenjava HTTP in poseg v CMS vmes pripadata klicatelju. Za to razdelitev obstaja trden tehnični razlog. Nadzornik Windows CryptoAPI, ki nominalno dodaja nepodpisane atribute, CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR, spodleti z napako CRYPT_E_INVALID_INDEX na ločeni strukturi SignedData, ki jo uporablja PAdES. Zato mora izboljšani CMS priti iz kodirnika CMS pod vašim lastnim nadzorom. Nobena knjižnica ne može tiho vstaviti žetona z enim samim sistemskim klicem, vsaka, ki to trdi, pa izvaja poseg nekje, kjer ga ne morete videti.
var
Pdf: TPDFlib;
StsId: Integer;
HashHex, TstDer, TsAttr, AugmentedCms: AnsiString;
begin
Pdf := TPDFlib.Create;
try
StsId := Pdf.NewPAdESSignatureTimeStampProcessFromFile('invoice-signed.pdf', '');
Pdf.SetPAdESSignatureTimeStampField(StsId, 'Sig1');
Pdf.SetPAdESSignatureTimeStampDigestAlgorithm(StsId, 2);
HashHex := Pdf.GetPAdESSignatureValueHashHex(StsId);
// both calls below are application code: an HTTP POST to your TSA,
// and a CMS re-encode that attaches the token as an unsigned attribute
TstDer := RequestTimeStampToken(HashHex);
TsAttr := Pdf.BuildPAdESSignatureTimeStampAttribute(TstDer);
AugmentedCms := AttachUnsignedAttribute(Pdf.GetPAdESSignatureCMSBytes(StsId), TsAttr);
Pdf.SetPAdESSignatureCMSBytes(StsId, AugmentedCms);
Pdf.EndPAdESSignatureTimeStampProcessToFile(StsId, 'invoice-bt.pdf');
if Pdf.GetPAdESSignatureTimeStampProcessResult(StsId) <> 1 then
raise Exception.Create('timestamp embedding failed');
Pdf.ReleasePAdESSignatureTimeStampProcess(StsId);
finally
Pdf.Free;
end;
end;
Tukaj pazite na kode rezultatov: 12 pomeni, da imenovano podpisno polje ne obstaja, 11 da obstoječega CMS ni bilo mogoče razčleniti, 13 pa, da se razširjeni CMS ne prilega več rezervirani ogradi /Contents. Koda 13 je tista, ki boli, saj je edina rešitev ponovno podpisovanje: običajen žeton časovnega žiga s svojo verigo certifikatov obsega 4 do 6 KB, 8192-bajtna rezervacija, narejena med korakom B-B, pa obstaja prav zato, da ima ta korak dovolj prostora.
Potrjevanje se začne pri ByteRange in ne pri verigi certifikatov
Zelena kljukica v pregledovalniku je odločitev o zaupanju glede na shrambo certifikatov te naprave in ne strukturna razsodba o datoteki. Programsko potrjevanje bi se moralo začeti nižje, z vprašanjem, ki ga inkrementalne posodobitve naredijo subtilnega: katere bajte dejansko pokriva posamezen podpis? Vsaka izboljšava, obravnavana tukaj (bodisi drug podpis, slovar DSS ali časovni žig dokumenta), prispe prek inkrementalne posodobitve, vsaka posodobitev pa doda bajte izven območja /ByteRange prejšnjega podpisa. Ti dodani bajti so legitimni. Potrjevalec jih mora še vedno razvrstiti glede na politiko spreminjanja dokumenta, raven DocMDP za posamezno polje, v kateri ta politika živi, pa je berljiva z GetSignatureDocMDPLevelByName.
var
Doc: TPDFlibSignDoc;
Names: TStringList;
I: Integer;
B0, B1, B2, B3, FileSize: Int64;
begin
FileSize := TFile.GetSize('invoice-bt.pdf'); // before Open: SignDoc holds a share lock
Doc := TPDFlibSignDoc.Create;
try
if not Doc.Open('invoice-bt.pdf', '', False) then
raise Exception.Create('cannot open for audit');
Names := TStringList.Create;
try
Doc.GetSignatureFieldNames(Names);
for I := 0 to Names.Count - 1 do
if Doc.GetSignatureValueObjNum(Names[I]) > 0 then // >0 means actually signed
begin
B0 := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 11)));
B1 := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 12)));
B2 := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 13)));
B3 := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 14)));
if (B0 = 0) and (B2 + B3 = FileSize) then
Writeln(Names[I], ': covers the file to EOF')
else
Writeln(Names[I], ': earlier revision, or unexpected ByteRange layout');
end;
finally
Names.Free;
end;
Doc.Close;
finally
Doc.Free;
end;
end;
Na tej revizijski poti živita dve pasti. Metoda TPDFlibSignDoc.Open drži datoteko z izključnim zaklepom skupne rabe, zato mora potrjevalec, ki želi zgoščevati surove bajte datoteke za verifikacijo CMS, prebrati datoteko v pomnilnik pred njenim odpiranjem za revizijo. Obrnite ta vrstni red in branje spodleti zaradi zaklepa, ki ste ga nastavili sami. Druga past je tiha: ustreznica v ravnem API-ju GetSignProcessByteRange vrne Integer, medtem ko so osnovni odmiki Int64, zato nad 2 GB ravni klic odreže podatke brez opozorila, kar je razlog, da ta primer pridobiva odmike prek revizijskega razreda. Omeniti velja tudi eno odsotnost. Ravni sloj sploh nima ovoja VerifySignature. Kriptografske razsodbe prihajajo iz razrednega nivoja TPDFlibSignatureVerifier, ki vrne vsValid, vsInvalid ali vsUnknown, ali iz zunanjega potrjevalca, ki mu vaša politika skladnosti že zaupa.
Dolgoročno potrjevanje: DSS, VRI in časovni žig dokumenta
PAdES-B-LT obstaja, ker je infrastruktura za preklic umerljiva. Standard ETSI EN 319 142-1 §5.4.2.2 določa Document Security Store: slovar na ravni dokumenta, ki prenaša certifikate, CRL-je in odzive OCSP, izbirno indeksirane na podpis prek vnosov VRI, ključanih z zgoščeno vrednostjo /Contents vsakega podpisa. Potek v PDFlibPas odraža obliko časovnega žiga. NewPAdESDSSProcessFromFile odpre proces; AddPAdESDSSCertificate, AddPAdESDSSCRL in AddPAdESDSSOCSP sprejmejo bloke DER; AddPAdESDSSVRI poveže izbrano gradivo z enim podpisom; EndPAdESDSSProcessToFile pa zapiše vse kot inkrementalno posodobitev. Težak del ostaja na vaši strani. Pridobivanje gradiva za preklic in ocenjevanje, ali je dovolj sveže, da ga je vredno vgraditi, je naloga klicatelja. Knjižnica zagotavlja, da so slovarji strukturno skladni; ne more pa jamčiti, da je vaš odzivnik OCSP povedal resnico.
Arhivska končna točka, B-LTA, doda časovni žig dokumenta: ločeno podpisno polje, katerega vrsta je DocTimeStamp in ne Sig, ustvarjeno prek SetSignProcessDocTimeStamp z rezervirano dolžino podpisa. Ne nadomešča časovnega žiga podpisa iz koraka B-T. Časovni žig podpisa dokazuje, kdaj je določen podpis obstajal; časovni žig dokumenta ščiti celotno datoteko, vključno z dokazi DSS, in je element, ki ga dolgoročni arhiv obnavlja vsakih nekaj let ob slabljenju algoritmov. Zrel arhivski profil vsebuje oboje. Za bralnike, ki so starejši od teh struktur, TPDFlibSignDoc.EnsurePAdESExtensions zabeleži razvijalsko razširitev ESIC v katalog dokumentov, s čimer naznani, da datoteka uporablja funkcije, ki jih določa ETSI.
Za revizijski vidik, kar pomeni popisovanje podpisnih polj čez korpus, izvoz postavitev ByteRange in množično branje ravni DocMDP, si oglejte sorodni članek Orodna vrstica za skladnost in podpisovanje. Podpisani dokumenti, ki morajo prav tako izpolnjevati arhivsko politiko, spadajo v delovni tok, opisan v priročniku PDF/A in PDF/UA preverjanje v Delphiju. Celotna dokumentacija API in ocenjevalni prenosi so na voljo na produktni strani losLab PDF Library for Delphi.