Työpöytä, joka ketjuttaa vaatimustenmukaisuusvalidoinnin digitaaliseen allekirjoitukseen, on koordinoitava neljä vaihetta, tässä järjestyksessä, ja pidettävä ne sidottuina yhteen tavujoukkoon koko matkan. Se ajaa PDF/A- tai PDF/UA-preflightin. Se soveltaa korjaukset, joita löydökset vaativat, ja tallentaa korjatun version. Se allekirjoittaa juuri tuon version. Sitten se lukee allekirjoitetun tiedoston takaisin ja vahvistaa, että allekirjoitus todella peittää sen. Järjestys ei ole kosmeettinen. Ohita takaisinluku ja luotat omaan kirjoituspolkuusi; anna preflightin ajaa väärää versiota vasten ja vaatimustenmukaisuusraporttisi kuvaa tiedostoa, jota et koskaan toimittanut
Osa, jonka useimmat itse tehdyt putket saavat väärin, on sauma validoinnin ja allekirjoituksen välillä. Aja ne kahtena erillisenä työkaluna korjausläpikäynnillä välissä ja vähintään kolme erillistä tiedoston versiota tulee olemassa, kukin omilla tavuillaan. Preflight-raportti, jonka annat auditoijalle, kuvaa yhtä niistä. Allekirjoitus jäädyttää toisen. Mikään tiedostossa ei toteaa, että ne ovat sama versio, ja usein eivät ole. PDF Library for Delphi, losLabin PDF Developer -kirjasto Delphille ja C++Builderille, laittaa preflightin ja PAdES-allekirjoituksen yhden facade-luokan taakse, joten koko sekvenssi voi elää yhdessä prosessissa, joka ei koskaan menetä jälkeä siitä, mistä tavuista se puhuu. Jokainen alla oleva kutsu on olemassa kirjastossa tänään, ja samoin jokainen ansa joka on huomioitu sen rinnalla
Kolme versiota yhdestä dokumentista, ja miten kuilu avautuu
Lase tallennukset. Alkuperäinen saapuu ylävirrasta. Korjausläpikäynti lataa sen, kytkee vaatimustenmukaisuustilan päälle ja kirjoittaa korjatun version. Allekirjoitusläpikäynti liittää allekirjoituksen inkrementaalisen päivityksenä, mikä on kolmas kirjoitus. Kolme tallennusta, kolme tavuasettelua, ja preflight-raportti ei tarkoita mitään ellei se nimeä, minkä kolmesta se peittää. Tiedoston SHA-256, tallennettuna jokaisen preflight-ajon ja jokaisen allekirjoituksen viereen, on halpa ankkuri, joka antaa sinun todistaa, että versio, jonka validoit, on versio, jonka allekirjoitit
Yksi kirjaston käytös kiristää tuota kuria edelleen. Vaatimustenmukaisuuskorjaukset, joita pyydetään SetPDFAMode:n tai SetPDFUAMode:n kautta, eivät astu voimaan kun kutsut niitä. Niitä sovelletaan tallennuksen aikana. Automaattikorjaukset kuten merkintöjen tulostuslipun pakottaminen tai PDF/UA-sarkainjärjestyksen asettaminen laskeutuvat tulostiedostoon eivätkä minnekään muualle, joten tarkistus, joka ajetaan dokumenttia vasten, jonka juuri "korjasit" muistissa, ei kerro sinulle mitään tavuista, jotka ovat menossa allekirjoittajalle. Tallenna ensin, sitten preflightaa tallennettu tiedosto. Muistissa oleva tila on luonnos; vain tiedosto levyllä on todellinen
Preflight levyltä, ja nolla joka tarkoittaa kahta asiaa
Flat-preflightin tulokohta on CheckFileCompliance(FileName, Password, ComplianceTest, Options). Testi 1 valitsee PDF/A:n (ISO 19005), testi 2 PDF/UA:n (ISO 14289). Se avaa tiedoston kirjaston suoratoistolukijan kautta, joten LoadFromFile:a ei tarvita ensin, ja se palauttaa merkkijonoluettelokahvan, joka kantaa yhden löydöksen per kohde:
var
PDF: TPDFlib;
ListID, I: Integer;
begin
PDF := TPDFlib.Create;
try
ListID := PDF.CheckFileCompliance('invoice-fixed.pdf', '', 1, 0); // 1 = PDF/A
if ListID = 0 then
begin
if PDF.LastErrorCode <> 0 then
raise Exception.Create('Preflight could not read the file')
else
Writeln('No PDF/A findings');
end
else
begin
for I := 0 to PDF.GetStringListCount(ListID) - 1 do
Writeln(PDF.GetStringListItem(ListID, I));
PDF.ReleaseStringList(ListID);
end;
finally
PDF.Free;
end;
end;
Ansa istuu paluuarvossa, ja se on sellaista lajia, joka menee läpi jokaisesta onnistumistiestä. Nolla tarkoittaa "ei löydöksiä". Nolla tarkoittaa myös "tiedostoa ei voitu avata", koska toteutus palauttaa 0 aina kun tulosluettelo palaa tyhjänä, lukuepäonnistuminen mukaan lukien. Työpöytä, joka lukee 0:n vihreänä valona, hyväksyy iloisesti tiedoston, jonka jokin toinen prosessi on lukinnut. Kutsun parittaminen LastErrorCode:n kanssa, kuten yllä, erottaa kaksi tapausta. Tarkistaja avaa myös tiedoston deny-write-jakotilassa, joten jos korjausvaiheesi yhä pitää kirjoittajakahvaa, preflight epäonnistuu syystä, jolla ei ole mitään tekemistä vaatimustenmukaisuuden kanssa ja kaikella tekemistä unohdetun virtan kanssa
Kun ihminen eikä putki tarvitsee lukea löydöksiä, CreatePreflightReport renderöi ne luettavaksi raportiksi. ComparePreflightReports diffaa kahta ajokertaa, mikä on siisti tapa näyttää, että korjaus tyhjensi alkuperäiset löydökset ilman että se hiljaa esitteli uusia
Tarkistetun version allekirjoittaminen SignProcessilla
Kun tallennettu versio on läpäissyt preflightin ja sen tiiviste on tallennettu, allekirjoita juuri tuo tiedosto eikä mikään muu. SignProcess-API luetaan kuten builder. Avaa prosessikahva, määritä se rivi riviltä, commit, lue sitten tuloskoodi takaisin
ProcessID := PDF.NewSignProcessFromFile('invoice-fixed.pdf', '');
if ProcessID = 0 then
raise Exception.Create('Cannot open source for signing');
PDF.SetSignProcessField(ProcessID, 'ApprovalSig');
PDF.SetSignProcessPFXFromFile(ProcessID, 'company.pfx', PfxPassword);
PDF.SetSignProcessInfo(ProcessID, 'Invoice approval', 'Berlin', 'billing@example.com');
PDF.SetSignProcessCustomSubFilter(ProcessID, 'ETSI.CAdES.detached'); // PAdES baseline
PDF.SetSignProcessDigestAlgorithm(ProcessID, 2); // SHA-256
PDF.SetSignProcessReserveContentsBytes(ProcessID, 8192); // room for a later timestamp
PDF.EndSignProcessToFile(ProcessID, 'invoice-signed.pdf');
if PDF.GetSignProcessResult(ProcessID) <> 1 then
Writeln('Sign failed, code ', PDF.GetSignProcessResult(ProcessID));
PDF.ReleaseSignProcess(ProcessID);
Kaksi riviä tuossa sekvenssissä kantavat enemmän painoa kuin näyttää. SetSignProcessCustomSubFilter arvolla ETSI.CAdES.detached valitsee PAdES-allekirjoituksen sellaisena kuin se on profiloitu ETSI EN 319 142-1:ssä ennemmin kuin legacy-perheessä adbe.pkcs7.detached, mikä on ero sellaisen allekirjoituksen välillä, jonka eurooppalainen validattori hyväksyy, ja sellaisen jonka se liputtaa. SetSignProcessReserveContentsBytes täyttää /Contents-placeholderin, ja koko, jonka valitset tässä, on päätös tulevaisuudesta: jos allekirjoituksen aikaleima on koskaan seuraamassa, suurennetun CMS:n on mahduttava tilaan, jonka varaat nyt, koska placeholder ei voi kasvaa myöhemmin ilman koko asian uudelleenallekirjoittamista. Varaa anteliaasti ja tuhlaat muutaman kilotavun. Varaa liian tiukasti ja aikaleimavaihe epäonnistuu kuukausien päästä ylivuodolla, jonka yhdistäminen takaisin tähän yhteen riviin on kamppailu
GetSignProcessResult vastaa koodilla, ei booleanilla, ja koodit ovat säilyttämisen arvoisia. 1 on onnistuminen. 4 on väärä PDF-salasana, 7 väärä sertifikaattisalasana, 9 PFX joka ei kanna yksityistä avainta, 11 epäonnistuminen kun allekirjoitusta sovellettiin. Romahduta ne true/false:ksi ja heität pois ainoan tiedon palasen, joka erottaa väärä-salasana-tukitapauksen key-without-private-part-tapauksesta. Kirjaa kokonaisluku
Takaisinluku: juuri tuottamasi tiedoston auditointi
Minkään työpöydän ei pitäisi luottaa polkuun, joka kirjoitti tiedoston, jota se on bout sertifioimaan. Auditointiluokka TPDFlibSignDoc avaa allekirjoitetun tulosteen uudelleen ja lukee allekirjoitussanakirjan merkinnät suoraan levyltä:
var
Doc: TPDFlibSignDoc;
Names: TStringList;
FS: TFileStream;
I: Integer;
SourceSize, RangeStart, GapStart, TailStart, TailLen: Int64;
begin
// Kaappaa koko ennen Open-kutsua: audit-objekti pitää tiedostossa jaetun lukituksen
FS := TFileStream.Create('invoice-signed.pdf', fmOpenRead or fmShareDenyNone);
SourceSize := FS.Size;
FS.Free;
Doc := TPDFlibSignDoc.Create;
Names := TStringList.Create;
try
if not Doc.Open('invoice-signed.pdf', '', False) then Exit;
Doc.GetSignatureFieldNames(Names);
for I := 0 to Names.Count - 1 do
if Doc.GetSignatureValueObjNum(Names[I]) > 0 then // > 0 tarkoittaa, että kenttä on allekirjoitettu
begin
RangeStart := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 11)));
GapStart := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 12)));
TailStart := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 13)));
TailLen := StrToInt64(string(Doc.GetSignatureValueByName(Names[I], 14)));
if (RangeStart = 0) and (TailStart + TailLen = SourceSize) then
Writeln(Names[I], ': signature covers the file to EOF')
else
Writeln(Names[I], ': earlier revision, or unusual ByteRange layout');
end;
Doc.Close;
finally
Names.Free;
Doc.Free;
end;
end;
ValueKey-argumentit kuvaavat sanakirjamerkintöihin. Key 0 palauttaa raaka-CMS:n /Contents:stä, keyt 2 ja 3 /Filter- ja /SubFilter-nimet, ja 11–14 neljä ByteRange-numeroa. Tekstiarvot tulevat takaisin GetSignatureTextValueByName:n kautta: key 0 on väitetty allekirjoitusaika, ja key 5 erottaa tavallisen Sig:n DocTimeStamp:stä, mikä merkitsee kun dokumentti kantaa molempia
Tiedoston-koon kaappaus tuon esimerkin yläosassa on kantava, ei siivoustyötä. TPDFlibSignDoc.Open pitää tiedoston rajoittavan jakolukon alla koko elinkaarensa ajan, joten kaikki, mikä tarvitsee raakatavuja (allekirjoitetun välin hashäys, CMS-tiivisteen uudelleenlaskenta), on luettava tiedosto ennen kuin Open kutsutaan. Kirjaston oma SigningWorkbench-demo lukee koko tiedoston muistiin ensin juuri tästä syystä, ja työpöytä, joka ohittaa järjestyksen, epäonnistuu ajoittain, sillä koneella joka sattuu häviämään kilvan
ByteRange-aritmetiikka, joka todistaa kattavuuden
Terve yhden allekirjoituksen tiedosto on ByteRange muotoa [0 a b c]: kattavuus alkaa offsetista 0, ohittaa hex-/Contents-placeholderin a:n ja b:n välillä, jatkuu sitten tavun b+c läpi. Kun b+c vastaa tiedoston kokoa, allekirjoitus peittää kaiken tiedoston loppuun, mikä on tulos, jonka haluat. Kun se jää lyhyeksi, joku liitti inkrementaalisen päivityksen allekirjoituksen kirjoittamisen jälkeen. Se on täysin laillista ISO 32000-1§12.8:n alla, koska myöhemmät lomaketäytöt, toinen allekirjoitus ja DSS-sanakirja saapuvat kaikki juuri näin. Se on myös juuri se fakta, jonka auditointiloki pitäisi tallentaa allekirjoitusaikana ennemmin kuin rekonstruoida paineen alla riidan aikana
Valvo kokonaislukuleveyttä kun teet tätä aritmetiikkaa. Flat-API:n GetSignProcessByteRange palauttaa 32-bittisen Integerin, mutta taustalla olevat arvot ovat Int64, joten yli 2 GB tiedostossa flat-aksessori hiljaisesti typistää. Tavoittele class-tason TPDFlibSigner.GetByteRange:ä, joka palauttaa Int64:n, tai jäsennä arvot ulos GetSignatureValueByName:sta niin kuin auditointikoodi yllä tekee
Mitä kirjasto jättää sinulle
Kaksi rajaa on parempi oppia suunnitteluaikana kuin loppusprissä. Flat TPDFlib-API ei kanna lainkaan allekirjoituksen verifointia. Kryptografinen verifiointi elää yhden tason alempana, TPDFlibSignatureVerifier:ssä, jonka VerifySignature vastaa valid, invalid tai unknown. Myös sisäänrakennettua HTTP-asiakasta RFC 3161 -aikaleimaviranomaisille ei ole. Kirjasto laskee hashin, joka aikaleimaviranomaisen on vastasignattava, ja upottaa suurennetun CMS:n uudelleen kun token palaa, mutta verkkokierroksen TSA:lle on sinun kirjoitettavasi. molemmat on suoraviivaista kääriä ja aidosti epämiellyttäviä löytää puuttuvina viikko ennen julkaisua, joten suunnittele ne mukaan ensimmäisestä luonnoksesta
Yksi kysymys vaatimustenmukaisuudesta on arvoista ratkaista selvästi, koska se päättää, minne viimeinen portti menee: rikkooko allekirjoituksen lisääminen PDF/A:n? Ei sinänsä. Allekirjoitus saapuu inkrementaalipäivityksenä, ja ISO 19005-2 eteenpäin sallii eksplisiittisesti allekirjoitetut dokumentit. Saalis on allekirjoitusulkonäkö, joka pelaa samoilla säännöillä kuin mikä tahansa muu sivusisältö, upotetut fontit ja laite-riippumaton väri mukaan lukien. Joten viimeinen portti työpöydässä on vielä yksi preflight-ajo, tällä kertaa allekirjoitettua tulosta vasten. Kohdele CheckFileCompliance:ä nopeana putkensisäisenä tarkistuksena ja yhä verifioi julkaisuehdokkaat riippumattomalla työkalulla kuten veraPDF, koska validattorit toteuttavat päällekkäisiä mutta ei identtisiä sääntöjoukkoja; kun kaksi ovat eri mieltä, löydösteksti yleensä nimeää lausekkeen luettavaksi
Yksi järjestyskohta putoaa kaikesta tästä. Allekirjoitus ja aikaleimaus eivät ole yksi läpikäynti: perusallekirjoitus kirjoitetaan ensin, sitten erillinen aikaleimaprosessi suurentaa CMS:ää varatun /Contents-tilan sisällä, mikä on juuri syy miksi reserve-bytes-rivi aiemmin kantoi niin paljon painoa. Aikaleimaa ja pitkäaikaisen validoinnin tasoja varten, jotka rakentuvat tälle työpöydälle, PAdES-allekirjoituksen ja -validoinnin käyntiläpivienti kantaa allekirjoituksen perustasolta B-LT:hen, ja preflight-puoli menee syvemmälle PDF/A- ja PDF/UA-preflight-opastuksessa. Täydellinen API-dokumentaatio ja kokeilulataukset elävät PDF Library for Delphi-tuotesivulla