Teknisk artikel

PDFium VCL: fjärrsignering med PAdES via HSM och moln

PDFiumPas delar upp PAdES-signering i två anrop så att den privata nyckeln aldrig behöver finnas i din process. PreparePadesRemoteSignature skriver en inkrementell uppdatering med en tom, fastbredds-/Contents-platshållare och lämnar tillbaka en begärandepost som bär SHA-256-dokumentdigesten, det exakta ByteRange och ett fingeravtryck av den förberedda filen. CompletePadesRemoteSignature tar den frikopplade CMS din signeringstjänst returnerar och lägger in den i den reserverade platsen

Mellan de två anropen kan minuter eller timmar gå, processen kan startas om, och arbetet kan flytta till en annan maskin. Det gapet är hela anledningen till att API:et är utformat på det här sättet

Varför kan inte en fjärrnyckel använda det vanliga signeringsanropet?

Därför att SignPadesBytes förutsätter att signeringsoperationen sker inuti anropet. Den bygger den inkrementella uppdateringen, beräknar digesten över ByteRange, signerar den, och skriver resultatet, allt innan den returnerar. Det är exakt rätt när nyckeln lever i Windows certifikatarkiv eller i en PKCS#12-fil du läste in

Det är omöjligt när nyckeln lever i en nätverks-HSM, en kvalificerad signaturskapande enhet driven av en betrodd tjänsteleverantör, eller ett moln-signerings-API som kräver att användaren bekräftar på en telefon. I de fallen är sekvensen inget funktionsanrop, det är en konversation: du skickar en digest, något annat autentiserar en människa, och en CMS kommer tillbaka senare. Ett synkront API kan inte uttrycka ”senare” utan att blockera en tråd på en operation som kan behöva en andra faktor

Tvåfasprotokollet

Fas ett förbereder dokumentet. PDFiumPas lägger till signaturfältet och värdeordboken, reserverar ContentsSize byte hex-kodat utrymme i /Contents, beräknar ByteRange runt den reservationen, och producerar en TPadesRemoteSigningRequest som innehåller FormatVersion, PreparedFingerprint, DocumentDigest, det fyrelementiga ByteRange, ContentsHexOffset och ContentsSize

Det enda värdet din signeringstjänst behöver är DocumentDigest: SHA-256-digesten den returnerade CAdES SignedData måste bära som sin meddelandedigest. Allt annat i posten finns så att fas två kan bevisa att filen den slutför är den fil den digesten beräknades från

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 reserverade för 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;

  // Spara sessionen så en senare körning – eller en annan maskin – kan slutföra den
  Session := TFileStream.Create('contract.signreq', fmCreate);
  try
    SavePadesRemoteSigningRequest(Session, Request);
  finally
    Session.Free;
  end;

  SendDigestToSigningService(Request.DocumentDigest);
end;

Vad vägrar Complete, och varför finns varje kontroll?

Slutförandet är där en fjärrsigneringsdesign oftast går fel, så valideringen är medvetet oförlåtande. CompletePadesRemoteSignature avvisar en förberedd PDF vars fingeravtryck inte längre matchar begäran, ett ByteRange som inte matchar de registrerade platshållarkoordinaterna, modifierade /Contents-avgränsare, en platshållare som inte längre är tom, en CMS större än reservationen, en CMS som inte är exakt ett DER-värde, en ostödd SignedData-form, ett saknat signing-certificate-v2-attribut, och en CMS vars meddelandedigest inte är lika med den förberedda dokumentdigesten

Var och en av dem motsvarar ett verkligt fel. Kontrollerna av fingeravtryck och ByteRange fångar fallet där någon regenererade den förberedda filen mellan faserna, vilket skulle ge en signatur som validerar mot byte ingen har. Kontrollen av den tomma platshållaren fångar dubbelt slutförande, där en andra CMS skrivs över en signatur som redan finns. Kontrollen av meddelandedigesten fångar det farligaste fallet av alla: en korrekt formad CMS signerad över ett annat dokument, vilket är vad du får när en kö blandar ihop två samtidiga signeringssessioner. Utan den skulle du producera en fil som ser signerad ut och misslyckas i valideringen överallt, eller värre, som bär någon annans godkännande

