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

Пост-квантово и EdDSA подписване на PDF с HotPDF в Delphi

HotPDF верифицира ML-DSA-44, ML-DSA-65, ML-DSA-87, Ed25519 и Ed448 CMS подписи в заредени PDF документи и подписва чрез включвани доставчици, така че частният ключ никога не трябва да живее вътре във вашия Delphi процес. Втората половина е частта, от която повечето екипи се нуждаят най-напред. Хардуерен токен, отдалечена услуга за подписване и национална eID карта всички отказват да предадат ключ, и докато пайплайнът за подписване не бъде отделен от хранилището на ключове, нито един от тях не може да се използва изобщо

Това разделяне е смисълът на THPDFSignatureProvider. HotPDF запазва частите, които трябва да притежава — синтактичен разбор на CMS, изграждане на SignedData, подредба на /ByteRange — и делегира еднаствената операция, която не може да притежава, а именно превръщането на дайджест в подпис с ключ, който няма право да види. Всичко по-долу следва от това разделение

Защо валиден ML-DSA подпис не минава проверка?

Защото HotPDF отказва ML-DSA върху зареден документ, който не декларира разширението за него. ML-DSA — решетъчната схема за подпис, стандартизирана като FIPS 204, и причината хората да казват «пост-квантов PDF» — все още няма регистрация в ISO 32000-2. PDF, който носи такъв, използва алгоритъм, който базовият стандарт не назовава, а файл, който мълчаливо използва не назован алгоритъм, е файл, чиято присъда не може да бъде възпроизведена от никой друг

Така че HotPDF прави претенцията изрична. EnsureMLDSAExtensions повишава документа до PDF 2.0, когато е разрешено, и записва /Extensions /HotPDF << /BaseVersion /2.0 /ExtensionLevel 1 >> в каталога. От страна на четенето LoadedDocumentDeclaresMLDSAExtension отчита дали тази декларация е оцеляла, а VerifyLoadedSignatureWithOptions прилага същия тест, преди да зачете Options.AllowMLDSA. Задайте флага върху недеклариран документ и той остава изключен — опцията може да разхлаби политика, но никога структурното изискване

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'contract-pq.pdf';
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 11);
    Pdf.CurrentPage.TextOut(50, 720, 0, 'Supply agreement 2026-114');
    Pdf.EnsureMLDSAExtensions;   // declare before the signature is written
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Извикайте го преди запазване, не след него. Декларацията е част от подписания байтов диапазон, а каталог, закърпен след това, е или неподписана промяна върху подписан файл, или втора ревизия, която валидатор ще отчита като модификация

Три семейства алгоритми, една точка за верификация

И трите семейства пристигат през VerifyLoadedSignatureWithOptions, който приема индекс на подпис, изходния поток, запис THPDFCMSVerifyOptions и out параметър за детайлите на подписа. Записът има точно три полета и всяко отговаря на въпрос, който по-рано изискваше прездравяване

SignatureProvider замества вашия собствен доставчик с вградения платформен. OpenSSLLibraryPath избира библиотека OpenSSL 3, която е това, което доставя верификация на Ed25519 и Ed448 в чист режим, каквото Windows CNG не предлага навсякъде. AllowMLDSA включва решетъчните алгоритми, при подчинение на горната проверка за разширение. Точният OID на разпознатия алгоритъм се връща в THPDFSignatureInfo.SignatureAlgorithmOID, така че одитен лог може да запише какво е било верифицирано, а не какво е било заявено

var
  Opts: THPDFCMSVerifyOptions;
  Info: THPDFSignatureInfo;
  Status: THPDFSignatureVerifyStatus;
  Src: TFileStream;
begin
  Opts := THPDFCMSVerifyOptions.Default;
  Opts.OpenSSLLibraryPath := 'C:\openssl3\libcrypto-3-x64.dll';
  Opts.AllowMLDSA := Pdf.LoadedDocumentDeclaresMLDSAExtension;
  Src := TFileStream.Create('contract-pq.pdf', fmOpenRead or fmShareDenyWrite);
  try
    Status := Pdf.VerifyLoadedSignatureWithOptions(0, Src, Opts, Info);
    if Status = svValid then
      Memo1.Lines.Add('signed with OID ' + string(Info.SignatureAlgorithmOID));
  finally
    Src.Free;
  end;
end;

Ed25519 и Ed448 не се нуждаят от декларация за разширение, защото ISO 32000-2 вече ги допуска. Нуждаят се обаче от доставчик, който ги реализира, което при повечето Windows разгръщания означава да насочите OpenSSLLibraryPath към библиотека, която вие доставяте и контролирате, вместо към каквото и да е на машината

Какво всъщност обещава доставчикът за подписване?

