Teknisk artikkel

Inspeksjon av PDF-signaturer og PAdES-nivåer i Delphi

Du har mottatt en signert PDF og må vise i visningsprogrammet ditt hvem som signerte den, når den ble signert, om signaturen dekker hele filen, og hvor langt den går mot langsiktig samsvar. PDFium Component for Delphi og Lazarus svarer på alle fire spørsmålene med skrivebeskyttede kall: FPDF_GetSignatureCount og FPDFSignatureObj_*-familien eksponerer signaturordboken, og TPdf.ValidatePades klassifiserer PAdES-baselinjenivået. Dette er den første av tre artikler om PDF-signaturer med PDFium; de to som følger dekker opprettelse av en B-B-signatur og tilføying av langsiktige tidsstempler. En grense må imidlertid slås fast først: alt her er inspeksjon, og å lese hva en signatur hevder er en annen jobb enn å verifisere dens kryptografi eller bestemme om du stoler på signataren

Hvorfor en PDF-signaturordbok ikke bare er en rå byteblokk

En PDF-signatur er en ordbok, ikke et ugjennomsiktig vedlegg, og de to viktigste oppføringene forteller deg hvor mye av filen som faktisk er beskyttet. ISO 32000-1 §12.8 definerer signaturordboken med en /ByteRange- og en /Contents-oppføring. /Contents inneholder en heksadesimal-kodet CMS SignedData-struktur (RFC 5652), den kryptografiske konvolutten som bærer signatursertifikatet, de signerte attributtene og selve signaturverdien. /ByteRange er delen utviklere undervurderer: det er en tabell med to forskyvnings- og lengdeområder som til sammen dekker hele filen unntatt heksadesimalstrengen i /Contents. Det gapet er nøyaktig der signaturbytene sitter, og de to områdene på hver side er akkurat det signaturen forplikter seg til

Designet av ByteRange er det som gjør en trinnvis lagring revisjonsvennlig. Fordi en signatar ikke kan hashe signaturbyter som ikke eksisterer ennå, deles filen rundt /Contents-plassholderen, og alt annet hashes inn i signaturen. En signatur der ByteRange ikke når slutten av filen er et advarselstegn: innhold som legges til etter det dekkede området gjennom en senere trinnvis oppdatering, vil ikke bryte signaturen selv om det endrer hva leseren ser. Så det første en seriøs inspektør sjekker, er ikke hvem som signerte, men om signaturen dekker bytene den ser ut til å godkjenne

Lese signaturordboken med PDFiums skrivebeskyttede API

PDFium-komponenten eksponerer signaturordboken gjennom to skrivebeskyttede medlemmer: SignatureCount og Signature[Index]-posten. Under panseret kaller de FPDF_GetSignatureCount, FPDF_GetSignatureObject og FPDFSignatureObj_*-aksessorene for /SubFilter, /ByteRange, /Contents, /Reason og signeringstidspunktet. Skrivebeskyttet er nøkkelordet her: PDFium kan liste og lese signaturer, men har ikke noe API for å opprette eller skrive en, og det er grunnen til at signeringssiden i denne serien implementeres av selve biblioteket snarere enn av PDFium

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;

Hva er forskjellen mellom PAdES B-B, B-T, B-LT og B-LTA?

De fire PAdES-baselinjenivåene danner en stige fra en minimalt gyldig signatur til en som er bygget for å overleve tiår med arkivering, og hvert nivå inneholder strengt tatt nivået under. ETSI EN 319 142-1 definerer dem som B-B, B-T, B-LT og B-LTA. B-B (Basic) is signaturen pluss de obligatoriske signerte attributtene og ingenting mer. B-T (Timestamp) legger til et klarert RFC 3161-tidsstempel over signaturen, slik at signeringstidspunktet bekreftes av en tidsstempelautoritet i stedet for å bli hevdet av signatarens egen klokke. B-LT (Long-Term) bygger inn valideringsmaterialet — sertifikatkjeden og eventuelt OCSP- eller CRL-svar — inne i filen, slik at signaturen fortsatt kan valideres år senere når utstedelsesinfrastrukturen er borte. B-LTA (Long-Term med arkivtidsstempel) pakker inn et dokumenttidsstempel rundt dette materialet, noe som beskytter selve langtidsdataene og gir deg et punkt for å tidsstemple på nytt før den underliggende kryptografien foreldes

Den praktiske tolkningen handler om tidshorisont. En B-B-signatur svarer på "har noen signert dette". B-T svarer på "og når, beviselig". B-LT svarer på "og kan jeg fortsatt sjekke det etter at sertifikatene utløper". B-LTA svarer på "og vil den sjekken fortsatt være gyldig om tjue år". Reguleringsprofiler velger et trinn: mange e-fakturerings- og eIDAS-kontekster krever minst B-T, og arkiveringsmandater krever B-LT eller B-LTA. Å vite hvilket trinn et dokument faktisk når, før du godtar eller avviser det, er hele poenget med inspeksjonstrinnet

