Tekninen artikkeli

PDF-allekirjoitusten ja PAdES-tasojen tarkastelu Delphissä

Olet vastaanottanut allekirjoitetun PDF-tiedoston ja sinun on näytettävä katseluohjelmassasi, kuka sen allekirjoitti, milloin se allekirjoitettiin, kattaako allekirjoitus koko tiedoston ja kuinka pitkälle se ulottuu pitkäaikaisessa vaatimustenmukaisuudessa. Delphille ja Lazarukselle tarkoitettu PDFium-komponentti vastaa kaikkiin neljään kysymykseen vain luku -kutsuilla: FPDF_GetSignatureCount and FPDFSignatureObj_*-perhe tuovat esiin allekirjoitussanakirjan, ja TPdf.ValidatePades luokittelee PAdES-perustason (baseline level). Tämä on ensimmäinen kolmesta PDF-allekirjoituksia käsittelevästä artikkelista; seuraavat kaksi käsittelevät B-B-allekirjoituksen luomista ja pitkäaikaisten aikaleimojen lisäämistä. Yksi raja on kuitenkin asetettava heti alkuun: kaikki tässä on tarkastelua (inspection), ja allekirjoituksen väitteiden lukeminen on eri asia kuin sen kryptografian todentaminen tai allekirjoittajaan luottaminen

Miksi PDF-allekirjoitussanakirja ei ole vain tavumöykky

PDF-allekirjoitus on sanakirja (dictionary), ei läpinäkymätön liitetiedosto, ja sen kaksi tärkeintä merkintää kertovat, kuinka paljon tiedostosta on todella suojattu. ISO 32000-1 §12.8 määrittelee allekirjoitussanakirjan /ByteRange- ja /Contents-merkinnöillä. /Contents sisältää heksakoodatun CMS SignedData -rakenteen (RFC 5652), kryptografisen kuoren, joka kuljettaa allekirjoittajan varmenteen, allekirjoitetut määritteet ja itse allekirjoitusarvon. /ByteRange on osa, jota kehittäjät aliarvioivat: se on kahden siirtymä-pituus-välin (offset-length spans) taulukko, joka yhdessä kattaa koko tiedoston lukuun ottamatta /Contents-heksamerkkijonoa. Tämä aukko on juuri se paikka, jossa allekirjoitustavut istuvat, ja kaksi väliä kummallakin puolella ovat juuri ne, joihin allekirjoitus sitoutuu

ByteRange-rakenne tekee inkrementaalisesta tallennuksesta auditoitavan. Koska allekirjoittaja ei voi laskea tiivistettä (hash) allekirjoitustavuille, joita ei vielä ole olemassa, tiedosto jaetaan /Contents-paikkamerkin ympärille ja kaikki muu tiivistetään allekirjoitukseen. Allekirjoitus, jonka ByteRange ei ulotu tiedoston loppuun, on varoitusmerkki: myöhemmän inkrementaalisen päivityksen kautta tehty sisällön lisääminen suojatun alueen jälkeen ei rikkoisi allekirjoitusta, vaikka se muuttaa sitä mitä lukija näkee. Siksi ensimmäinen asia, jonka vakava tarkastaja tarkistaa, ei ole se kuka allekirjoitti, vaan kattaako allekirjoitus ne tavut, jotka se näyttää vahvistavan

Allekirjoitussanakirjan lukeminen PDFiumin vain luku -rajapinnan kautta

PDFium-komponentti tuo allekirjoitussanakirjan esille kahden vain luku -jäsenen kautta: SignatureCount ja Signature[Index]-tietueen. Taustalla ne kutsuvat funktioita FPDF_GetSignatureCount, FPDF_GetSignatureObject ja FPDFSignatureObj_*-lukijoita arvoille /SubFilter, /ByteRange, /Contents, /Reason ja allekirjoitusaika. Vain luku on tässä ratkaiseva sana: PDFium pystyy luettelemaan ja lukemaan allekirjoituksia, mutta sillä ei ole rajapintaa sellaisen luomiseen tai kirjoittamiseen. Tämän vuoksi tämän sarjan allekirjoittava puoli on toteutettu itse kirjaston, eikä PDFiumin toimesta

