Articolo tecnico

Elenchi fidati eIDAS e firme PDF qualificate

Decidere che una firma PDF sia qualificata sotto eIDAS significa rispondere a una domanda che non c'entra nulla con la crittografia: il certificato è stato emesso da un servizio di fiducia che uno stato membro ha elencato come qualificato, al momento in cui la firma è stata fatta. La risposta vive in un elenco fidato, un documento XML pubblicato per territorio, e l'intero valore di quel documento dipende dalla sua autenticità. Così il componente PDFium rifiuta di guardare dentro finché qualcuno non lo abbia garantito. TPdfEuropeanTrustedList.ParseAuthenticated consegna i byte grezzi completi a un IPdfTrustedListAuthenticator fornito dal chiamante prima di interpretare anche un solo servizio, e crea uno snapshot solo se quell'autenticatore passa esplicitamente

Flusso autentica-prima-di-interpretare per un elenco fidato europeo in Delphi: l'XML grezzo da un prelievo fresco o da uno snapshot in cache passa attraverso IPdfTrustedListAuthenticator prima che TPdfEuropeanTrustedList interpreti qualcosa
Elenchi freschi e in cache incontrano lo stesso autenticatore, e uno snapshot esiste solo dopo che è passato

Quell'ordinamento è il progetto. Tutto il resto di questa funzione ne discende, comprese le parti che sembrano scomode

Interpretato non è fidato

Un elenco fidato che si interpreta pulitamente dice che l'XML è ben formato. Non dice nulla su chi lo abbia scritto. Poiché l'elenco è ciò su cui poggia l'intera decisione di stato qualificato, accettarne uno perché si è interpretato renderebbe la decisione priva di significato: un attaccante che possa sostituire l'elenco può dichiarare qualificata la propria certificate authority

Lo stesso ragionamento vale per la cache, e questa è la trappola che vale la pena nominare. La cache degli snapshot conserva l'XML originale insieme a un digest SHA-256, e sarebbe facile trattare un digest corrispondente al caricamento come prova che l'elenco sia genuino. Non lo è. Un digest calcolato dallo stesso processo che ha conservato il file, senza chiave coinvolta, verifica solo che i byte non siano cambiati da quando li ha scritti; se l'elenco era fraudolento quando è stato messo in cache, il digest conferma che è lo stesso elenco fraudolento. Così il caricamento di uno snapshot in cache passa attraverso lo stesso autenticatore dell'interpretazione di uno fresco. Integrità e autenticità sono proprietà diverse e solo una di esse ha bisogno di una chiave

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
  // La politica di Lei vive qui: verificare la firma enveloped XMLDSIG
  // contro il certificato di firma dell'elenco fissato fuori banda,
  // e descrivere ciò che si è controllato per la traccia di 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);
  // Lo snapshot esiste solo perché l'autenticatore ha detto sì
  Cache := TFileStream.Create('tl-de.snapshot', fmCreate);
  try
    List.SaveCache(Cache);
  finally
    Cache.Free;
  end;
end;

Il validatore non possiede la politica di rete

Un validatore PAdES non ha nulla a che fare con decidere come raggiungere la lista delle liste fidate, se passare per un proxy, quanto spesso riprovare, o cosa fare quando un territorio è irraggiungibile. Quelle sono decisioni di applicazione e deployment, e in ambienti regolamentati sono spesso auditate. Così gli aggiornamenti arrivano attraverso IPdfTrustedListSource, a cui viene consegnato un URI e un tetto di byte e che restituisce byte

Ciò che il componente fa rispettare sono gli invarianti che fanno di un aggiornamento un aggiornamento anziché una sostituzione. Update richiede che il territorio sia invariato, che il numero di sequenza aumenti strettamente e che l'ora di emissione non indietreggi. Quei tre controlli sconfiggono gli attacchi di downgrade più evidenti: riprodurre un elenco più vecchio che elenca ancora un servizio nel frattempo ritirato, o sostituire con l'elenco di un altro territorio i cui servizi non si intendeva fidare

Invarianti di aggiornamento fatte rispettare dal componente elenco fidato di PDFium: territorio invariato, numero di sequenza strettamente crescente e ora di emissione che non indietreggia mai, che insieme bloccano gli attacchi di downgrade
Tre controlli monotoni separano un aggiornamento genuino da un elenco riprodotto o sostituito

Limiti del parser, e niente DTD affatto

TPdfTrustedListOptions pone tetti alla dimensione dell'XML, al numero di token, alla profondità di annidamento, al numero di servizi, al numero di certificati e alla dimensione di un singolo certificato, con una funzione di classe Default che fornisce valori utilizzabili. Gli elenchi fidati sono documenti pubblicati di dimensione prevedibile, quindi i limiti sono economici da fissare e non esiste elenco legittimo che debba superarli

