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

Валидиране на цифрови подписи в PDF в Delphi с HotPDF

HotPDF валидира цифрови подписи в заредени PDF документи чрез три метода на THotPDF: GetLoadedSignatureInfo, VerifyLoadedSignature и VerifyLoadedSignatureEx, въведени във v2.259.0; Компонентът извършва повторно хеширане на сегментите /ByteRange на оригиналния файл, проверява атрибута CMS messageDigest и изпълнява проверка на RSA PKCS#1 v1.5 спрямо вградения сертификат на подписалия, като връща svValid, когато байтовете на документа са непокътнати

Сценарият е прозаичен, а залогът — не; Друга страна връща подписан договор, вашият работен процес трябва да го архивира и някой задава единствения въпрос, който има значение: това ли е документът, който изпратихме, байт по байт, подписан от сертификата, който твърди? Отговорът на това в код е страната за проверка на историята с подписите; страната за подписване, изграждане и вграждане на PAdES подписи на първо място, е разгледана в съпътстващата статия за създаване на PAdES цифрови подписи с HotPDF; Тази статия е за другата посока: пристига вече подписан PDF и искате програмно решение, а не екранна снимка на зелената отметка на Acrobat

Как подписаният PDF доказва, че не е бил променян?

Подписът в PDF защитава определени байтови диапазони на файла, а не абстрактна идея за „документа“; ISO 32000-1 §12.8 дефинира механизма: текстовото поле за подпис носи речник, чийто запис /Contents съдържа контейнер CMS SignedData (RFC 5652), а неговият масив /ByteRange указва точните области на файла, които подписът обхваща, съгласно §12.8.1; Масивът е списък от двойки отместване и дължина, на практика два сегмента: всичко преди шестнадесетичния низ /Contents и всичко след него; Стойността на подписа не може да покрива сама себе си, така че файлът се хешира около тази празнина

Този дизайн има последица, която оформя целия API: проверката трябва да хешира оригиналните сериализирани байтове точно така, как се намират на диска; Анализираният модел на обекти е безполезен за това, тъй като повторното сериализиране дори на непроменен документ произвежда различни байтове; Затова HotPDF извършва проверка спрямо изходния файл, от който е зареден документът, или спрямо предоставен от вас TStream от необработени байтове, никога спрямо представянето му в паметта

Четене на метаданни за подписа преди всяка проверка

GetLoadedSignatureInfo анализира речника на подписа и неговия CMS контейнер, без да докосва дори байт от документа, което го прави правилното първо извикване, когато трябва само да покажете кой е подписал и кога; Полетата за подписи се индексират от 0 по реда на текстовите полета, а GetLoadedSignatureFieldCount ви казва колко съществуват; Върнатият запис THPDFSignatureInfo носи името на полето, /SubFilter, общото име (Common Name) на сертификата на подписалия, отличителните имена (DN) на субекта и издателя, серийния номер, датите на валидност, времето на подписване (от подписания атрибут, когато е наличен, в противен случай записа /M на речника), името на алгоритъма за дайджест и низовете /Reason, /Location и /ContactInfo; Неговият член Status остава svNotVerified — честен етикет за „анализиран, но непроверен“

var
  Pdf: THotPDF;
  Info: THPDFSignatureInfo;
  I: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.LoadFromFile('signed-contract.pdf');
    for I := 0 to Pdf.GetLoadedSignatureFieldCount - 1 do
    begin
      Info := Pdf.GetLoadedSignatureInfo(I);
      Writeln('Поле:     ', Info.FieldName);
      Writeln('Подписал:    ', Info.SignerName);
      Writeln('Издател:    ', Info.IssuerDN);
      Writeln('Алгоритъм: ', Info.HashAlgorithm);
      Writeln('Подфилтър: ', Info.SubFilter);
    end;
  finally
    Pdf.Free;
  end;
end;

Изпълнение на криптографската проверка

VerifyLoadedSignatureEx извършва пълната проверка за документ, зареден от файл, и връща попълнения запис с информация с едно извикване: отваря отново изходния файл, хешира сегментите /ByteRange с алгоритъма за дайджест SignerInfo, сравнява резултата с подписания атрибут messageDigest (RFC 5652 §5.4) и след това валидира с RSA подписа върху повторното DER SET кодиране на подписаните атрибути; Когато подписът не носи подписани атрибути, RSA проверката се изпълнява директно върху хеша на документа; Поддържаните подписи са RSA PKCS#1 v1.5 с SHA-1, SHA-256, SHA-384 или SHA-512 дайджести, което обхваща подфилтрите adbe.pkcs7.detached и ETSI.CAdES.detached, произвеждани от масовите инструменти за подписване

var
  Status: THPDFSignatureVerifyStatus;
  Info: THPDFSignatureInfo;
begin
  Status := Pdf.VerifyLoadedSignatureEx(0, Info);
  case Status of
    svValid:
      if Info.CoversWholeDocument then
        Writeln('Валиден; подписът обхваща целия файл')
      else
        Writeln('Валиден; файлът е разширен след подписването');
    svDigestMismatch:
      Writeln('Байтовете на документа са променени след подписването');
    svSignatureInvalid:
      Writeln('RSA проверката е неуспешна върху подписаните атрибути');
    svUnsupportedAlgorithm:
      Writeln('Не-RSA ключ или непознат алгоритъм за дайджест');
    svMalformed:
      Writeln('CMS контейнерът не може да бъде анализиран');
    svSourceUnavailable:
      Writeln('Няма изходни байтове; използвайте претоварването с TStream');
  end;