Kravet på signing-certificate-v2 är en PAdES-konformitetsfråga snarare än en integritetsfråga. ETSI EN 319 142 kräver att signeringscertifikatet binds in i de signerade attributen, och en CMS som saknar det attributet är ingen PAdES-signatur även om den verifierar kryptografiskt. Att avvisa den vid slutförandet betyder att du får reda på det här, inte i en validatorrapport från en kund, ett ämne som utforskas vidare i varför validatorer avvisar 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;   // returnerad av HSM:en eller TSP:n

  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
        // Varje avvisning bär en specifik anledning; logga den ordagrant
        FailSession(E.Message);
    end;
  finally
    Dest.Free;
    Prepared.Free;
  end;
end;

Att korsa process- och maskingränser

SavePadesRemoteSigningRequest och LoadPadesRemoteSigningRequest serialiserar sessionen genom ett stabilt versionerat binärformat, vilket är det som gör designen praktisk snarare än bara korrekt. En webbapplikation kan förbereda ett dokument i en begäran, lagra den förberedda PDF:en och session-bloben, returnera en digest till webbläsaren för en smartkortssignatur, och slutföra filen i en helt annan begärandehanterare

Fältet FormatVersion är det som håller det säkert genom uppgraderingar. En session skriven av ett äldre bygge och inläst av ett nyare känns igen eller avvisas explicit, i stället för att feltolkas som en annorlunda formad post. Om din kö kan hålla sessioner i dagar, behandla formatversionen som ett driftsfaktum värt att logga, inte en implementationsdetalj

Att dimensionera platshållaren

ContentsSize är den enda parameter du måste tänka på, eftersom den fastställs innan CMS:en existerar. Den räknar den hex-kodade reservationen, så en 6 KB DER-CMS behöver minst 12 KB utrymme, och implementationen begränsar reservationen till 64 MiB

Reservera för lite och slutförandet misslyckas med ett CMS-för-stor-fel efter att din signeringstjänst redan har gjort sitt arbete, vilket på en mätt kvalificerad signeringstjänst betyder en bortkastad operation. Reservera för mycket och varje signerat dokument bär utfyllnaden för alltid. Det förnuftiga tillvägagångssättet är att mäta: signera ett dokument med din riktiga certifikatkedja, titta på DER-längden, dubbla den för hex, lägg sedan till generöst utrymme för tidsstämpelntokenet om du tänker uppgradera till en T-nivå-signatur. Kedjor med flera mellanled och ett långt OCSP-svar växer snabbare än folk förväntar sig

Vad som kommer efter signaturen

En slutförd fjärrsignatur är PAdES B-B. Långsiktig validering behöver en tidsstämpel och valideringsmaterialet, vilket är en separat inkrementell uppdatering som lägger till en DSS och dess per-signatur-VRI-ordböcker, beskrivet i långsiktiga signaturer med RFC 3161-tidsstämplar och DSS. Det steget är lokalt: det lägger till certifikat, OCSP-svar och CRL:er, av vilka inget behöver den privata nyckeln

Innan leverans, verifiera det du producerade med samma kodväg en förlitande part skulle använda, täckt i att inspektera digitala signaturer och PAdES-nivåer. Signering och verifiering är olika kod, och en fjärrsigneringspipeline är precis den plats där de två kan glida isär utan att någon märker det förrän en extern validator säger till

PDFiumPas är en Delphi- och Lazarus-komponent runt PDFium-motorn med en nativ Pascal-PAdES-stack, så signering, tidsstämpling och validering fungerar utan externa kommandoradsverktyg. Fullständig API-dokumentation och en testversion finns på sidan för PDFium Delphi-komponent