Решението, че PDF подпис е квалифициран под eIDAS означава отговаряне на въпрос, който няма нищо общо с криптографията: беше ли сертификатът издаден от trust услуга, която една държава членка е изброила като квалифицирана, в момента, в който подписът е направен; Отговорът живее в trusted списък, XML документ, публикуван на територия, и цялата стойност на този документ зависи от неговата автентичност; Така че PDFium компонентът отказва да погледне вътре в един, докато някой не го поръчителства; TPdfEuropeanTrustedList.ParseAuthenticated подава пълните сурови байтове на доставен от извикващия IPdfTrustedListAuthenticator, преди да парсне дори една услуга и създава snapshot само ако този authenticator изрично мине
Тази подредба е дизайнът; Всичко останало в тази функция следва от нея, включително частите, които изглеждат неудобни
Парснато не значи доверено
Trusted списък, който се парсва чисто, ви казва, че XML-ът е добре оформен; Той не ви казва нищо за кой го е написал; Понеже списъкът е това, върху което почива цялото ви решение за квалифициран статус, приемането на един, защото се е парсвал, би направило решението безсмислено: нападател, който може да замести списъка, може да обяви собствената си сертификатна авторитет за квалифицирана
Същото разсъждение важи за кеширането и това е капанът, който си заслужава да се назове; Snapshot кешът съхранява оригиналния XML заедно с SHA-256 дайджест и би било лесно да се третира съвпадащ дайджест при зареждане като доказателство, че списъкът е истински; Той не е; Дайджест, изчислен от същия процес, който е записал файла, без участието на ключ, верифицира само, че байтовете не са се променили, откакто сте ги записали; ако списъкът е бил фалшив, когато е кеширан, дайджестът потвърждава, че е същият фалшив списък; Така че зареждането на кеширан snapshot минава през същия authenticator като парсването на пресен; Цялостност и автентичност са различни свойства и само едното от тях се нуждае от ключ
uses
FPdfTrustedList;
type
TListAuthenticator = class(TInterfacedObject, IPdfTrustedListAuthenticator)
public
function Authenticate(const XmlData: TBytes;
out AuthenticationDetails: string): Boolean;
end;
function TListAuthenticator.Authenticate(const XmlData: TBytes;
out AuthenticationDetails: string): Boolean;
begin
// Вашата политика живее тук: верифицирайте XMLDSIG enveloped подписа
// срещу сертификата за подписване на списъка, който сте закачили
// извън лентата, и опишете какво сте проверили за audit следата
Result := VerifyEnvelopedXmlSignature(XmlData, FPinnedListSigner);
if Result then
AuthenticationDetails := 'XMLDSIG verified against pinned LOTL signer';
end;
var
List: TPdfEuropeanTrustedList;
Cache: TFileStream;
begin
List := TPdfEuropeanTrustedList.ParseAuthenticated(RawXml,
TListAuthenticator.Create, TPdfTrustedListOptions.Default);
// Snapshot съществува само защото authenticator-ът каза да
Cache := TFileStream.Create('tl-de.snapshot', fmCreate);
try
List.SaveCache(Cache);
finally
Cache.Free;
end;
end;
Валидаторът не притежава мрежова политика
PAdES валидатор няма работа да решава как да достигне списъка от trusted списъци, дали да мине през proxy, колко често да повтаря или какво да прави, когато територия е недостижима; Това са решения на приложение и deployment и в регулирани среди те често се одитират; Така че актуализациите пристигат през IPdfTrustedListSource, на който се подава URI и байтов таван и който връща байтове
Това, което компонентът наистина налага, са инвариантите, които правят една актуализация актуализация, а не заместване; Update изисква територията да е непроменена, номерът на последователност строго да нараства и времето на издаване да не се движи назад; Тези три проверки побеждават най-очевидните downgrade атаки: възпроизвеждане на по-стар списък, който все още изброява отдавна оттеглена услуга или размяна със списъка на друга територия, чиито услуги никога не сте имали намерение да доверявате
Лимити на парсера и изобщо без DTD
TPdfTrustedListOptions ограничава размера на XML, броя токени, дълбочината на влагане, броя услуги, броя сертификати и размера на отделен сертификат, като class функция Default предоставя употребляеми стойности; Trusted списъци са публикувани документи с предвидим размер, така че границите са евтини за задаване и няма легитимен списък, който трябва да ги надхвърли
Отделно и безусловно, парсерът отхвърля DTD и entity декларации; Това затваря и двата — отказ на услуга чрез разширяване на entities и маршрута за разкриване чрез външни entities — в един отказ и не струва нищо, защото trusted списъците не използват entities; Всеки XML парсер, достижим от ненадежден вход, трябва да бъде конфигуриран така; разликата тук е, че отказът не е конфигурируем, така че не може да бъде изключен от добронамерена промяна на опция
Квалифицираният статус се записва до веригата доверие, не се слива в нея
Страната на оценката е нарочно отделена; TPadesTrustValidationOptions.QualifiedTrustEvaluator приема IPdfQualifiedTrustEvaluator, който trusted-list snapshot-ът имплементира; По време на валидация evaluator-ът получава листовия сертификат, веригата и време за валидация, съпоставя сертификати на услуги чрез точно DER сравнение спрямо подписващия и веригата, съчетава статуса на услугата, идентификатора на типа услуга и qualifier URI-ите в този момент във времето и връща запис за оценка
Резултатът каца на две места върху всеки подпис: QualifiedTrustStatus като груб статус и QualifiedTrust като пълната оценка с територия, име на доставчик, име на услуга, тип идентификатор, статус и начално време на статуса; Това, което не прави, е да промени CertificateTrustStatus; Системната верига доверие и квалифицираният статус отговарят на различни въпроси и доклад, който ги свива, не може да разграничи „доверено, но не квалифицирано“ от „квалифицирано, но веригата не валидира“, двете от които са реални и двете от които се нуждаят от различна обработка
var
Options: TPadesTrustValidationOptions;
Report: TPadesValidationResult;
I: Integer;
begin
Options := TPadesTrustValidationOptions.Default;
Options.CheckRevocation := True;
Options.QualifiedTrustEvaluator := List; // автентикираният snapshot
Options.QualifiedValidationTime := SigningTime; // не Now
Report := Pdf.ValidatePadesTrust(Options);
for I := 0 to High(Report.Signatures) do
if Report.Signatures[I].QualifiedTrustStatus = pcsValid then
Writeln(Format('signature %d qualified by %s / %s (%s)',
[I, Report.Signatures[I].QualifiedTrust.Territory,
Report.Signatures[I].QualifiedTrust.ProviderName,
Report.Signatures[I].QualifiedTrust.ServiceName]))
else if Report.Signatures[I].QualifiedTrustStatus = pcsIndeterminate then
// Няма съответстваща услуга или snapshot-ът не може да отговори за това време
Writeln(Format('signature %d: qualified status undetermined', [I]));
end;
Защо времето за валидация не е сега
Защото квалификацията е свойство на момент; Trust услуга може да получи квалифициран статус, по-късно да го има отнет и още по-късно да бъде възстановена и всеки от тези преходи носи начално време в списъка; Подпис, направен, докато услугата е била квалифицирана, остава квалифициран след това; подпис, направен преди даването, не става квалифициран ретроактивно; Оценяването спрямо текущото време следователно дава грешния отговор и в двете посоки
Списъкът носи нужното за това: всеки запис за услуга има начално време на статус и флаг, разграничаващ исторически записи от текущи и evaluator-ът ги съчетава спрямо времето, което подавате; На практика това време идва от доверено времево клеймо върху подписа, а не от времето на подписване, заявено в CMS, което е причината дългосрочният validation материал да има значение дори за въпрос, който изглежда като policy запитване; страната на времевото клеймо и DSS е разгледана в статията за дългосрочни подписи
Какво все още трябва да изградите
Три неща и нито едно не принадлежи в PDF библиотека; Authenticator-ът, което значи истинска XMLDSIG верификация срещу сертификат за подписване на списъка, който сте получили през канал, на който вярвате; Политиката за fetch, което значи как и колко често опреснявате и какво прави вашето приложение, когато опресняване се провали; И териториалният обхват, което значи кои списъци носите изобщо, което е бизнес решение за в кои държави членки контрагентите ви подписват
Това, което получавате от компонента, е частта, която е лесна да се обърка тънко: подредба автентикирай-предади-парсвай, ограничен и без-entity XML парсинг, монотонни инварианти на актуализация, точно-DER съпоставяне на услуги, историческа оценка на статус и резултат, който остава отделен от обикновената верига доверие; Ако непосредственият ви проблем е по-базичен — че валидатор отхвърля подпис, който вярвате, че е добър — обичайните причини са каталогизирани в защо валидатори отхвърлят PAdES подписи, а повърхността за инспекция на подписи е описана в инспектиране на подписи и PAdES нива; Възможностите на компонента са изброени на продуктовата страница PDFium Delphi component