Separatamente e incondizionatamente, il parser rifiuta dichiarazioni DTD ed entità. Questo chiude sia la negazione di servizio da espansione di entità sia la via di divulgazione da entità esterne in un solo rifiuto, e non costa nulla perché gli elenchi fidati non usano entità. Qualsiasi parser XML raggiungibile da input non fidato dovrebbe essere configurato così; la differenza qui è che il rifiuto non è configurabile, quindi non può essere spento da una modifica di opzione ben intenzionata

Lo stato qualificato è registrato accanto alla fiducia della catena, non fuso con essa

Il lato valutazione è deliberatamente separato. TPadesTrustValidationOptions.QualifiedTrustEvaluator accetta un IPdfQualifiedTrustEvaluator, che lo snapshot dell'elenco fidato implementa. Durante la validazione il valutatore riceve il certificato foglia, la catena e un tempo di validazione, fa corrispondere i certificati di servizio per confronto DER esatto contro il firmatario e la catena, combina lo stato del servizio, l'identificatore del tipo di servizio e gli URI qualificatore in quel punto nel tempo, e restituisce un record di valutazione

Il risultato atterra in due posti su ogni firma: QualifiedTrustStatus come stato grossolano, e QualifiedTrust come valutazione completa con territorio, nome del provider, nome del servizio, identificatore del tipo, stato e ora di inizio dello stato. Ciò che non fa è cambiare CertificateTrustStatus. La fiducia della catena di sistema e lo stato qualificato rispondono a domande diverse, e un report che le fonde non può distinguere "fidato ma non qualificato" da "qualificato ma la catena non valida", entrambi reali ed entrambi bisognosi di gestione diversa

Valutazione della fiducia qualificata PAdES in Delphi: l'IPdfQualifiedTrustEvaluator fa corrispondere i certificati di servizio per confronto DER esatto e riempie QualifiedTrustStatus e QualifiedTrust mentre CertificateTrustStatus resta intoccato
La fiducia della catena e lo stato qualificato eIDAS sono registrati fianco a fianco così entrambi i riscontri restano visibili
var
  Options: TPadesTrustValidationOptions;
  Report: TPadesValidationResult;
  I: Integer;
begin
  Options := TPadesTrustValidationOptions.Default;
  Options.CheckRevocation := True;
  Options.QualifiedTrustEvaluator := List;      // lo snapshot autenticato
  Options.QualifiedValidationTime := SigningTime; // non 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
      // Nessun servizio corrispondente, o lo snapshot non può rispondere per questo tempo
      Writeln(Format('signature %d: qualified status undetermined', [I]));
end;

Perché il tempo di validazione non è adesso

Perché la qualificazione è una proprietà di un momento. Un servizio di fiducia può ricevere lo stato qualificato, vederlo in seguito ritirato, e in seguito ancora essere reintegrato, e ciascuna di quelle transizioni porta un'ora di inizio nell'elenco. Una firma fatta mentre il servizio era qualificato resta qualificata in seguito; una firma fatta prima del riconoscimento non diventa qualificata retroattivamente. Valutare rispetto all'ora corrente dà quindi la risposta sbagliata in entrambe le direzioni

L'elenco trasporta ciò che serve: ogni record di servizio ha un'ora di inizio dello stato e un flag che distingue le voci storiche da quelle correnti, e il valutatore le combina rispetto al tempo che Lei fornisce. In pratica quel tempo viene da un timestamp fidato sulla firma anziché dall'ora di firma dichiarata nel CMS, ed è per questo che il materiale di validazione a lungo termine conta anche per una domanda che sembra una consultazione di politica; il lato timestamp e DSS è trattato nell'articolo sulle firme a lungo termine

Cosa occorre ancora costruire

Tre cose, e nessuna appartiene a una libreria PDF. L'autenticatore, cioè la vera verifica XMLDSIG contro un certificato di firma dell'elenco ottenuto attraverso un canale di cui ci si fida. La politica di prelievo, cioè come e quanto spesso si aggiorni, e cosa faccia l'applicazione quando un aggiornamento fallisce. E l'ambito territoriale, cioè quali elenchi si trasportino affatto, che è una decisione di business su in quali stati membri firmino le controparti

Ciò che si ottiene dal componente è la parte facile da sbagliare in modo sottile: l'ordinamento autentica-prima-di-interpretare, il parsing XML limitato e senza entità, gli invarianti di aggiornamento monotoni, la corrispondenza dei servizi per DER esatto, la valutazione storica dello stato, e un risultato che resta separato dalla fiducia ordinaria della catena. Se il problema immediato è più elementare, che un validatore rifiuti una firma che si crede a posto, le cause usuali sono catalogate in perché i validatori rifiutano le firme PAdES, e la superficie di ispezione delle firme è descritta in ispezione delle firme e livelli PAdES. Le capacità del componente sono elencate nella pagina di prodotto di PDFium Delphi component