Teknisk artikel

PDFium VCL fjern-PAdES-signering: HSM og cloud-nøgler

PDFiumPas opdeler PAdES-signering i to kald, så den private nøgle aldrig behøver at være i din proces. PreparePadesRemoteSignature skriver en inkrementel opdatering med en tom, fast bredde /Contents-pladsholder og returnerer en anmodningsrecord, der bærer SHA-256-dokumentdigestet, det præcise ByteRange og et fingeraftryk af den forberedte fil. CompletePadesRemoteSignature tager den frakoblede CMS, din signeringstjeneste returnerer, og lægger den ind i det reserverede felt

Mellem de to kald kan der gå minutter eller timer, processen kan genstarte, og arbejdet kan flytte til en anden maskine. Det gab er hele grunden til, at API'en er formet på denne måde

Hvorfor kan en fjernnøgle ikke bruge det almindelige signeringskald?

Fordi SignPadesBytes antager, at signeringsoperationen sker inde i kaldet. Den bygger den inkrementelle opdatering, beregner digestet over ByteRange, signerer det og skriver resultatet, alt sammen før den returnerer. Det er præcis rigtigt, når nøglen bor i Windows-certifikatlageret eller i en PKCS#12-fil, du har indlæst

Det er umuligt, når nøglen bor i en netværks-HSM, en kvalificeret signaturoprettelsesenhed, drevet af en trust service provider, eller en cloud-signeringsAPI, der kræver, at brugeren bekræfter på en telefon. I de tilfælde er sekvensen ikke et funktionskald, det er en samtale: du sender et digest, noget andet autentificerer et menneske, og en CMS kommer tilbage senere. En synkron API kan ikke udtrykke "senere" uden at blokere en tråd på en operation, der måske kræver en anden faktor

Den to-fasede protokol

Fase et forbereder dokumentet. PDFiumPas tilføjer signaturfeltet og værdi-ordbogen, reserverer ContentsSize byte af hex-kodet plads i /Contents, beregner ByteRange omkring den reservation og producerer en TPadesRemoteSigningRequest, der indeholder FormatVersion, PreparedFingerprint, DocumentDigest, det fire-elements ByteRange, ContentsHexOffset og ContentsSize

Den eneste værdi, din signeringstjeneste har brug for, er DocumentDigest: SHA-256'en, den returnerede CAdES SignedData skal bære som sit besked-digest. Alt andet i recorden findes, så fase to kan bevise, at den fil, den fuldfører, er den fil, det digest blev beregnet ud fra

uses
  FPdfPades;

var
  Options: TPadesRemoteSignOptions;
  Request: TPadesRemoteSigningRequest;
  Source, Prepared, Session: TFileStream;
begin
  Options := TPadesRemoteSignOptions.Default;
  Options.Reason := 'Approved by finance';
  Options.Location := 'Lisbon';
  Options.Name := 'A. Moreira';
  Options.SigningTimeUtc := NowUtc;
  Options.ContentsSize := 16384;   // hex-byte reserveret til CMS'en

  Source := TFileStream.Create('contract.pdf', fmOpenRead or fmShareDenyWrite);
  Prepared := TFileStream.Create('contract.prepared.pdf', fmCreate);
  try
    PreparePadesRemoteSignature(Source, Prepared, Options, Request);
  finally
    Prepared.Free;
    Source.Free;
  end;

  // Gem sessionen, så en senere kørsel - eller en anden maskine - kan fuldføre den
  Session := TFileStream.Create('contract.signreq', fmCreate);
  try
    SavePadesRemoteSigningRequest(Session, Request);
  finally
    Session.Free;
  end;

  SendDigestToSigningService(Request.DocumentDigest);
end;

Hvad afviser Complete, og hvorfor findes hvert tjek?

Fuldførelse er der, hvor et fjernsigneringsdesign normalt går galt, så valideringen er bevidst ubarmhjertig. CompletePadesRemoteSignature afviser en forberedt PDF, hvis fingeraftryk ikke længere matcher anmodningen, et ByteRange, der ikke matcher de registrerede pladsholderkoordinater, ændrede /Contents-afgrænsere, en pladsholder, der ikke længere er tom, en CMS, der er større end reservationen, en CMS, der ikke er præcis én DER-værdi, en ikke-understøttet SignedData-form, et manglende signing-certificate-v2-attribut og en CMS, hvis besked-digest ikke er lig med det forberedte dokumentdigest

Hver af dem afspejler en reel fejl. Fingeraftryks- og ByteRange-tjekkene fanger tilfældet, hvor nogen har genereret den forberedte fil igen mellem faserne, hvilket ville producere en signatur, der validerer mod bytes, ingen har. Det tomme-pladsholder-tjek fanger dobbelt fuldførelse, hvor en anden CMS skrives oven på en signatur, der allerede findes. Besked-digest-tjekket fanger det farligste tilfælde af alle: en korrekt formet CMS, signeret over et andet dokument, hvilket er, hvad du får, når en kø blander to samtidige signeringssessioner sammen. Uden det ville du producere en fil, der ser signeret ud og fejler validering overalt, eller værre, som bærer en andens godkendelse

