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

eIDAS trusted списъци и квалифицирани PDF подписи

Решението, че PDF подпис е квалифициран под eIDAS означава отговаряне на въпрос, който няма нищо общо с криптографията: беше ли сертификатът издаден от trust услуга, която една държава членка е изброила като квалифицирана, в момента, в който подписът е направен; Отговорът живее в trusted списък, XML документ, публикуван на територия, и цялата стойност на този документ зависи от неговата автентичност; Така че PDFium компонентът отказва да погледне вътре в един, докато някой не го поръчителства; TPdfEuropeanTrustedList.ParseAuthenticated подава пълните сурови байтове на доставен от извикващия IPdfTrustedListAuthenticator, преди да парсне дори една услуга и създава snapshot само ако този authenticator изрично мине

Поток автентикирай-предади-парсвай за европейски trusted списък в Delphi: суров XML от пресен fetch или кеширан snapshot минава през IPdfTrustedListAuthenticator, преди TPdfEuropeanTrustedList да парсне каквото и да е
Пресни и кеширани списъци срещат същия authenticator и snapshot съществува само след като той мине

Тази подредба е дизайнът; Всичко останало в тази функция следва от нея, включително частите, които изглеждат неудобни

Парснато не значи доверено

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 атаки: възпроизвеждане на по-стар списък, който все още изброява отдавна оттеглена услуга или размяна със списъка на друга територия, чиито услуги никога не сте имали намерение да доверявате

Инварианти на актуализация, налагани от PDFium trusted list компонента: непроменена територия, строго нарастващ номер на последователност и време на издаване, което никога не се движи назад, което заедно блокира 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; Системната верига доверие и квалифицираният статус отговарят на различни въпроси и доклад, който ги свива, не може да разграничи „доверено, но не квалифицирано“ от „квалифицирано, но веригата не валидира“, двете от които са реални и двете от които се нуждаят от различна обработка

PAdES оценка на квалифицирано доверие в Delphi: IPdfQualifiedTrustEvaluator съпоставя сертификати на услуги чрез точно DER сравнение и запълва QualifiedTrustStatus и QualifiedTrust, докато CertificateTrustStatus остава недокоснат
Веригата доверие и eIDAS квалифицираният статус се записват едно до друго, така че и двете находки остават видими
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