Å validere én PAdES-signatur betyr å kontrollere tre uavhengige ting, og et grønt hakemerke i en leser sier bare noe om den tredje. For det første må /ByteRange-arrayet dekke de riktige bytene: spennene det navngir, må gjenskape nøyaktig det inndatasettet CMS-digesten ble tatt over, uten signerte byte igjen utenfor. For det andre må sertifikatet inne i CMS-en kjede seg til en rot du stoler på, og bære det signerte signeringssertifikatattributtet PAdES krever. For det tredje, dersom profilen hevder å ha et tidsstempel, må et RFC 3161-token binde signaturverdien til et tidspunkt før sertifikatet utløp. Acrobat presser alle tre ned i ett ikon; en samsvarskontrollør holder dem fra hverandre, og det bør koden som produserer disse filene, også gjøre. losLab PDF Library (PDF Library for Delphi) gir deg signeringssiden av dette, gjeninnbyggingen av tidsstempelet og granskingskallene for å inspisere et ByteRange før du stoler på det
Ett skille feller nesten enhver første PAdES-implementasjon, så det er verdt å slå fast før noen kode. En signatur skrevet med /SubFilter /adbe.pkcs7.detached er en fullt ut solid ISO 32000-1 §12.8-signatur som Acrobat vil melde som gyldig. Den er også ingen PAdES-signatur, fordi ETSI EN 319 142-1 krever ETSI.CAdES.detached på hvert baseline-nivå. En eIDAS-samsvarskontrollør avviser den første og godtar den andre selv om kryptografien er identisk. Profilen er en påstand dokumentet gjør om seg selv, og å få den påstanden riktig er ett kall i PDF Library for Delphi
Hva som gjør en PDF-signatur til en PAdES-signatur
ETSI EN 319 142-1 definerer fire baseline-nivåer stablet oppå CMS-formatet. PAdES-B-B er inngangen: en CAdES-signatur i et PDF-signaturfelt med SubFilter-en ETSI.CAdES.detached og et signert signeringssertifikatattributt. PAdES-B-T legger til et RFC 3161-tidsstempel over signaturverdien, som beviser at signaturen fantes før et tidspunkt ingen kan tilbakedatere. PAdES-B-LT bygger sertifikatene, CRL-ene og OCSP-svarene som trengs for validering, inn i et Document Security Store, slik at filen forblir verifiserbar etter at den utstedende CA-en har lagt ned infrastrukturen sin. PAdES-B-LTA kroner stabelen med et dokumenttidsstempel som beskytter det oppsamlede bevismaterialet på nytt etter hvert som algoritmer svekkes
PDF Library for Delphi avbilder disse begrepene på sitt sign-process-API. Profilmarkøren er SetSignProcessCustomSubFilter. Krever policyen din en angivelse av forpliktelsestype (bevis for opprinnelse, bevis for godkjenning eller en av de andre ETSI-identifikatorene nummerert 1 til 6), går det gjennom SetSignProcessCommitmentType. En eksplisitt signaturpolicy festes med SetSignProcessSignaturePolicy, som tar policyens OID og dens digest. Ett standardvalg fortjener oppmerksomhet: står digestalgoritmen på auto, velger biblioteket SHA-256 for ETSI- og adbe.pkcs7.detached-signaturer, og faller bare tilbake til SHA-1 på den eldre adbe.pkcs7.sha1-veien. Sett den eksplisitt likevel. Revisorer spør hvilken hash du brukte, og en eksplisitt verdi i koden er lettere å forsvare enn et standardvalg du må slå opp i håndboken for å forklare
Å produsere baseline-signaturen
Det flate API-et driver signering som en tilstandsmaskin i én omgang: åpne en prosess på kildefilen, konfigurer den, avslutt til en utdatafil, les resultatkoden. Sekvensen nedenfor produserer en PAdES-B-B-signatur med SHA-256. Linjen som betyr mest, har ingenting med selve signaturen å gjøre. Det er den bevisst overdimensjonerte /Contents-reservasjonen, for det er den ene tingen du ikke kan endre senere hvis et tidsstempel noen gang må legges til denne signaturen
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); // plass til et tidsstempel senere
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 returnerer 0 når kilden ikke lar seg åpne i det hele tatt. Deretter skiller GetSignProcessResult mellom de feilmodusene som faktisk opptrer i produksjon: 4 betyr feil PDF-passord, 7 feil PFX-passord, 9 en sertifikatfil uten privat nøkkel, 10 en utdatasti det ikke går an å skrive til, 11 en feil mens signaturbytene ble påført. Å logge den numeriske koden ved siden av navnet på inndatafilen gjør en vag støttesak om til en diagnose på ett minutt
Å legge til RFC 3161-tidsstempelet biblioteket ikke henter for deg
PDF Library for Delphi leverer ingen TSA-klient, og det er en bevisst grense snarere enn et hull. Biblioteket beregner hashen tidsstempelmyndigheten må medsignere, og bygger den utvidede CMS-en inn igjen etterpå; HTTP-utvekslingen og CMS-kirurgien i mellom hører kalleren til. Det finnes en hard teknisk grunn til delingen. Windows CryptoAPI-kontrollen som nominelt legger til usignerte attributter, CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR, feiler med CRYPT_E_INVALID_INDEX på det frakoblede SignedData-oppsettet PAdES bruker. Derfor må den forbedrede CMS-en komme fra en CMS-koder du selv styrer. Intet bibliotek kan stilltiende brette tokenet inn med ett systemkall, og ethvert som hevder det, gjør kirurgien et sted du ikke ser den
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);
// begge kallene nedenfor er applikasjonskode: en HTTP POST til din TSA,
// og en ny CMS-koding som fester tokenet som et usignert attributt
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;
Følg med på resultatkodene her: 12 betyr at det navngitte signaturfeltet ikke finnes, 11 at den eksisterende CMS-en ikke lot seg parse, og 13 at den utvidede CMS-en ikke lenger får plass i den reserverte /Contents-plassholderen. Kode 13 er den som svir, for den eneste løsningen er å signere på nytt: et typisk tidsstempeltoken med sertifikatkjeden sin ligger på 4 til 6 KB, og reservasjonen på 8192 byte gjort under B-B-trinnet finnes nettopp for at dette trinnet skal ha plass å lande på
Validering starter ved ByteRange, ikke ved sertifikatkjeden
Et grønt hakemerke i en leser er en tillitsbeslutning mot den maskinens sertifikatlager, ikke en strukturell dom over filen. Programmatisk validering bør begynne lenger nede, med spørsmålet inkrementelle oppdateringer gjør subtilt: hvilke byte dekker hver signatur egentlig? Hver utvidelse som drøftes her, enten det er en andre signatur, en DSS-ordbok eller et dokumenttidsstempel, kommer via inkrementell oppdatering, og hver oppdatering legger byte til utenfor den tidligere signaturens /ByteRange. De tilføyde bytene er legitime. En validator må likevel klassifisere dem mot dokumentets endringspolicy, og DocMDP-nivået per felt som den policyen bor i, kan leses med GetSignatureDocMDPLevelByName
var
Doc: TPDFlibSignDoc;
Names: TStringList;
I: Integer;
B0, B1, B2, B3, FileSize: Int64;
begin
FileSize := TFile.GetSize('invoice-bt.pdf'); // før Open: SignDoc holder en dellås
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 betyr faktisk signert
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;
To feller bor i denne granskingsveien. TPDFlibSignDoc.Open holder filen med en eksklusiv dellås, så en validator som også vil hashe de rå filbytene for CMS-verifisering, må lese filen inn i minnet før den åpnes for gransking. Snu den rekkefølgen, og lesingen feiler på en lås du selv satte. Den andre fellen er stille snarere enn høylytt: motstykket i det flate API-et, GetSignProcessByteRange, returnerer Integer mens de underliggende offsetene er Int64, så forbi 2 GB avkorter det flate kallet uten å klage, og det er derfor dette eksempelet henter offsetene gjennom granskingsklassen i stedet. Ett fravær er verdt å navngi også. Det flate laget har ingen VerifySignature-innpakning i det hele tatt. Kryptografiske dommer kommer fra TPDFlibSignatureVerifier på klassenivå, som returnerer vsValid, vsInvalid eller vsUnknown, eller fra en ekstern validator samsvarspolicyen din allerede stoler på
Langtidsvalidering: DSS, VRI og dokumenttidsstempelet
PAdES-B-LT finnes fordi infrastruktur for tilbakekalling er dødelig. ETSI EN 319 142-1 §5.4.2.2 spesifiserer Document Security Store: en ordbok på dokumentnivå som bærer sertifikater, CRL-er og OCSP-svar, valgfritt indeksert per signatur gjennom VRI-oppføringer nøklet på hashen av hver signaturs /Contents. Flyten i PDF Library for Delphi speiler tidsstempeldesignet. NewPAdESDSSProcessFromFile åpner prosessen; AddPAdESDSSCertificate, AddPAdESDSSCRL og AddPAdESDSSOCSP tar imot DER-blober; AddPAdESDSSVRI binder utvalgt materiale til én signatur; EndPAdESDSSProcessToFile skriver alt som en inkrementell oppdatering. Den vanskelige delen blir liggende hos deg. Å hente tilbakekallingsmaterialet, og å vurdere om det er ferskt nok til å være verdt å bygge inn, er kallerens jobb. Biblioteket garanterer at ordbøkene er strukturelt konforme; det kan ikke garantere at OCSP-svareren din snakket sant
Arkivendepunktet, B-LTA, legger til et dokumenttidsstempel: et eget signaturfelt hvis type er DocTimeStamp snarere enn Sig, produsert gjennom SetSignProcessDocTimeStamp med en reservert signaturlengde. Det erstatter ikke signaturtidsstempelet fra B-T-trinnet. Signaturtidsstempelet beviser når én bestemt signatur fantes; dokumenttidsstempelet beskytter hele filen, DSS-bevisene inkludert, og er elementet et langtidsarkiv fornyer med noen års mellomrom etter hvert som algoritmer svekkes. En moden arkivprofil bærer begge. For lesere som er eldre enn disse strukturene, noterer TPDFlibSignDoc.EnsurePAdESExtensions ESIC-utviklerutvidelsen i dokumentkatalogen, og kunngjør dermed at filen bruker ETSI-definerte funksjoner
Én reaksjon på alt dette er verdt å ta forskudd på, fordi den ser ut som en feil og ikke er det. En leser melder ofte «gyldighet ukjent» på en fil hvis PAdES-struktur er helt korrekt. Tillit og struktur er uavhengige akser. Leseren klarer rett og slett ikke å kjede signereren til en rot den stoler på, på den maskinen, noe som er rutine med private CA-er og testsertifikater, selv om både ByteRange-granskingen og CMS-verifiseringen går gjennom. Løsningen er å distribuere rotsertifikatet ordentlig, eller å vurdere mot EUs tillitslister når kvalifisert eIDAS-status er det egentlige målet, framfor å røre signeringskoden
For perspektivet fra granskingssiden, altså å telle opp signaturfelter på tvers av et korpus, dumpe ByteRange-oppsett og lese DocMDP-nivåer i bulk, se følgeartikkelen om arbeidsbenken for samsvar og signering. Signerte dokumenter som også må oppfylle arkivpolicy, hører hjemme i arbeidsflyten beskrevet i PDF/A- og PDF/UA-preflight i Delphi. Fullstendig API-dokumentasjon og evalueringsnedlastinger finnes på produktsiden for losLab PDF Library for Delphi