Tehnični članek

Pregled podpisov PDF in ravni PAdES v Delphi

Prejeli ste podpisan PDF in v svojem pregledovalniku morate prikazati, kdo ga je podpisal, kdaj je bil podpisan, ali podpis pokriva celotno datoteko in kako daleč gre glede dolgoročne skladnosti. Komponenta PDFium Component za Delphi in Lazarus odgovori na vsa štiri vprašanja z bralnimi klici: FPDF_GetSignatureCount in družina FPDFSignatureObj_* izpostavita slovar podpisov, TPdf.ValidatePades pa klasificira izhodiščno raven PAdES. To je prvi od treh člankov o podpisih PDF s PDFium; naslednja dva pokrivata ustvarjanje podpisa B-B in dodajanje dolgoročnih časovnih žigov. Ena meja pa je jasna že na začetku: vse tukaj je le pregled, branje trditev podpisa pa je povsem drugačna naloga od preverjanja njegove kriptografije ali odločanja o tem, ali podpisniku zaupate

Zakaj slovar podpisov PDF ni le skupina (blob) bajtov

Podpis PDF je slovar in ne neprozorna priloga, njegovo najpomembnejša vnosa pa vam povesta, kolikšen del datoteke je dejansko zaščiten. ISO 32000-1 §12.8 definira slovar podpisov z vnosoma /ByteRange in /Contents. /Contents drži heksadecimalno kodirano strukturo CMS SignedData (RFC 5652), kriptografsko ovojnico, ki prenaša potrdilo podpisnika, podpisane atribute in sam podpis. /ByteRange pa je tisti del, ki ga razvijalci podcenjujejo: to je polje dveh razponov odmikov in dolžin, ki skupaj pokrivata celotno datoteko razen heksadecimalnega niza /Contents. Ta vrzel je natanko mesto, kjer sedijo bajti podpisa, oba razpona na obeh straneh pa sta natanko tisto, čemur se podpis zavezuje

Zasnova ByteRange je tisto, zaradi česar je inkrementalno shranjevanje mogoče revidirati. Ker podpisnik ne more zgoščevati bajtov podpisa, ki še ne obstajajo, se datoteka razdeli okoli placeholderja /Contents, vse ostalo pa se zgošča v podpis. Podpis, katerega ByteRange ne doseže konca datoteke, je opozorilni znak: vsebina, dodana po pokritem obsegu prek poznejše inkrementalne posodobitve, ne bi prelomila podpisa, čeprav spremeni tisto, kar bralec vidi. Zato prva stvar, ki jo resen pregledovalec preveri, ni to, kdo je podpisal, temveč ali podpis pokriva bajte, ki jih na videz potrjuje

Reading the signature dictionary with PDFium's read-only API

Komponenta PDFium Component razkriva slovar podpisov prek dveh članov le za branje: SignatureCount in zapisa Signature[Index]. Pod pokrovom pokličeta FPDF_GetSignatureCount, FPDF_GetSignatureObject in dostopne metode FPDFSignatureObj_* za /SubFilter, /ByteRange, /Contents, /Reason ter čas podpisovanja. "Le za branje" je tukaj ključna fraza: PDFium lahko našteje in prebere podpise, nima pa API-ja za ustvarjanje ali pisanje podpisov, zato je podpisni del te serije implementiran s strani same knjižnice in ne s strani PDFium-a

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);            // signer date string, e.g. D:20260708120000+02'00'
      Writeln('Reason    : ', Sig.Reason);
      Writeln('CMS length: ', Length(Sig.Content)); // raw DER SignedData taken from /Contents
      Writeln('DocMDP    : ', Sig.Permission);      // 0 = no certification, 1..3 = MDP level
    end;
  finally
    Pdf.Free;
  end;
end;

