PDFium-komponenten signerer en PDF med en digital PAdES B-B-signatur gjennom metoden SignPades: den laster dokumentet, hasher det signerte byteområdet, bygger en CAdES CMS struktur og legger til signaturen som en trinnvis oppdatering. Den kryptografiske motoren støttes kun på Windows, så beskytt ethvert kall med PadesCryptoAvailable før du signerer
Situasjonen er velkjent. En kontrakts-PDF lander på skrivebordet ditt, juridisk avdeling vil ha den digitalt signert før den sendes ut, og du strekker deg etter den samme PDFium-versjonen du allerede bruker til å rendre og inspisere dokumenter, bare for å oppdage at PDFium ikke kan skrive en signatur i det hele tatt. Dens signatur-API er strengt skrivebeskyttet. PDFium-komponenten tetter dette gapet ved å eie hele signeringsrørledningen i Pascal, fra hash-funksjonen helt opp til injeksjon på bytenivå, og denne artikkelen går gjennom denne rørledningen fra start til slutt
Hvorfor kan ikke PDFium skrive en digital signatur?
PDFium eksponerer signaturer som skrivebeskyttede objekter og tilbyr ingenting for å opprette en. FPDFSignatureObj_*-familien lar deg liste opp en eksisterende signatur, lese dens /Contents og inspisere dens /ByteRange, men det finnes ingen motpart som bygger en signaturordbok, reserverer et felt for /Contents, eller skriver et byteområde; trinnvis lagring eksisterer (FPDF_SaveAsCopy med FPDF_INCREMENTAL), men har ingen signeringskobling. Enhver komponent som signerer en PDF på toppen av PDFium må derfor generere hver signaturbyte selv, og det er grunnen til at PDFium-komponenten bygger systemet fra tre rene Pascal-enheter. FPC 3.2.2 leveres med md5 og sha1, men overhode ingen SHA-2, og Delphis System.Hash SHA-256 API er ikke kildekode-kompatibelt med FPC, så FPdfSha256 is en frittstående FIPS 180-4-implementering som holder alle CMS-kodinger på én enkelt TSHA256Digest-type uten kompilatorforgreninger. FPdfAsn1 leverer DER-koderen og -leseren som CMS-strukturene trenger, og FPdfCms setter sammen CAdES SignedData på toppen av begge
Hvordan signerer du en PDF digitalt i Delphi?
Last inn dokumentet, og kall deretter SignPades med et sertifikatavtrykk (thumbprint). PDFium-komponenten slår opp dette avtrykket mot sertifikatlageret til gjeldende bruker ("MY"), henter det samsvarende sertifikatet og dets private nøkkel, og skriver en signert kopi til banen du oppgir
uses
PDFium, FPdfCrypto;
procedure SignContract(const AThumbprint: string);
var
Pdf: TPdf;
begin
if not PadesCryptoAvailable then
raise Exception.Create('PAdES signing requires the Windows CNG backend');
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'contract.pdf'; // the document to sign
Pdf.Active := True;
// Second argument: SHA-1 thumbprint of a certificate in the Current
// User "MY" store. First argument: destination for the signed copy.
if not Pdf.SignPades('contract-signed.pdf', AThumbprint) then
raise Exception.Create('Signing failed');
finally
Pdf.Free;
end;
end;
Hva inngår i CMS-en: signerte attributter og RFC 5652
En PAdES baselinesignatur er ikke en rå RSA-signatur over filen; det er en CAdES CMS SignedData-struktur som bærer et påkrevd sett med signerte attributter, og FPdfCms.BuildSignedData sender ut nøyaktig dette settet: content-type, message-digest og signing-certificate-v2, som er ESS-attributtet som binder signaturen til signatarens sertifikat ved hjelp av en hash. Én detalj her ødelegger for nesten alle hjemmesnekrede CMS-implementeringer. RFC 5652 §5.4 krever at sammendraget for signerte attributter skal beregnes over DER SET OF-kodingen, tag 0x31, merknad: de samme attributtene ligger inne i SignerInfo under IMPLICIT [0]-taggen, 0xA0. PDFium-komponenten koder attributtsettet én gang, gjør et sammendrag av 0x31-formen, og skriver deretter om bare den første tag-byten til 0xA0 før utsending, slik at én buffer tjener begge roller uten en ny runde over treet
var
Pdf: TPdf;
Opts: TPadesSignOptions;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'contract.pdf';
Pdf.Active := True;
Opts := TPadesSignOptions.Default;
Opts.CertificateThumbprint := 'a1b2c3d4e5f6...'; // signer in the MY store
Opts.Reason := 'I approve this agreement';
Opts.Location := 'Berlin, DE';
Opts.ContentsSize := 16384; // hex width of /Contents
if not Pdf.SignPades('contract-signed.pdf', Opts) then
raise Exception.Create('Signing failed');
finally
Pdf.Free;
end;
end;
Overbelastningen (overload) av alternativer legger til metadataene for signaturordboken som ISO 32000-1 §12.8.1 definerer: Reason, Location, ContactInfo og Name, som alle er valgfrie og skrives inn i signaturverdiordboken. Én begrensning er det lett å snuble i. Hvis du angir CommitmentTypeOid for å legge til et CAdES commitment-type-indication signert attributt, må du ikke også angi Reason; ETSI EN 319 142-1 §6.3 forbyr å bruke begge deler, fordi de to uttrykker samme hensikt på forskjellige måter
Hvordan passer ByteRange- og /Contents-feltet sammen?
En signatur må dekke hele filen unntatt bytene som inneholder selve signaturen, og PAdES løser denne sirkulariteten med en plassholder med fast bredde som SignPadesBytes administrerer presist. Den reserverer en /Contents heksadesimalstreng på ContentsSize byte (standard 16384, komfortabelt større enn en typisk CMS SignedData), serialiserer den trinnvise oppdateringen for å finne den nøyaktige forskyvningen for feltet, og beregner deretter /ByteRange som to områder som rammer inn feltet, alt før åpningsskilletegnet for heksadesimalstrengen og alt etter dets avsluttende skilletegn. SHA-256 kjøres kun over disse to områdene. Den ferdige CMS-en er heksadesimalt kodet inn i det reserverte feltet, nullfylt til den faste bredden, og oppdateringen for kryssreferansen legges til. Fordi bredden er fastsatt på forhånd, vil utfylling av feltet ikke forskyve noen etterfølgende byte, som er hele grunnen til at byteområdet forblir gyldig; de opprinnelige dokumentbytene bevares ordrett, slik at en tidligere signatur på samme fil overlever intakt, akkurat som trinnvis signering i ISO 32000-1 §12.8.1 krever
Windows CNG-kjøretidsmotoren og dens grenser
PDFium-komponenten signerer bare på Windows, og denne grensen er bevisst. FPdfCryptoWin binder crypt32.dll og ncrypt.dll dynamisk, noe som ikke medfører noen DLL-avhengighet ved kompilering, og signeringskjeden er standard CNG: åpne MY-lageret, finn sertifikatet etter hash, hent dets private nøkkelhåndtak gjennom CryptAcquireCertificatePrivateKey, og kall NCryptSignHash. RSA med PKCS#1 v1.5, RSA-PSS og ECDSA støttes. ECDSA trenger en korreksjon som de andre ikke gjør, fordi NCryptSignHash returnerer det rå IEEE P1363 r- og s-paret, mens CMS forventer en DER ECDSA-Sig-Value SEQUENCE, så motoren koder den på nytt i henhold til RFC 5480
var
Pdf: TPdf;
Opts: TPadesSignOptions;
Output: TFileStream;
begin
if not PadesCryptoAvailable then
Exit; // no signing backend on this platform
Opts := TPadesSignOptions.Default;
Opts.CertificateThumbprint := ReadThumbprintFromConfig;
Pdf := TPdf.Create(nil);
Output := TFileStream.Create('contract-signed.pdf', fmCreate);
try
Pdf.FileName := 'contract.pdf';
Pdf.Active := True;
Pdf.SignPadesToStream(Output, Opts);
finally
Output.Free;
Pdf.Free;
end;
end;
Den praktiske konsekvensen er at den private nøkkelen må befinne seg i Windows-sertifikatlageret. Et sertifikat i en PFX-fil fungerer bare etter at du har importert det til sertifikatlageret for gjeldende bruker, og på det tidspunktet er sertifikatavtrykket verdien du sender til SignPades. Denne utgivelsen har ingen PKCS#11- eller HSM-bane og ingen programvarenøkkel-filkjøretidsmotor, så når PadesCryptoAvailable returnerer False, er det ganske enkelt ikke mulig å signere på den maskinen
Der PAdES B-B stopper
PAdES B-B er baselinjen, minimumet av de fire PAdES-nivåene: den beviser hvem som signerte, og at bytene ikke har blitt endret siden, og ingenting utover det. En B-B-signatur bærer ikke med seg noe klarert tidsstempel, så den kan ikke bevise når signeringen skjedde, og den bygger ikke inn tilbakekallingsdata, så en kontrollør må år senere selv hente sertifikatkjeden og dens status. Disse gapene er akkurat det de høyere nivåene lukker. Når du trenger et signeringstidspunkt en revisor vil godta, vil tilføying av et RFC 3161-tidsstempel og DSS for langsiktig validering flytte signaturen til B-T og oppover; når du vil lese en ferdig signatur og bekrefte hvilket nivå den har nådd, er inspeksjon av en PDF-signatur og dens PAdES-nivå det tilhørende verktøyet; og før du signerer noe som helst, forteller revisjon av en PDF for sikkerhetsrisikoer deg hva du er i ferd med å sette navnet ditt på
Metodene for SignPades som vises her leveres med PDFium Component for Delphi og C++Builder, sammen med den skrivebeskyttede signaturinspeksjonen PDFium leverer som standard