end;

Две подробности за имплементацията си струва да знаете, тъй като те обясняват неуспехи, които изглеждат мистериозни отстрани; Първо, проверката на подписаните атрибути е взискателна към кодирането: вътре във файла атрибутите са маркирани като [0] IMPLICIT, но подписът е изчислен върху тяхната форма DER SET OF, така че модулът за проверка слага нови маркери преди хеширане, точно както изисква RFC 5652 §5.4; Собственоръчно написан модул за проверка, който хешира байтовете така, както се появяват във файла, ще отхвърли всеки правилно подписан документ; Второ, /Contents по конвенция се запълва с нули до резервиран байтов лимит, така че модулът за проверка съкращава DER обекта до действителната дължина на външната му SEQUENCE преди анализ; нормално е да има изглеждащи като боклук нули накрая, а не повреда; Същата фамилия рискове при ASN.1 анализа от страната на импортиране на сертификати е предмет на статията за повишаване на сигурността на PKCS#12 и ASN.1 в HotPDF

Какво всъщност гарантира валидният подпис?

svValid означава точно следното: байтовете, посочени от /ByteRange, се хешира до стойността, която подписалият е подписал, и подписът се валидира под публичния ключ на сертификата, вграден в CMS контейнера; Това е целостта на байтовете плюс обвързването на ключа, и нищо повече; Валидирането на веригата от сертификати и доверието са изрично извън обхвата на модула за проверка на HotPDF: той не обхожда веригата до корен (root), не проверява за отнемане (revocation) и не се консултира с хранилище за доверие; Самоподписан сертификат от нападател, който е преподписал променен документ, ще се валидира като svValid, тъй като математиката е вътрешно съгласувана; Дали подписалият е този, за когото се представя, и дали някой трябва да му се довери, е политическо решение, което принадлежи на отделен слой — било то списък с разрешени сертификати на вашата организация, хранилището за сертификати на Windows или валидиращ орган

Флагът CoversWholeDocument предпазва от по-фина празнина; Подписът покрива единствено своя /ByteRange, а инкременталният механизъм за актуализация на PDF позволява добавяне на съдържание след подпис, без това да го анулира, което е по дизайн и е начинът, по който работят процесите с множество подписи; Флагът се изчислява по време на проверката и е верен само когато двата сегмента плюс празнината /Contents обхващат целия файл; Когато svValid пристигне с флаг CoversWholeDocument равен на false, подписаната ревизия е непокътната, но файлът съдържа по-късни допълнения и вашият работен процес трябва да реши дали да толерира промените, въведени с тях

Документите, заредени от поток или шифровани, се нуждаят от собствени изходни байтове

Методите без параметри VerifyLoadedSignature и VerifyLoadedSignatureEx зависят от това компонентът да помни от кой файл е зареден документът; Заредете документа от поток и няма да има име на файл, което да се отвори отново; същото важи и след пътя за повторно зареждане с парола, използван за шифровани документи, описан в статията за AES-256 PDF шифроване с HotPDF; И в двата случая методите, работещи с файлове на диска, връщат svSourceUnavailable, вместо да гадаят; Решението е претоварването с TStream, което ви позволява да предадете оригиналните необработени байтове от мястото, където ги съхранявате — файл, който все още имате, буфер в паметта или голям двоичен обект (blob) в база данни

var
  Src: TFileStream;
  Status: THPDFSignatureVerifyStatus;
  Info: THPDFSignatureInfo;
begin
  // Документ, зареден от поток: компонентът не съдържа изходно
  // име на файл, така че предоставете оригиналните байтове сами.
  Src := TFileStream.Create('signed-contract.pdf',
    fmOpenRead or fmShareDenyWrite);
  try
    Status := Pdf.VerifyLoadedSignature(0, Src, Info);
    if Status <> svValid then
      Writeln('Валидирането неуспешно: ', Ord(Status));
  finally
    Src.Free;
  end;
end;

Съобщаване за това, което не можете да валидирате

Модул за проверка, който знае само „валиден“ и „невалиден“, ще докладва погрешно документи, които просто не разбира, така че изброяването на състоянията разделя случаите, които вашият потребителски интерфейс трябва да разграничава; svDigestMismatch означава, че байтовете на документа са променени след подписването — класическият сигнал за подмяна; svSignatureInvalid означава, че байтовете се хешират правилно, но RSA проверката е неуспешна, което сочи към повредена или фалшифицирана стойност на подписа; svUnsupportedAlgorithm е честният отговор за ECDSA ключове и непознати дайджести: подписът може да бъде напълно добър, HotPDF просто не може да го провери и докладването му като „невалиден“ би навредило на здрав документ; svMalformed сигнализира за CMS контейнер, който не е могъл да бъде анализиран изобщо; За проверки от тип портал VerifyAllLoadedSignatures връща true само когато съществува поне едно поле за подпис и всяко едно от тях се валидира като svValid — удобна единствена булева стойност за конвейер за архивиране, който отхвърля всичко по-малко

Валидирането на подписи, подписването на PAdES, шифроването с AES-256 и API за редактиране на заредени документи се доставят в една и съща собствена VCL библиотека за Delphi и C++Builder, без външни DLL зависимости; пълният списък с функции и поддържаните версии на IDE са на страницата на продукта HotPDF Component