Yhden PAdES-allekirjoituksen validointi tarkoittaa kolmen toisistaan riippumattoman asian tarkistamista, ja katselimen vihreä valintamerkki kertoo vain kolmannesta. Ensiksi /ByteRange-taulukon on katettava oikeat tavut: sen nimeämien jaksojen on muodostettava täsmälleen sama syöte, josta CMS-tiiviste otettiin, eikä allekirjoitettuja tavuja saa jäädä niiden ulkopuolelle. Toiseksi CMS:n sisällä olevan varmenteen on muodostettava ketju luottamaasi juureen ja sisällettävä PAdESin vaatima allekirjoitettu allekirjoitusvarmenneattribuutti. Kolmanneksi, jos profiili väittää käyttävänsä aikaleimaa, RFC 3161 -tunnisteen on sidottava allekirjoitusarvo ajankohtaan ennen varmenteen vanhenemista. Acrobat tiivistää kaikki kolme yhdeksi kuvakkeeksi; vaatimustenmukaisuuden tarkistin pitää ne erillään, ja niin pitäisi myös näitä tiedostoja tuottavan koodin. losLab PDF Library (PDF Library for Delphi) antaa sinulle allekirjoituspuolen, aikaleiman upottamisen uudelleen sekä auditointikutsut ByteRange-arvon tarkistamiseksi ennen kuin luotat siihen
Yksi erottelu hämmentää lähes jokaista ensimmäistä PAdES-toteutusta, joten se kannattaa todeta ennen koodia. Allekirjoitus, joka on kirjoitettu arvolla /SubFilter /adbe.pkcs7.detached, on täysin pätevä ISO 32000-1:n §12.8:n mukainen allekirjoitus, jonka Acrobat ilmoittaa kelvolliseksi. Se ei kuitenkaan ole PAdES-allekirjoitus, koska ETSI EN 319 142-1 vaatii ETSI.CAdES.detached-arvon jokaisella perustasolla. eIDAS-vaatimustenmukaisuuden tarkistin hylkää ensimmäisen ja hyväksyy toisen, vaikka kryptografia on sama. Profiili on väite, jonka asiakirja esittää itsestään, ja tämän väitteen saaminen oikeaksi on PDFlibPasissa yksi kutsu
Mikä tekee PDF-allekirjoituksesta PAdES-allekirjoituksen
ETSI EN 319 142-1 määrittelee neljä CMS-muodon päälle pinottua perustasoa. PAdES-B-B on aloitustaso: CAdES-allekirjoitus PDF-allekirjoituskentässä, jossa on ETSI.CAdES.detached-SubFilter ja allekirjoitettu allekirjoitusvarmenneattribuutti. PAdES-B-T lisää allekirjoitusarvoon RFC 3161 -aikaleiman, joka todistaa allekirjoituksen olleen olemassa ennen ajankohtaa, jota kukaan ei voi jälkikäteen muuttaa. PAdES-B-LT upottaa validointiin tarvittavat varmenteet, CRL:t ja OCSP-vastaukset Document Security Storeen, joten tiedosto säilyy varmennettavana myös sen jälkeen, kun varmenteen myöntänyt CA poistaa infrastruktuurinsa käytöstä. PAdES-B-LTA täydentää pinon asiakirja-aikaleimalla, joka suojaa kertyneet todisteet uudelleen algoritmien heikentyessä
PDF Library for Delphi kartoittaa nämä käsitteet allekirjoitusprosessin API:insa. Profiilimerkki on SetSignProcessCustomSubFilter. Jos käytäntösi tarvitsee sitoumuksen tyypin osoituksen (alkuperätodisteen, hyväksyntätodisteen tai jonkin muista ETSI-tunnisteista 1–6), se asetetaan SetSignProcessCommitmentType-kutsulla. Nimenomainen allekirjoituskäytäntö liitetään kutsulla SetSignProcessSignaturePolicy, joka vastaanottaa käytännön OID:n ja sen tiivisteen. Yksi oletusarvo ansaitsee huomiota: kun tiivistealgoritmi jätetään automaattiseksi, kirjasto valitsee SHA-256:n ETSI- ja adbe.pkcs7.detached-allekirjoituksille ja palaa SHA-1:een vain vanhassa adbe.pkcs7.sha1-polussa. Aseta se silti nimenomaisesti. Auditoijat kysyvät, mitä tiivistettä käytit, ja eksplisiittistä arvoa koodissa on helpompi puolustaa kuin oletusarvoa, jonka selittämiseksi pitää lukea käsikirja
Perustason allekirjoituksen tuottaminen
Litteä API ohjaa allekirjoitusta kertaluonteisena tilakoneena: avaa prosessi lähdetiedostolle, määritä se, viimeistele tulostetiedostoon ja lue tuloskoodi. Seuraava sarja tuottaa SHA-256:lla allekirjoitetun PAdES-B-B-allekirjoituksen. Tärkein rivi ei liity itse allekirjoitukseen. Se on tarkoituksella ylimitoitettu /Contents-varaus, koska sitä ei voi enää myöhemmin muuttaa, jos allekirjoitukseen täytyy joskus lisätä aikaleima
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;
NewSignProcessFromFile palauttaa arvon 0, kun lähdettä ei voida avata lainkaan. Sen jälkeen GetSignProcessResult erottaa tuotannossa todella esiintyvät virhetilat: 4 tarkoittaa väärää PDF-salasanaa, 7 väärää PFX-salasanaa, 9 varmennetiedostoa, jossa ei ole yksityisavainta, 10 tulostuspolkua, johon ei voi kirjoittaa, ja 11 virhettä allekirjoitustavujen lisäämisessä. Numeerisen koodin kirjaaminen lähdetiedoston nimen viereen muuttaa epämääräisen tukipyynnön minuutin diagnoosiksi
RFC 3161 -aikaleiman lisääminen, jota kirjasto ei nouda puolestasi
PDF Library for Delphi ei sisällä TSA-asiakasta, ja kyseessä on harkittu raja eikä puute. Kirjasto laskee tiivisteen, jonka aikaleimaviranomaisen täytyy allekirjoittaa, ja upottaa sen jälkeen laajennetun CMS:n uudelleen; välissä oleva HTTP-vaihto ja CMS:n muokkaus kuuluvat kutsujalle. Jaolle on vahva tekninen syy. Windows CryptoAPI -ohjaus, jonka nimellisesti pitäisi lisätä allekirjoittamattomia attribuutteja, CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR, epäonnistuu arvolla CRYPT_E_INVALID_INDEX PAdESin käyttämässä irrotetussa SignedData-rakenteessa. Siksi laajennetun CMS:n on tultava omassa hallinnassasi olevasta CMS-kooderista. Mikään kirjasto ei voi liittää tunnistetta huomaamatta yhdellä järjestelmäkutsulla, ja jokainen näin väittävä kirjasto tekee muokkauksen jossakin, mitä et näe
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);
// molemmat alla olevat kutsut ovat sovelluskoodia: HTTP POST TSA:lle,
// ja CMS-uudelleenkoodaus, joka liittää tunnisteen allekirjoittamattomaksi attribuutiksi
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;
Tarkkaile tässä tuloskoodeja: 12 tarkoittaa, ettei nimettyä allekirjoituskenttää ole olemassa, 11 sitä, ettei olemassa olevaa CMS:ää voitu jäsentää, ja 13 sitä, ettei laajennettu CMS enää mahdu varattuun /Contents-paikkamerkkiin. Koodi 13 sattuu eniten, sillä ainoa korjaus on allekirjoittaa uudelleen: tyypillinen aikaleimatunniste varmenneketjuineen on kooltaan 4–6 KB, ja B-B-vaiheessa tehty 8192 tavun varaus on olemassa juuri siksi, että tälle vaiheelle jää tilaa
Validointi alkaa ByteRange-arvosta, ei varmenneketjusta
Katselimen vihreä valintamerkki on kyseisen koneen varmennesäilöön perustuva luottamuspäätös, ei tiedoston rakenteellinen tuomio. Ohjelmallisen validoinnin pitää alkaa alempaa kysymyksellä, jonka inkrementaaliset päivitykset tekevät hienovaraiseksi: mitkä tavut kukin allekirjoitus todella kattaa? Jokainen tässä käsitelty laajennus, toinen allekirjoitus, DSS-sanakirja tai asiakirja-aikaleima, saapuu inkrementaalisena päivityksenä, ja jokainen päivitys lisää tavut aiemman allekirjoituksen /ByteRange-arvon ulkopuolelle. Lisätyt tavut ovat oikeutettuja. Validoijan on silti luokiteltava ne asiakirjan muokkauskäytännön mukaan, ja kyseisen käytännön kenttäkohtainen DocMDP-taso on luettavissa GetSignatureDocMDPLevelByName-kutsulla
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 tarkoittaa, että allekirjoitus on todella tehty
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;
Tähän auditointipolkuun liittyy kaksi ansaa. TPDFlibSignDoc.Open pitää tiedostoa yksinomaisella jakolukolla, joten validoijan, joka haluaa myös tiivistää raa'at tiedostotavut CMS-varmennusta varten, on luettava tiedosto muistiin ennen sen avaamista auditointiin. Käännä järjestys ja luku epäonnistuu itse asettamaasi lukkoon. Toinen ansa on hiljainen: litteän API:n vastine GetSignProcessByteRange palauttaa Integer-arvon, vaikka taustalla olevat siirtymät ovat Int64-arvoja, joten yli 2 GB:n kohdalla litteä kutsu katkaisee arvon ilman huomautusta. Siksi tämä esimerkki hakee siirtymät auditointiluokan kautta. Myös yksi puuttuva asia kannattaa nimetä. Litteällä kerroksella ei ole lainkaan VerifySignature-käärettä. Kryptografiset päätelmät tulevat luokkatason TPDFlibSignatureVerifier-tarkistimesta, joka palauttaa vsValid-, vsInvalid- tai vsUnknown-arvon, tai ulkoisesta validoijasta, johon vaatimustenmukaisuuskäytäntösi jo luottaa
Pitkäaikainen validointi: DSS, VRI ja asiakirja-aikaleima
PAdES-B-LT on olemassa, koska peruutusinfrastruktuuri on kuolevainen. ETSI EN 319 142-1:n §5.4.2.2 määrittelee Document Security Storen: asiakirjatason sanakirjan, joka sisältää varmenteita, CRL:iä ja OCSP-vastauksia ja joka voidaan indeksoida allekirjoituskohtaisesti kunkin allekirjoituksen /Contents-arvon tiivisteellä avainnettujen VRI-merkintöjen kautta. PDF Library for Delphi-työnkulku heijastaa aikaleimasuunnittelua. NewPAdESDSSProcessFromFile avaa prosessin; AddPAdESDSSCertificate, AddPAdESDSSCRL ja AddPAdESDSSOCSP vastaanottavat DER-kokoelmia; AddPAdESDSSVRI sitoo valitun materiaalin yhteen allekirjoitukseen; EndPAdESDSSProcessToFile kirjoittaa kaiken inkrementaalisena päivityksenä. Vaikea osa jää sinun puolellesi. Peruutusmateriaalin noutaminen ja arviointi, onko se riittävän tuoretta upotettavaksi, on kutsujan tehtävä. Kirjasto takaa sanakirjojen rakenteellisen vaatimustenmukaisuuden; se ei voi taata, että OCSP-vastaajasi puhui totta
Arkistointipäätepiste B-LTA lisää asiakirja-aikaleiman: erillisen allekirjoituskentän, jonka tyyppi on DocTimeStamp eikä Sig ja joka tuotetaan SetSignProcessDocTimeStamp-kutsulla varatulla allekirjoituspituudella. Se ei korvaa B-T-vaiheen allekirjoitusaikaleimaa. Allekirjoitusaikaleima todistaa, milloin yksi tietty allekirjoitus oli olemassa; asiakirja-aikaleima suojaa koko tiedoston, DSS-todisteet mukaan lukien, ja se on osa, jonka pitkäaikaisarkisto uusii muutaman vuoden välein algoritmien heikentyessä. Kypsä arkistointiprofiili sisältää molemmat. Näitä rakenteita edeltäville lukijoille TPDFlibSignDoc.EnsurePAdESExtensions tallentaa ESIC-kehittäjälaajennuksen asiakirjakatalogiin ja ilmoittaa, että tiedosto käyttää ETSI:n määrittelemiä ominaisuuksia
Yksi reaktio tähän kaikkeen kannattaa torjua, koska se näyttää virheeltä mutta ei ole sitä. Katselin ilmoittaa usein "kelpoisuus tuntematon" -tilan tiedostolle, jonka PAdES-rakenne on täysin oikea. Luottamus ja rakenne ovat riippumattomia ulottuvuuksia. Katselin ei yksinkertaisesti voi ketjuttaa allekirjoittajaa tällä koneella luottamaansa juureen, mikä on tavallista yksityisillä CA:illa ja testivarmenteilla, vaikka sekä ByteRange-auditointi että CMS-varmennus läpäistään. Korjaus on juurivarmenteen asianmukainen jakelu tai EU:n luottamusluetteloita vasten arviointi silloin, kun tavoitteena on hyväksytty eIDAS-tila, ei allekirjoituskoodin muuttaminen
Auditointinäkökulman, eli allekirjoituskenttien luetteloinnin aineistosta, ByteRange-asettelujen vedostamisen ja DocMDP-tasojen joukkolukemisen, löydät oheisesta artikkelista vaatimustenmukaisuuden ja allekirjoittamisen työpöydästä. Allekirjoitetut asiakirjat, joiden on täytettävä myös arkistointikäytäntö, kuuluvat työnkulkuun, joka kuvataan artikkelissa PDF/A- ja PDF/UA-esitarkistuksesta Delphissä. Täysi API-dokumentaatio ja arviointilataukset ovat losLab PDF Library for Delphi -tuotesivulla