En validator avviser en PAdES-signatur av én av tre grunner i nesten alle tilfeller vi har feilsøkt med PDFium-komponenten i Delphi: tabellen /ByteRange inneholder fremdeles sine null-plassholdere, signeringstiden /M er ikke en velformet PDF-datostreng, or en inkrementell oppdatering droppet /Encrypt-oppføringen fra et kryptert dokument. Alle tre produserer filer som åpnes fint, rendres fint, og feiler i det øyeblikket en kompatibel validator leser signaturordboken
Scenarioet som motiverer denne artikkelen er smertefullt spesifikt. Du signerer en kontrakt med arbeidsflyten fra artikkelen om PAdES B-B-signering, din egen inspeksjonskode rapporterer at signaturen er til stede og strukturelt sunn, hver lokale test er grønn — og deretter laster motparten opp filen til sin valideringsplattform og får et rødt kryss. Ingenting ved feilen er synlig i en viser, fordi ingen av disse tre manglene berører sideinnholdet. De lever helt og holdent i signaturordboken og fil-traileren, som er akkurat der validatorer ser og visere stort sett ikke gjør det
Hvorfor er min PDF-signatur-ByteRange ugyldig?
En ByteRange-avvisning betyr nesten alltid at de fire feltene aldri ble fylt ut, ikke at områdene er subtilt feil. Tabellen /ByteRange [A B C D] deklarerer to spenn, byte A til A+B og byte C til C+D, og EN 319 142-1 §6.3 (krav k) krever at de til sammen dekker hele filen unntatt den heksadesimal-kodede /Contents-strengen. I tall: A is 0, C >= A+B, C+D er lik fillengden, og gapet mellom B og C inneholder nøyaktig <...>-heks-strengen — to parentes-byte pluss to heks-karakterer per byte med CMS-data. En validator som leser [0 0 0 0] konkluderer med at signaturen ikke dekker noe og avviser den, uansett hvor korrekt CMS-strukturen inni /Contents er
Mekanikken bak hvordan dette skjer er verdt å forstå, fordi det samme mønsteret finnes i alle signerings-stakker. En signerer kan ikke vite de endelige avvikene før filen er lagt ut, så skrivefunksjonen sender ut tabellen med null-plassholdere med fast bredde og fyller dem inn etter utleggingen. I utgivelser av PDFium-komponenten før 2.14.1 søkte denne innfyllingen etter plassholdermønsteret fra og med /Contents-posisjonen — men /ByteRange sitter før /Contents i ordboken, så søket fant ingenting og alle tre erstatningene mislyktes i det stille. CMS-sammendraget ble beregnet over de riktige områdene, så kryptografien var sunn; deklarasjonen av disse områdene forble på null, så enhver kompatibel validator avviste filen. PAdES B-LTA Document Time-stamps, som bruker samme ordbokoppsett, feilet på samme måte — relevant hvis du bygger på langsiktige B-LT- og B-LTA-signaturer. Versjon 2.14.1 fyller inn fra starten av signaturobjektet, og fiksingen dekkes av en regresjonstest som tolker den produserte filen og bekrefter aritmetikken under
function SignedByteRangeCoversFile(const FileName: string): Boolean;
var
Raw: TBytes;
Text: AnsiString;
P, N: Integer;
F: array[0..3] of Int64;
begin
Raw := TFile.ReadAllBytes(FileName);
SetString(Text, PAnsiChar(@Raw[0]), Length(Raw));
P := Pos('/ByteRange', Text); // first signature only
Result := P > 0;
if not Result then Exit;
Inc(P, Length('/ByteRange'));
for N := 0 to 3 do
begin
while (P <= Length(Text)) and not (Text[P] in ['0'..'9']) do Inc(P);
F[N] := 0;
while (P <= Length(Text)) and (Text[P] in ['0'..'9']) do
begin
F[N] := F[N] * 10 + Ord(Text[P]) - Ord('0');
Inc(P);
end;
end;
// EN 319 142-1 §6.3 req k: spans cover everything except /Contents
Result := (F[0] = 0) and (F[2] >= F[0] + F[1]) and
(F[2] + F[3] = Int64(Length(Raw)));
end;
Tjue linjer med ren RTL-kode, ingen bibliotek-kall, og den fanger hele feilklassen ved produksjonstidspunktet. Hvis du beholder én påstand (assertion) fra denne artikkelen, behold denne: tolk dine egne signerte utdata og sjekk de tre likhetene før filen forlater prosessen din
Hvorfor flagger validatorer /M-signeringstiden som feilaktig?
Strenge validatorer avviser en signeringstid som ikke er en fullstendig PDF-datostreng, og de to delene som oftest mangler er D:-prefikset og UTC-avviksmarkøren. ISO 32000-1 §7.9.4 definerer datoformatet som D:YYYYMMDDHHmmSS etterfulgt av et avvik — Z for UTC, eller et signert +HH'mm'-forhold til dette. En ren 20260709143000 tolkes som en dato av en tolerant leser, men en validator som bruker grammatikken bokstavelig, ser en feilaktig streng i et felt med obligatorisk format og flagger signaturen. Utgivelser av PDFium-komponenten før 2.14.4 skrev /M-oppføringen for TPdf.SignPades og SignPadesBytes i akkurat den rene formen; siden 2.14.4 bærer oppføringen D:-prefikset og Z-markøren, slik at den deklarerte signeringstiden lyder D:20260709143000Z
To praktiske merknader knytter seg til dette feltet. For det første: skriv UTC og oppgi det; et tidsstempel uten avviksmarkør tvinger validatoren til å gjette tidssoneforholdet, og §7.9.4 behandler avviket som en del av formatet i stedet for en valgfri detalj. For det andre: husk hva /M er — signatørens deklarerte tid, en påstand snarere enn et bevis. En validator sjekker formatet her, ikke sannheten; bevisbar tid kommer fra et RFC 3161-tidsstempel på PAdES B-T og over. Å formatere et felt riktig og å stole på det er separate beslutninger, og validatorer håndhever bare den første
Hvorfor utløser SignPades EPadesCrypto på en kryptert PDF?
PDFium-komponenten nekter å signere et kryptert dokument fordi alternativet er å produsere en fil som kompatible lesere vil ødelegge ved åpning. En PAdES-signatur legges til som en inkrementell oppdatering, og ISO 32000-1 §7.5.6 krever at den nye traileren til en oppdateringsseksjon bærer hver oppføring fra forrige trailer unntatt /Prev — i et kryptert dokument inkluderer det /Encrypt. Droppes den, deklarerer den nyeste traileren filen som ukryptert, slik at en kompatibel leser tolker den krypterte kroppen som ren tekst og får søppel. Enda verre: strømmene og strengene signatøren legger til, måtte selv ha vært kryptert med dokumentnøkkelen for å være lovlige, noe en signatur-injektor i ren tekst ikke kan gjøre. Det finnes ingen måte å legge til en gyldig signatur i ren tekst på en kryptert fil, så siden versjon 2.14.2 utløser TPdf.SignPades, SignPadesBytes og InjectPadesDssMarkers unntaket EPadesCrypto i stedet for å sende ut et korrupt eller uverifiserbart resultat
try
if not Pdf.SignPades('contract-signed.pdf', AThumbprint) then
Writeln('Signing reported failure');
except
on E: EPadesCrypto do
begin
// e.g. 'SignPadesBytes: the source document is encrypted;
// remove encryption before signing'
Writeln('Cannot sign: ', E.Message);
end;
end;
Unntaket er det riktige utfallet, så utform arbeidsflyten rundt det i stedet for å fange det opp og prøve på nytt. Dekrypter først med eierautoritet, signer ren-tekst-kopien, og hvis distribusjonskanalen krever kryptering, godta at kryptering-så-signering og signering-så-kryptering produserer forskjellige gjenstander med forskjellige valideringshistorier. En signatur beregnet over byte i ren tekst kan ikke overleve re-kryptering av disse bytene, så de ærlige alternativene er en signert ren-tekst-fil eller en beslutning dokumentert ved siden av koden
Hvordan to feil kan bekrefte hverandre
ByteRange-defekten forble skjult så lenge fordi vår egen validator tok feil på en komplementær måte, og det er den mest overførbare lærdommen i denne artikkelen. Dekningssjekken i ValidatePadesCompliance krevde at det andre spennet startet nøyaktig på A+B — et null-gap — noe som feilklassifiserer standard-oppsettet der gapet inneholder /Contents-heks-strengen. Dermed avviste den interne validatoren akkurat det kompatible oppsettet og den interne generatoren produserte det aldri, slik at ende-til-ende signere-så-validere-rørledninger tilfeldigvis forble grønne. Hver feil nektet testpakken moteksempelet som ville ha avdekket den andre. Versjon 2.14.1 fikset begge sider i samme utgivelse: generatoren fyller inn alle fire felt, og validatoren godtar C >= A+B med C+D lik fillengden
Den metodiske løsningen er kryss-validering mot en implementasjon du ikke har skrevet selv. En generator og en validator som deler en kodebase, en forfatter eller til og med bare en mental modell av formatet, kan være enige om en delt misforståelse på ubestemt tid; en uavhengig validator bryter symmetrien. Kjør dine signerte utdata gjennom minst én ekstern samsvarskontroll før en utgivelse, og behold en strukturell egensjekk i bygget som en rask førstelinje — TPdf.ValidatePades, beskrevet i artikkelen om signaturinspeksjon, rapporterer dekningsfeilen som et navngitt problem
var
R: TPadesValidationResult;
begin
Pdf.FileName := 'contract-signed.pdf';
Pdf.Active := True;
R := Pdf.ValidatePades;
if ppeiByteRangeNotCoveringFile in R.Issues then
Writeln('ByteRange does not cover the file');
if not R.IsCompliant then
Writeln('Structural issues present: do not ship this file');
end;
En sjekkliste for avvisning av signerte utdata
Når en plattform avviser din PAdES-signatur, sjekk de enkle strukturelle årsakene før du mistenker sertifikater eller tillitskjeder. Les tabellen /ByteRange og bekreft de tre likhetene: første felt null, det andre spennet starter på eller etter slutten av det første, det andre spennet slutter nøyaktig ved filslutt. Les /M-oppføringen og bekreft at det er en fullstendig §7.9.4-datostreng med D:-prefikset og en avviksmarkør. Bekreft at kildedokumentet ikke var kryptert da signaturen ble lagt til — og hvis stakken din signerte det likevel uten å klage, behandle den stillheten som en feil i stakken. Alle de tre sjekkene kjører på rå byte i løpet av millisekunder, og etter vår erfaring forklarer de avvisningen langt oftere enn noen kryptografisk årsak
Signerings-, inspeksjons- og valideringskallene som brukes her — SignPades, ValidatePades og PAdES-samsvarskontrollene med deres rettede ByteRange-aritmetikk, datoformatering og krypteringssjekk — leveres med PDFium Component for Delphi, C++Builder og Lazarus