Tekninen artikkeli

PAdES-digitaaliset allekirjoitukset Delphissä: allekirjoitus ja vahvistus PDFlibPasilla

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

PAdES-perustasojen B-B, B-T, B-LT ja B-LTA tikapuut PDF Library for Delphi -kirjastolla rakennettuina, osoittaen miten kukin taso lisää aikaleimoja, DSS-todisteita tai uusittavan asiakirja-aikaleiman ETSI.CAdES.detached-ytimen päälle
Jokainen ETSI-baselinetaso pinoaa yhden lisätakuun samaan CAdES-ytimeen, allekirjoitetuista attribuuteista uusittavaan dokumenttiaikaleimaan

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

Putki RFC 3161 -aikaleiman lisäämiseksi PAdES-allekirjoitukseen Delphissä, joka erottaa PDF Library for Delphi -tiivistyksen ja upottamisen kutsujan TSA-pyynnöstä ja CMS-uudelleenkoodauksesta varatun /Contents-tilan sisällä
Kirjasto tiivistää ja upottaa uudelleen, kun taas koodisi noutaa tokenin ja suorittaa CMS-leikkauksen, ja tuloksen on osuttava 8192-tavuisen /Contents-varauksen sisään
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

Allekirjoitetun PDF:n tavuasettelun auditointi Delphissä, joka näyttää ByteRange-kattamat välit, poissuljetut /Contents-tavut, alueen ulkopuolelle liitetyt inkrementaaliset päivitykset ja kattavuustuomion tiedostokokoa vasten
Kaksi katettua väliä, joista allekirjoituksen omat tavut on poissuljettu, kertovat kattavuuden totuuden, ja liitetyt päivitykset luokitellaan DocMDP-käytännön perusteella sen sijaan että niitä pelättäisiin
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