Kravet om signing-certificate-v2 er et PAdES-konformitetsanliggende snarere end et integritetsanliggende. ETSI EN 319 142 kræver, at signeringscertifikatet er bundet ind i de signerede attributter, og en CMS uden det attribut er ikke en PAdES-signatur, selv om den verificerer kryptografisk. At afvise det ved fuldførelse betyder, at du finder ud af det her, ikke i en validatorrapport fra en kunde, et emne udforsket yderligere i hvorfor validatorer afviser PAdES-signaturer

var
  Request: TPadesRemoteSigningRequest;
  Session, Prepared, Dest: TFileStream;
  CmsDer: TBytes;
begin
  Session := TFileStream.Create('contract.signreq', fmOpenRead);
  try
    Request := LoadPadesRemoteSigningRequest(Session);
  finally
    Session.Free;
  end;

  CmsDer := FetchDetachedCmsFromService;   // returneret af HSM'en eller TSP'en

  Prepared := TFileStream.Create('contract.prepared.pdf', fmOpenRead);
  Dest := TFileStream.Create('contract.signed.pdf', fmCreate);
  try
    try
      CompletePadesRemoteSignature(Prepared, Dest, Request, CmsDer);
    except
      on E: EPadesCrypto do
        // Hver afvisning bærer en specifik årsag; log den verbatim
        FailSession(E.Message);
    end;
  finally
    Dest.Free;
    Prepared.Free;
  end;
end;

At krydse proces- og maskingrænser

SavePadesRemoteSigningRequest og LoadPadesRemoteSigningRequest serialiserer sessionen gennem et stabilt versioneret binært format, hvilket er det, der gør designet praktisk frem for blot korrekt. En webapplikation kan forberede et dokument i én anmodning, gemme den forberedte PDF og session-blobben, returnere et digest til browseren til en smartkort-signatur og fuldføre filen i en helt anden anmodningshandler

FormatVersion-feltet er det, der holder det sikkert på tværs af opgraderinger. En session, skrevet af en ældre build og indlæst af en nyere, genkendes eller afvises eksplicit, frem for at blive fejlfortolket som en anderledes formet record. Hvis din kø kan holde sessioner i dage, bør du behandle formatversionen som en driftsmæssig kendsgerning, det er værd at logge, ikke en implementeringsdetalje

Dimensionering af pladsholderen

ContentsSize er den ene parameter, du skal tænke over, fordi den er fastsat, før CMS'en eksisterer. Den tæller den hex-kodede reservation, så en 6 KB DER CMS kræver mindst 12 KB plads, og implementeringen loftfæster reservationen ved 64 MiB

Reservér for lidt, og fuldførelse fejler med en for-stor-CMS-fejl, efter din signeringstjeneste allerede har udført sit arbejde, hvilket på en målt kvalificeret signaturtjeneste betyder en spildt operation. Reservér for meget, og hvert signeret dokument bærer polstringen for evigt. Den fornuftige tilgang er at måle: signér ét dokument med din rigtige certifikatkæde, se på DER-længden, dobbelt den for hex, og læg derefter rigelig margin til for tidsstemplingstokenet, hvis du agter at opgradere til en T-niveau-signatur. Kæder med flere mellemled og et langt OCSP-svar vokser hurtigere, end folk forventer

Hvad kommer efter signaturen

En fuldført fjernsignatur er PAdES B-B. Langtidsvalidering kræver et tidsstempel og valideringsmaterialet, hvilket er en separat inkrementel opdatering, der tilføjer en DSS og dens per-signatur VRI-ordbøger, beskrevet i langtidssignaturer med RFC 3161-tidsstempler og DSS. Det trin er lokalt: det tilføjer certifikater, OCSP-svar og CRL'er, hvoraf ingen kræver den private nøgle

Før udsendelse, verificér det, du producerede, med den samme kodesti, en relying party ville bruge, dækket i inspektion af digitale signaturer og PAdES-niveauer. Signering og verifikation er forskellig kode, og en fjernsigneringspipeline er præcis det sted, hvor de to kan drive fra hinanden, uden at nogen bemærker det, før en ekstern validator siger det

PDFiumPas er en Delphi- og Lazarus-komponent omkring PDFium-motoren med en native Pascal PAdES-stak, så signering, tidsstempling og validering fungerer uden eksterne kommandolinjeværktøjer. Fuld API-dokumentation og en trial-build findes på PDFium's produktside for Delphi-komponenten