var
  Pdf: TPdf;
  i: Integer;
  Sig: TPdfSignature;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'contract-signed.pdf';
    Pdf.Active := True;
    for i := 0 to Pdf.SignatureCount - 1 do
    begin
      Sig := Pdf.Signature[i];
      Writeln('SubFilter : ', Sig.Encoding);        // ETSI.CAdES.detached, adbe.pkcs7.detached, ...
      Writeln('Signed at : ', Sig.Time);            // allekirjoittajan ilmoittama päivämäärä, esim. D:20260708120000+02'00'
      Writeln('Reason    : ', Sig.Reason);
      Writeln('CMS length: ', Length(Sig.Content)); // raaka DER SignedData otettuna /Contents-kentästä
      Writeln('DocMDP    : ', Sig.Permission);      // 0 = ei sertifiointia, 1..3 = MDP-taso
    end;
  finally
    Pdf.Free;
  end;
end;

Jokainen TPdfSignature-tietue mäppäytyy suoraan sanakirjaan. Encoding on /SubFilter, eli kaikkein diagnosoivin kenttä, koska se nimeää allekirjoituksen käsittelijän ja erottaa heti modernin ETSI.CAdES.detached-allekirjoituksen vanhasta tai kielletystä. Time on allekirjoittajan ilmoittama allekirjoitusaika PDF-päivämäärämerkkijonona, mikä on allekirjoittajan väite eikä suinkaan luotettu aika. Content on raaka CMS SignedData, ja Permission paljastaa DocMDP-sertifiointitason (0 tavalliselle hyväksyntäallekirjoitukselle, 1-3 sertifiointiallekirjoitukselle, joka lukitsee myöhemmät muutokset). Yksi kenttä, jota tietue ei tuo esille, on jäsennetty ByteRange. Tämä jättäminen on tietoinen valinta, koska ValidatePades tekee ByteRange-aluelaskennan puolestasi sen sijaan, että joutuisit laskemaan sen käsin uudelleen

Mitä eroa on PAdES-tasoilla B-B, B-T, B-LT ja B-LTA?

Neljä PAdES-perustasoa muodostavat tikkaat minimaalisen valideista allekirjoituksista aina vuosikymmenten arkistointia varten rakennettuihin, ja kukin taso sisältää tiukasti sen alla olevan tason. ETSI EN 319 142-1 määrittelee ne tasoina B-B, B-T, B-LT ja B-LTA. B-B (Basic) on allekirjoitus ja pakolliset allekirjoitetut määritteet eikä mitään muuta. B-T (Timestamp) lisää luotetun RFC 3161 -aikaleiman allekirjoituksen päälle, joten allekirjoitushetken todistaa aikaleimaviranomainen (TSA) allekirjoittajan oman kellon sijasta. B-LT (Long-Term) upottaa todentamismateriaalin — varmenneketjun ja valinnaisesti OCSP- tai CRL-vastaukset — tiedoston sisälle, joten allekirjoitus voidaan todentaa vuosia myöhemmin, kun varmentaja on jo poistunut. B-LTA (Long-Term with Archive timestamp) käärii asiakirjan aikaleiman (document timestamp) tämän materiaalin ympärille, suojaten pitkäaikaisia tietoja ja tarjoten pisteen uudelleenleimausta varten ennen kuin taustalla oleva kryptografia vanhenee

Käytännön luenta koskee aikahorisonttia. B-B-allekirjoitus vastaa kysymykseen "allekirjoittiko joku tämän". B-T vastaa kysymykseen "ja milloin, todistettavasti". B-LT vastaa kysymykseen "ja voinko edelleen tarkistaa sen, kun varmenteet ovat vanhentuneet". B-LTA vastaa kysymykseen "ja kestääkö tuo tarkistus vielä kahdenkymmenen vuoden päästä". Sääntelyprofiilit valitsevat tasonsa: monet verolaskutus- ja eIDAS-ympäristöt vaativat vähintään B-T-tason, ja arkistointimandaatit vaativat B-LT- tai B-LTA-tasoa. Sen tietäminen, minkä tason asiakirja todella saavuttaa ennen kuin hyväksyt tai hylkäät sen, on tarkastusvaiheen ydin

