Teknisk artikel

PAdES digitale signaturer i Delphi: Signering og validering med PDFlibPas

Denne lokaliserede version går direkte ind i PAdES Digital Signatures in Delphi: Signing and Validation with PDFlibPas og bruger den opdaterede engelske artikel som teknisk reference for Delphi-, PDF- og dokumentsoftwareteams

Siden omsætter den opdaterede basisartikel til konkrete kontrolpunkter for planlægning, implementering og validering

Hvad der blev synkroniseret fra den engelske artikel

Den engelske basisartikel er blevet udvidet med mere praktisk kontekst, konkrete beslutningspunkter og eksempler, så denne side skal læses som en arbejdsguide frem for en kort oversigt

Vigtige afsnit i den opdaterede basisartikel:

  • Brug små reproducerbare inputfiler, før funktionen kobles til produktionsdata
  • Hold produktnavne, API-navne, filnavne og literalværdier uændrede
  • Gem validatoroutput og versionsoplysninger sammen med den genererede prøvefil

Praktiske valg i implementationen

Start med den konkrete filtype, det ønskede output og den fejltilstand brugeren skal se. Bind derefter hvert API-kald til et kontrollerbart resultat, så validering, logning og support kan gentage kundens scenarie

  • Brug små reproducerbare inputfiler, før funktionen kobles til produktionsdata
  • Hold produktnavne, API-navne, filnavne og literalværdier uændrede
  • Gem validatoroutput og versionsoplysninger sammen med den genererede prøvefil

Kode og API-punkter

Kodeeksemplerne er bevaret uændret, fordi udviklere skal kunne sammenligne dem direkte med Delphi-, C++Builder- og Lazarus/FPC-projekter

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);  // room for a timestamp later
    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;
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);
    // both calls below are application code: an HTTP POST to your TSA,
    // and a CMS re-encode that attaches the token as an unsigned attribute
    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;
var
  Doc: TPDFlibSignDoc;
  Names: TStringList;
  I: Integer;
  B0, B1, B2, B3, FileSize: Int64;
begin
  FileSize := TFile.GetSize('invoice-bt.pdf');  // before Open: SignDoc holds a share lock
  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 means actually signed
        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;

Kontrol før udgivelse

Gennemgå outputfilen med samme værktøjer, som kunden eller arkivet bruger. Notér komponentversion, testdata, validatorversion og observeret resultat, så en senere regression kan spores præcist

Supplerende teknisk gennemgang

Denne udvidede sektion knytter an til artiklen PAdES Digital Signatures in Delphi with PDFlibPas på dansk og uddyber den samme arbejdsgang fra et teams perspektiv, hvor beslutninger i generatoren, i valideringen og i den efterfølgende logning skal kunne spores senere. Den engelske kilde på den tilhørende side via hreflang viser, hvorfor det ikke er nok blot at oversætte overskrifterne; pointen er at forklare, hvorfor dokumentet først er færdigt, når regler, output og kontrolspor faktisk hænger sammen

I implementeringsartikler er det nyttigt at skille design fra verifikation. Først fastlægges filtypen, det forventede resultat og den fejltilstand, brugeren skal se, og derefter bindes hvert API-kald til et resultat, der kan afprøves i samme scenarie igen. Det gælder både PDF-arbejdsgange og regnearksfunktioner: kodeeksemplerne forbliver uændrede, men brødteksten skal forklare, hvorfor komponentversion, skabelon-id, inputdata og valideringsstatus bør logges sammen

Det er også vigtigt at bevare produktnavne, API-navne, filnavne og literalværdier præcist som i den engelske kilde. Det holder den fælles reference ramme for udvikling, support og kvalitetssikring og mindsker risikoen for, at den lokale version bare bliver en løs parafrase uden konkret teknisk substans. Hvis artiklen indeholder kode, skal kommentarerne og tokenerne stå urørte, fordi det netop er dem, der forbinder teksten med det virkelige projekt

Når siden læses efter publicering, hjælper det at tænke på den som en del af en sporbar kæde. En god valideringsnote beskriver, hvad der blev testet, hvilket værktøj der vurderede resultatet, hvilke versioner der var involveret, og hvor beviset for et match eller et afslag er gemt. Når en regression opstår senere, er det arkiverede rapportmateriale og den tilhørende inputfil langt mere værd end mindet om, at "det gik dengang"

For denne lokaliserede gren gælder derfor en enkel regel: behold de centrale beslutninger, verifikationspunkterne og kodekonteksten samlet, så artiklen ikke kun kan læses første gang, men også bruges ved senere fejlsøgning, audit og versionssammenligning. Det er forskellen mellem et kort sammendrag og et arbejdsdokument, der stadig har værdi efter flere udgivelser

  • Brug først små reproducerbare inputfiler
  • Behold produktnavne, API-navne, filnavne og literalværdier uændrede
  • Gem komponentversion, validatorresultat og inputdata sammen
  • Bevar kodeblokke og kommentarer præcist som i kilden

Relateret læsning