Техническая статья

PDFlibPas: PAdES signing and validation в Delphi

losLab PDF Library предоставляет командам Delphi и C++Builder PDF-движок с доступным исходным кодом для настольных, серверных, DLL, ActiveX и Dylib процессов, включая встроенные проверки PDF/A и PDF/UA, подписи PAdES и выбор рендерера без отправки документов во внешний PDF-сервис.

Эта статья предназначена для teams that need to create, inspect, and validate signed PDF workflows inside Delphi applications. Она рассматривает PAdES signing and validation как промышленную инженерию документов, а не как одиночный вызов компонента.

Практический риск состоит в том, что signature creation and signature validation are often implemented separately, causing mismatched trust decisions when timestamps, revocation data, or incremental updates change. Поэтому процессу нужны письменный контракт, наблюдаемая диагностика и реалистичные регрессионные файлы.

Архитектурные решения

Use one trust policy for signing and validation. accepted certificate stores, chain policy, timestamp source, and revocation source / PAdES profile, long-term validation requirements, and archive retention period

  • accepted certificate stores, chain policy, timestamp source, and revocation source
  • PAdES profile, long-term validation requirements, and archive retention period
  • whether warnings create a block, manual review, or documented waiver
  • how later document changes are restricted after the trusted revision

Порядок реализации

Validate the final signed revision, not the draft. The order below keeps the workflow reviewable for Delphi and C++Builder teams.

  1. prepare the document and collect validation prerequisites before signing
  2. apply the signature, timestamp, and revocation evidence according to policy
  3. validate the final signed file and classify every warning
  4. store trust evidence with the business record rather than only inside the PDF
  5. revalidate representative files when trust anchors or policy change

Доказательства проверки

Trust evidence for signed documents. Keep these fields with the output or support record.

  • signature status, byte range, digest algorithm, signer certificate, and chain result
  • timestamp token status, revocation source, DSS/VRI presence, and validation time
  • policy version, warning classification, and waiver decision
  • final signed file hash and validator result

Long-term validation needs supporting data

PAdES workflows need certificate-chain checks, timestamps, revocation data, DSS/VRI information, byte-range validation, and policy decisions for warnings. The final file must be validated after all signing bytes are written.

Customer-visible behavior

Users do not see internal call order. They see whether the file opens, validates, prints, edits, imports, or gets rejected. The workflow should translate PAdES signing and validation results into states users can act on.

  • prepare the document and collect validation prerequisites before signing
  • apply the signature, timestamp, and revocation evidence according to policy
  • validate the final signed file and classify every warning
  • a signature can be cryptographically intact but untrusted by current policy
  • revocation services may be unavailable when the document is signed

Замечания для инженерного ревью по PAdES signing and validation

Используйте эти замечания, чтобы убедиться, что функция вышла за рамки демо и может быть обоснована на релизе, в поддержке и при эскалации клиента

  • Решение: accepted certificate stores, chain policy, timestamp source, and revocation source. Точка приложения при реализации: apply the signature, timestamp, and revocation evidence according to policy. Доказательство приемки: policy version, warning classification, and waiver decision. Триггер регрессии: clock differences can make timestamp and certificate validity hard to explain
  • Решение: PAdES profile, long-term validation requirements, and archive retention period. Точка приложения при реализации: validate the final signed file and classify every warning. Доказательство приемки: final signed file hash and validator result. Триггер регрессии: a signature can be cryptographically intact but untrusted by current policy
  • Решение: whether warnings create a block, manual review, or documented waiver. Точка приложения при реализации: store trust evidence with the business record rather than only inside the PDF. Доказательство приемки: signature status, byte range, digest algorithm, signer certificate, and chain result. Триггер регрессии: revocation services may be unavailable when the document is signed

Пограничные случаи

  • a signature can be cryptographically intact but untrusted by current policy
  • revocation services may be unavailable when the document is signed
  • incremental updates after signing need a clear allowed-change policy
  • clock differences can make timestamp and certificate validity hard to explain

Примечания по Delphi / C++Builder

PDFlibPas should sit behind a small service boundary that receives files, streams, profiles, and credentials, then returns output paths, warnings, metrics, and validation status. Важные термины включают PAdES, signature validation, timestamp, revocation, DSS, byte range.

Пример кода Delphi

Следующий эскиз Delphi показывает практическую границу сервиса для этой темы. Оставляйте проверки политики, журналирование и валидацию вне узкого блока вызова продукта, чтобы сценарий было проще тестировать.

procedure ValidatePadesPackage(const InputFile: string; const TrustPolicy: TTrustPolicy);
var
  Pdf: TPDFlib;
  ProcessId: Integer;
begin
  Pdf := TPDFlib.Create;
  try
    ProcessId := Pdf.NewSignProcessFromFile(InputFile, '');
    CheckByteRange(Pdf, ProcessId);
    ValidateCertificatePath(Pdf, ProcessId, TrustPolicy);
    Pdf.ReleaseSignProcess(ProcessId);
  finally
    Pdf.Free;
  end;
end;

Производственный чек-лист

  • Запускайте сценарий на пустом файле, обычном клиентском файле и файле худшего случая
  • Открывайте сгенерированный PDF в целевом просмотрщике, валидаторе, принтере или downstream-приложении
  • Записывайте версию продукта, версию профиля, хэш входа, путь вывода, затраченное время и число предупреждений
  • Храните пароли, сертификаты, временные файлы и данные клиентов по явным правилам хранения
  • Добавляйте регрессионные документы, когда клиентский файл выявляет новый граничный случай

Документация по продукту

PDFlibPas

Дополнительные примеры кода

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;