Perustason havaitseminen TPdf.ValidatePades-metodilla

PDFium-komponentti tiivistää koko tasokysymyksen yhteen kutsuun. TPdf.ValidatePades palauttaa TPadesValidationResult-tietueen, jonka Level-kenttä on TPadesLevel-tyyppiä — plNone, plUnknown, plB_B, plB_T, plB_LT tai plB_LTA — yhdessä ongelmaluettelon, allekirjoitusmäärän ja asiakirjan aikaleimojen määrän kanssa. Taso päätellään nousevasti: validaattori vahvistaa ensin B-B-tason, korottaa sen sitten B-T-tasoksi, jos allekirjoituksen aikaleima tai asiakirjan aikaleima on olemassa, B-LT-tasoksi, jos luettelossa on /DSS-sanakirja varmenteineen ja /Extensions /ESIC Level 1 -merkki, ja B-LTA-tasoksi, jos sekä asiakirjan aikaleima että ESIC Level 2 -merkki ovat olemassa. Kaksi apuria tekee tuloksesta toiminnallisen: IsCompliant on True vain, kun taso saavuttaa vähintään B-B-tason ja ongelmaluettelo on tyhjä, ja IsCompliantAt mahdollistaa politiikan alarajan (kuten plB_T) vaatimisen

var
  Pdf: TPdf;
  R: TPadesValidationResult;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'contract-signed.pdf';
    Pdf.Active := True;
    R := Pdf.ValidatePades;
    case R.Level of
      plNone:    Writeln('Ei PAdES-allekirjoitusta');
      plUnknown: Writeln('Allekirjoitus on olemassa, mutta tasoa ei voida määrittää');
      plB_B:     Writeln('PAdES B-B   (perus)');
      plB_T:     Writeln('PAdES B-T   (luotettu aikaleima)');
      plB_LT:    Writeln('PAdES B-LT  (upotettu pitkäaikainen materiaali)');
      plB_LTA:   Writeln('PAdES B-LTA (arkistoaikaleima)');
    end;
    Writeln('Signatures   : ', R.SignatureCount);
    Writeln('DocTimeStamps: ', R.DocTimeStampCount);
    if R.IsCompliantAt(plB_T) then
      Writeln('Täyttää B-T-tason vähimmäisvaatimuksen')
    else
      Writeln('Alittaa vaaditun B-T-tason');
  finally
    Pdf.Free;
  end;
end;

Miksi adbe.pkcs7.sha1 on kielletty SubFilter?

Koska SHA-1 on murrettu ja adbe.pkcs7.sha1-käsittelijä käyttää sitä kiinteästi. Tämä SubFilter laskee asiakirjan tiivisteen SHA-1-algoritmillä ennen sen käärimistä PKCS#7:ään, ja SHA-1 on ollut alttiina törmäyshyökkäyksille (collision-vulnerable) jo vuosia. Siksi standardi EN 319 142-1 lauseke 6.3 kieltää sen suoraan perustason allekirjoituksilta. ValidatePades nostaa virheen ppeiForbiddenSubFilter, kun se näkee arvon adbe.pkcs7.sha1 tai adbe.x509.rsa_sha1, ja se nostaa virheen ppeiBadDigestAlgorithm, when itse CMS käyttää MD5- tai SHA-1-algoritmiä tiivisteenä (lauseke 6.2.1). Nämä ovat kaksi erillistä tarkistusta, jotka pyydystävät saman heikkousluokan eri tasoilla

