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