Vsak zapis TPdfSignature se preslika neposredno v slovar. Encoding je /SubFilter, najbolj diagnostično polje, saj poimenuje rokovalnik podpisa in takoj loči sodoben podpis ETSI.CAdES.detached od zastarelega ali prepovedanega. Time je avtorjev deklarirani čas podpisovanja kot niz datuma PDF, kar je trditev podpisnika in ne zaupanja vreden čas. Content je surovi CMS SignedData, Permission pa razkriva stopnjo certificiranja DocMDP (0 za običajen podpis odobritve, 1 do 3 za certifikacijski podpis, ki zaklene poznejše spremembe). Edino polje, ki ga zapis ne razkriva, je razčlenjeni ByteRange, in ta opustitev je namerna, saj ValidatePades opravi aritmetiko pokritosti ByteRange namesto vas, namesto da bi jo morali ročno računati

Kakšna je razlika med PAdES B-B, B-T, B-LT in B-LTA?

Štiri izhodiščne ravni PAdES sestavljajo lestev od minimalno veljavnega podpisa do tistega, ki je zgrajen, da preživi desetletja arhiviranja, vsaka raven pa strogo vsebuje tisto pod njo. ETSI EN 319 142-1 jih definira kot B-B, B-T, B-LT in B-LTA. B-B (Basic) is the signature plus the mandatory signed attributes and nothing more. B-T (Timestamp) doda zaupanja vreden časovni žig RFC 3161 čez podpis, tako da trenutek podpisovanja potrdi organ za časovno žigosanje in ga ne trdi le podpisnikova ura. B-LT (Long-Term) vgradi potrditveno gradivo — verigo potrdil in neobvezno odzive OCSP ali CRL — znotraj datoteke, tako da je podpis mogoče potrditi leta pozneje, ko izdajateljske infrastrukture ni več. B-LTA (Long-Term z arhivskim časovnim žigom) pa ovije časovni žig dokumenta okoli tega gradiva, s čimer zaščiti dolgoročne podatke in vam da točko za ponovno časovno žigosanje, preden osnovna kriptografija zastara

Praktično branje govori o časovnem horizontu. Podpis B-B odgovarja na "ali je to nekdo podpisal". B-T odgovarja na "in kdaj, dokazljivo". B-LT odgovarja na "in ali ga še vedno lahko preverim, ko potrdila potečejo". B-LTA pa odgovarja na "in ali bo to preverjanje držalo čez dvajset let". Regulativni profili izberejo prečko: številni konteksti e-računov in eIDAS zahtevajo vsaj B-T, arhivski mandati pa segajo po B-LT ali B-LTA. Vedeti, katero prečko dokument dejansko doseže, preden ga sprejmete ali zavrnete, je celotna tema koraka pregleda

Zaznavanje izhodiščne ravni s TPdf.ValidatePades

Komponenta PDFium Component zmanjša celotno vprašanje ravni na en sam klic. TPdf.ValidatePades vrne zapis TPadesValidationResult, katerega polje Level je TPadesLevelplNone, plUnknown, plB_B, plB_T, plB_LT ali plB_LTA — skupaj z naborom težav, številom podpisov in številom časovnih žigov dokumenta. Raven se izpeljuje monotono: validator najprej vzpostavi B-B, nato ga poviša v B-T, če je prisoten časovni žig podpisa ali časovni žig dokumenta, v B-LT, če katalog prenaša /DSS s potrdili in označevalec /Extensions /ESIC ravni 1, ter v B-LTA, če sta prisotna časovni žig dokumenta in označevalec ESIC ravni 2. Pomočnika naredita rezultat uporaben: IsCompliant je True le, ko raven doseže vsaj B-B in je nabor težav prazen, IsCompliantAt pa vam omogoča uveljavitev spodnje meje politike, kot je plB_T

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('No PAdES signature present');
      plUnknown: Writeln('Signature present but level undeterminable');
      plB_B:     Writeln('PAdES B-B   (basic)');
      plB_T:     Writeln('PAdES B-T   (trusted timestamp)');
      plB_LT:    Writeln('PAdES B-LT  (long-term material embedded)');
      plB_LTA:   Writeln('PAdES B-LTA (archive timestamp)');
    end;
    Writeln('Signatures   : ', R.SignatureCount);
    Writeln('DocTimeStamps: ', R.DocTimeStampCount);
    if R.IsCompliantAt(plB_T) then
      Writeln('Meets the B-T policy floor')
    else
      Writeln('Below the required B-T level');
  finally
    Pdf.Free;
  end;