Ongelmaluettelossa on yhteensä 26 jäsentä, ja useimmin kohdattavat liittyvät rakenteeseen ja kattavuuteen. ppeiByteRangeNotCoveringFile on aiemmin kuvattu kattavuustarkistus. ppeiForbiddenCertKey laukeaa, kun allekirjoitussanakirja sisältää /Cert-merkinnän, minkä PAdES kieltää, koska varmenneketjun on sijaittava CMS-rakenteen SignedData.certificates-kentässä. ppeiMissingSigningCertificate, ppeiMissingContentType ja ppeiMissingMessageDigest liputtavat puuttuvat pakolliset allekirjoitetut määritteet, ja ppeiDetachedContentViolation pyydystää allekirjoituksen, joka virheellisesti upottaa allekirjoitetun sisällön sen irrottamisen sijaan. Luettelon läpikäynti muuttaa pelkän hylkäyksen diagnoosiksi, jonka voit kirjata lokiin

var
  R: TPadesValidationResult;
  Issue: TPadesValidationIssue;
begin
  R := Pdf.ValidatePades;
  if R.Issues <> [] then
    for Issue := Low(TPadesValidationIssue) to High(TPadesValidationIssue) do
      if Issue in R.Issues then
        Writeln('Issue: ',
          GetEnumName(TypeInfo(TPadesValidationIssue), Ord(Issue)));
end;

Mitä ValidatePades ei tarkista

ValidatePades todentaa rakenteen, ei luottamusta, ja näiden kahden sekoittaminen keskenään on vaarallinen virhe. Tulos plB_LTA tarkoittaa, että asiakirja sisältää oikein muotoillun B-LTA-allekirjoituksen, jossa kaikki vaaditut määritteet, materiaalit ja aikaleimat ovat oikeissa paikoissa — se ei tarkoita, että allekirjoitus on kryptografisesti validi, että varmenneketju johtaa luotettuun juurivarmenteeseen tai että mitään ketjun varmenteista ei ole peruutettu. Validaattori ei tarkoituksella suorita kryptografista todentamista: se ei laske allekirjoitusta uudelleen ByteRange-alueelta, ei rakenna tai arvioi luottamusketjua eikä tarkista OCSP- tai CRL-peruutustietoja. Tämä jako on tarkoituksellinen ja hyödyllinen, koska rakenteellinen tarkastus on nopeaa, täysin determinististä eikä vaadi avaimia, verkkoa tai alustan kryptografiaa. Siten ValidatePades toimii identtisest Windowsissa, Linuxissa ja macOS:ssä puhtaana Pascal-koodina tiedoston tavujen yli. Luottamusketjun todentaminen sen sijaan on erottamatonta politiikasta — mihin juurivarmisteisiin luotat, miten haet peruutustiedot, mikä on aikaleiman sietokykysi —, ja se kuuluu myöhempään vaiheeseen, joka riippuu alustan varmennesäilöstä. Käsittele siis onnistunutta ValidatePades-kutsun tulosta välttämättömänä ehtona sille, että allekirjoitus on muotoiltu oikein, ja välitä rakenteellisesti ehjä allekirjoitus varsinaiseen kryptografiseen todentamiseen ennen kuin luotat siihen

Tämä rakenteellinen tarkistus on oikea paikka aloittaa, ja se sopii luontevasti yhteen laajempien lukutarkistusten kanssa, joita käsitellään artikkelissa PDF-turvallisuusriskien auditointi PDFium-komponentilla ja konformisuustyössä, kuten artikkelissa painovalmiiden PDF/X-asiakirjojen validointi. Kun pystyt lukemaan ja luokittelemaan allekirjoituksen, seuraava vaihe on sellaisen luomisen: tämän sarjan toinen artikkeli kattaa PDF-tiedostojen allekirjoittamisen PAdES B-B -tasolla, ja kolmas laajentaa sen luotettuihin aikaleimoihin ja pitkäaikaisiin B-LT- ja B-LTA-allekirjoituksiin. Vain luku -allekirjoitustarkastus ja ValidatePades-luokittelija, jotka tässä esitetään, ovat osa Delphille, C++Builderille ja Lazarukselle tarkoitettua PDFium-komponenttia