Detektere baselinjenivået med TPdf.ValidatePades

PDFium-komponenten reduserer hele nivåspørsmålet til ett kall. TPdf.ValidatePades returnerer en TPadesValidationResult-post der Level-feltet er en TPadesLevelplNone, plUnknown, plB_B, plB_T, plB_LT eller plB_LTA — sammen med et sett med problemer, et signaturantall og et dokumenttidsstempelantall. Nivået utledes monotont: validatoren etablerer B-B først, og oppgraderer deretter til B-T hvis et signaturtidsstempel eller et dokumenttidsstempel er til stede, til B-LT hvis katalogen inneholder en /DSS med sertifikater og /Extensions /ESIC Nivå 1-markøren, og til B-LTA hvis både et dokumenttidsstempel og ESIC Nivå 2-markøren er til stede. To hjelpere gjør resultatet brukbart: IsCompliant er True bare når nivået når minst B-B og settet med problemer er tomt, og IsCompliantAt lar deg håndheve en nedre policygrense som 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;

Hvorfor er adbe.pkcs7.sha1 et forbudt SubFilter?

Fordi SHA-1 er utdatert og adbe.pkcs7.sha1-behandleren bygger den inn. Dette SubFilteret forhånds-hasher dokumentet med SHA-1 før det pakkes inn i PKCS#7, og SHA-1 har vært sårbar for kollisjoner i årevis, så EN 319 142-1 klausul 6.3 forbyr det fullstendig for en baselinesignatur. ValidatePades utløser ppeiForbiddenSubFilter når den ser adbe.pkcs7.sha1 eller adbe.x509.rsa_sha1, og den utløser ppeiBadDigestAlgorithm når CMS-en selv bruker MD5 eller SHA-1 som meldingssammendrag (klausul 6.2.1). Dette er to separate sjekker som fanger opp samme type svakhet på to forskjellige lag

Settet med problemer har totalt 26 medlemmer, og de du vil støte på oftest samler seg rundt struktur og dekning. ppeiByteRangeNotCoveringFile er dekningssjekken beskrevet tidligere. ppeiForbiddenCertKey utløses når signaturordboken inneholder en /Cert-oppføring, noe PAdES forbyr fordi kjeden i stedet må ligge inne i CMS SignedData.certificates. ppeiMissingSigningCertificate, ppeiMissingContentType og ppeiMissingMessageDigest markerer obligatoriske signerte attributter som mangler, og ppeiDetachedContentViolation fanger opp en signatur som feilaktig bygger inn det signerte innholdet i stedet for å koble det fra. Å gå gjennom settet gjør en ren avvisning om til en diagnose du kan loggføre

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;

Hva ValidatePades ikke sjekker

ValidatePades validerer struktur, ikke tillit, og å forveksle disse to er en alvorlig feil. Et resultat på plB_LTA betyr at dokumentet inneholder en velformet B-LTA-signatur med alle de påkrevde attributtene, materialene og tidsstemplene på rett plass — det betyr ikke at signaturen er kryptografisk gyldig, at sertifikatet kan spores til en rot du stoler på, eller at ingen sertifikater i kjeden er tilbakekalt. Validatoren utfører bevisst ingen kryptografisk verifisering: den beregner ikke signaturen på nytt over ByteRange, bygger eller evaluerer ikke tillitskjeden, og sjekker ikke OCSP- eller CRL-tilbakekallingsstatus. Det skillet er tilsiktet og nyttig, fordi strukturell inspeksjon er rask, fullstendig deterministisk og krever ingen nøkler, nettverk eller plattformkryptografi, så ValidatePades kjører identisk på Windows, Linux og macOS som ren Pascal over filbytene. Validering av tillitskjede er derimot uadskillelig fra policy — hvilke røtter du stoler på, hvordan du henter tilbakekallingsinformasjon, hvor streng tidsstempeltoleransen din er — og det hører hjemme på et senere trinn som avhenger av plattformens sertifikatlager. Se derfor på en bestått ValidatePades som den nødvendige bekreftelsen på at en signatur er riktig utformet, og overlat deretter en strukturelt sunn signatur to faktisk kryptografisk verifisering før du stoler på den

Den strukturelle sjekken er det rette stedet å starte, og den passer naturlig sammen med de mer omfattende skrivebeskyttede sjekkene i revisjon av PDF-sikkerhetsrisikoer med PDFium-komponenten og med formattilpasningsarbeid som validering av utskriftsklare PDF/Xdokumenter. Når du kan lese og klassifisere en signatur, er neste trinn å produsere en: den andre artikkelen i denne serien dekker signering av PDF-er med PAdES B-B, og den tredje utvider dette til klarerte tidsstempler og langsiktige B-LT- og B-LTA-signaturer. Den skrivebeskyttede signaturinspeksjonen og ValidatePades-klassifisereren som vises her er en del av PDFium-komponenten for Delphi, C++Builder og Lazarus