end;

Zakaj je adbe.pkcs7.sha1 prepovedan SubFilter?

Ker je SHA-1 zlomljen in ga rokovalnik adbe.pkcs7.sha1 zapeče vanj. Ta SubFilter vnaprej zgosti dokument s SHA-1, preden ga ovije v PKCS#7, SHA-1 pa je že leta ranljiv za kolizije, zato klavzula 6.3 standarda EN 319 142-1 to popolnoma prepoveduje za izhodiščni podpis. ValidatePades sproži ppeiForbiddenSubFilter, ko vidi adbe.pkcs7.sha1 ali adbe.x509.rsa_sha1, in sproži ppeiBadDigestAlgorithm, ko CMS sam uporablja MD5 ali SHA-1 kot izvleček sporočila (klavzula 6.2.1). To sta dva ločena pregleda, ki lovita isto vrsto slabosti na dveh različnih ravneh

Nabor težav ima skupaj 26 članov, tisti, ki jih boste najpogosteje srečali, pa se zbirajo okoli strukture in pokritosti. ppeiByteRangeNotCoveringFile je pregled pokritosti, opisan prej. ppeiForbiddenCertKey se sproži, ko slovar podpisov prenaša vnos /Cert, kar PAdES prepoveduje, saj mora veriga namesto tega živeti znotraj CMS-ja SignedData.certificates. ppeiMissingSigningCertificate, ppeiMissingContentType in ppeiMissingMessageDigest označujejo manjkajoče obvezne podpisane atribute, ppeiDetachedContentViolation pa ujame podpis, ki napačno vgradi podpisano vsebino, namesto da bi jo ločil. Naštevanje nabora spremeni golo zavrnitev v diagnozo, ki jo lahko zabeležite

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;

Česar ValidatePades ne preverja

ValidatePades potrjuje strukturo in ne zaupanja, zamenjava enega za drugega pa je nevarna napaka. Rezultat plB_LTA pomeni, da dokument vsebuje pravilno oblikovan podpis B-LTA z vsemi predpisanimi atributi, gradivi in časovnimi žigom na pravih mestih — ne pomeni pa, da je podpis kriptografsko veljaven, da se veriga potrdil povezuje s korenom (root), ki mu zaupate, ali da nobeno potrdilo v verigi ni bilo preklicano. Validator namerno ne izvaja kriptografskega preverjanja: ne preračuna podpisa čez ByteRange, ne zgradi in ne ovrednoti verige zaupanja ter ne preveri stanja preklica OCSP ali CRL. Ta razdelitev je namerna in uporabna, ker je strukturni pregled hiter, popolnoma determinističen in ne potrebuje ključev, omrežja ali kriptografije platforme, zato ValidatePades teče enako na sistemih Windows, Linux in macOS kot čisti Pascal čez bajte datoteke. Preverjanje verige zaupanja pa je nasprotno neločljivo od politike — katerim korenskim potrdilom zaupate, kako pridobivate preklic, kako stroga je vaša toleranca do časovnih žigov —, in spada v poznejšo fazo, ki je odvisna od shrambe potrdil platforme. Zato obravnavajte uspešno opravljen ValidatePades kot nujen pogoj, da je podpis pravilno oblikovan, nato pa strukturno zdrav podpis predajte pravemu kriptografskemu preverjanju, preden se nanj zanesete

Ta strukturni prehod je pravo mesto za začetek in se naravno poveže z širšimi bralnimi pregledi v reviziji varnostnih tveganj PDF s komponento PDFium Component ter z delom o skladnosti formatov, kot je potrjevanje pripravljenih dokumentov PDF/X za tisk. Ko lahko preberete in razvrstite podpis, je naslednji korak ustvarjanje enega: drugi članek v tej seriji pokriva podpisovanje PDF-jev s PAdES B-B, tretji pa to razširi na zaupanja vredne časovne žige ter dolgoročne podpise B-LT in B-LTA. Pregled podpisov le za branje in klasifikator ValidatePades, prikazana prag tukaj, so del komponente PDFium Component za Delphi, C++Builder in Lazarus