Доставчикът обещава едно нещо: при заявка връща статус и, при подписване, байтове. THPDFSignatureProviderRequest носи алгоритъма и неговия OID, OID на дайджеста, дължината на PSS солта, дали входът е съобщение или вече пресметнат дайджест, самия вход, публичния ключ или сертификат, идентификатор на ключ и идентификатор на операция. Нищо в този запис не е специфично за HotPDF — това е речникът, който драйверът на токен или услугата за подписване вече говори

Три реализации се доставят с библиотеката. THPDFCallbackSignatureProvider обвива анонимни методи, което е най-краткият път от съществуваща вътрешна рутина за подписване до работещ PDF подпис. THPDFRemoteSignatureProvider обвива транспортен callback с лимит за повторни опити, регистър за отменяне и граници за размер на входа и подписа, така че заклещен HSM не може да стане заклещено приложение. THPDFPKCS11SignatureProvider сериализира RSA операции срещу притежавана от извикващия, вече удостоверена PKCS#11 сесия и манипулатор на частен ключ — HotPDF никога не вписва, никога не вижда PIN и никога не затваря сесия, която не е отварял

var
  Provider: THPDFRemoteSignatureProvider;
begin
  Provider := THPDFRemoteSignatureProvider.Create(
    function(const Req: THPDFSignatureProviderRequest; Attempt: Integer;
      out Signature: TBytes): THPDFSignatureProviderStatus
    begin
      // POST Req.Input to the signing service; Req.KeyIdentifier selects the key
      if PostToSigningService(Req.KeyIdentifier, Req.Input, Signature) then
        Result := spsValid
      else
        Result := spsProviderError;
    end,
    3,          // RetryLimit
    1048576,    // MaxInputBytes
    65536);     // MaxSignatureBytes
  try
    // hand Provider to the signing call
  finally
    Provider.Free;
  end;
end;

Защо статусният enum има шест стойности вместо булев

THPDFSignatureProviderStatus разграничава spsValid, spsInvalid, spsUnsupported, spsMalformed, spsProviderError и spsCancelled, а свиването им в едно ви струва способността да действате правилно. Подпис, който е криптографски грешен (spsInvalid), е събитие за сигурност. Алгоритъм, който доставчикът не реализира (spsUnsupported), е пролука в разгръщането. Транспортен отказ (spsProviderError) си струва да се повтори, а подсказване за токен, отменено от потребителя (spsCancelled), не си струва да се повтаря изобщо

Правилото за подписване е тясно: доставчик за подписване връща spsValid само с непразен подпис. Доставчиците за верификация връщат spsValid или spsInvalid, а останалите четири остават разграничени и по двата пътя. Ако пишете доставчик, устояйте на изкушението да изобразявате всичко, което не разпознавате, като spsInvalid — това превръща липсваща DLL в доклад, че подписът на клиента е фалшифициран

Къде подписът реално каца във файла

Две функции свързват доставчиците с реални PDF байтове. HPDFCMSBuildSignedDataWithProvider изгражда отделен CMS от дайджест SHA-256 на документ, което е правилната входна точка, когато работният ви процес изчислява дайджеста другаде. HPDFCMSSignPDFStreamWithProvider подписва съществуващо место за подпис в PDF поток и запазва стандартния пайплайн /ByteRange, което е правилната входна точка, когато HotPDF сам е подредил местото

Запазването на този пайплайн има повече значение, отколкото звучи. Конвенцията /ByteRange — два диапазона, които прескачат hex прозореца на подписа — е това, което всеки валидатор проверява първо, а базиран на доставчик път, който да го презапише, би счупил съответствие с PAdES, без значение колко здрава е криптографията. HotPDF запазва подредбата идентична с вградения път за подписване, така че документ, подписан през PKCS#11 токен, се верифицира със същия код за верификация на подписи като подписан от PFX файл. За правилата на профила, които сядат над избора на алгоритъм, вижте ръководството за PAdES базови подписи в Delphi, а за ECDSA-специфичните капани на кодирането, които предхождат този модел с доставчици — записките за ECDSA CMS верификация и P1363 формати на подписи

Ред на миграция, който не оставя документите ви изостанали

Пост-квантовата готовност е проблем на графика, а не превключвател. Почти никой разгърнат PDF четец не валидира ML-DSA днес, така че документ, подписан само с него, от гледна точка на четеца е документ с неверифицируем подпис. Редът, който оцелява при контакт с реални архиви, е: запазете RSA или ECDSA като подписа, който валидатор ще съди, добавете декларацията за разширение и втори ML-DSA подпис там, където политика изисква квантово-устойчиво доказателство, и преместете първичния подпис само когато консумиращите системи са настигнали

Това, което HotPDF ви дава днес, е способността да записвате и верифицирате и двете, от същия код, с алгоритъма често записан във файла и в резултата от верификацията. HotPDF е нативен VCL PDF компонент за Delphi и C++Builder без външен PDF runtime, така че пъщите за подписване и верификация се доставят вътре във вашия изпълним файл, а не до него — вижте страницата на HotPDF Delphi PDF компонент за пълния списък с функции и пробно изтегляне