Artículo técnico

Listas de confianza eIDAS y firmas PDF cualificadas

Decidir que una firma PDF es cualificada bajo eIDAS significa responder una pregunta que no tiene nada que ver con la criptografía: si el certificado fue emitido por un servicio de confianza que un estado miembro listó como cualificado en el momento en que se hizo la firma. La respuesta vive en una lista de confianza, un documento XML publicado por territorio, y todo el valor de ese documento depende de su autenticidad. Por eso el componente PDFium se niega a mirar dentro de una hasta que alguien la avale. TPdfEuropeanTrustedList.ParseAuthenticated entrega los bytes crudos completos a un IPdfTrustedListAuthenticator suministrado por quien llama antes de analizar un solo servicio, y crea una instantánea solo si ese autenticador aprueba explícitamente

Flujo de autenticar antes de analizar para una lista de confianza europea en Delphi: el XML crudo de una obtención fresca o de una instantánea cacheada pasa por IPdfTrustedListAuthenticator antes de que TPdfEuropeanTrustedList analice nada
Las listas frescas y cacheadas pasan por el mismo autenticador, y una instantánea existe solo después de que aprueba

Ese orden es el diseño. Todo lo demás en esta función se deriva de él, incluidas las partes que se ven incómodas

Analizada no es confiable

Una lista de confianza que analiza limpiamente les dice que el XML está bien formado. No les dice nada sobre quién la escribió. Como la lista es aquello sobre lo que descansa toda su decisión de estado cualificado, aceptar una porque analizó bien volvería sin sentido la decisión: un atacante que pueda sustituir la lista puede declarar cualificada a su propia autoridad certificadora

El mismo razonamiento aplica al caché, y esta es la trampa que vale nombrar. El caché de instantáneas guarda el XML original junto con un resumen SHA-256, y sería fácil tratar un resumen coincidente al cargar como prueba de que la lista es genuina. No lo es. Un resumen calculado por el mismo proceso que guardó el archivo, sin clave involucrada, verifica solo que los bytes no cambiaron desde que ustedes los escribieron; si la lista era fraudulenta cuando se cacheó, el resumen confirma que es la misma lista fraudulenta. Así que cargar una instantánea cacheada pasa por el mismo autenticador que analizar una fresca. Integridad y autenticidad son propiedades distintas y solo una de ellas necesita una clave

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
  // Su política vive aquí: verificar la firma envuelta XMLDSIG
  // contra el certificado firmante de la lista que fijaron fuera de banda,
  // y describir qué comprobaron para la pista de auditoría
  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);
  // La instantánea existe solo porque el autenticador dijo que sí
  Cache := TFileStream.Create('tl-de.snapshot', fmCreate);
  try
    List.SaveCache(Cache);
  finally
    Cache.Free;
  end;
end;

El validador no es dueño de la política de red

Un validador PAdES no tiene por qué decidir cómo alcanzar la lista de listas de confianza, si pasar por un proxy, con qué frecuencia reintentar, o qué hacer cuando un territorio es inalcanzable. Esas son decisiones de aplicación y de despliegue, y en entornos regulados se auditan con frecuencia. Así que las actualizaciones llegan a través de IPdfTrustedListSource, que recibe una URI y un tope de bytes y devuelve bytes

Lo que el componente sí aplica son los invariantes que hacen de una actualización una actualización y no una sustitución. Update exige que el territorio no cambie, que el número de secuencia aumente estrictamente y que la hora de emisión no retroceda. Esas tres comprobaciones derrotan los ataques de degradación más obvios: reproducir una lista más antigua que todavía lista un servicio ya retirado, o meter la lista de otro territorio cuyos servicios nunca intentaron confiar

Invariantes de actualización que aplica el componente de listas de confianza de PDFium: territorio sin cambios, número de secuencia estrictamente creciente y una hora de emisión que nunca retrocede, que en conjunto bloquean ataques de degradación
Tres comprobaciones monótonas separan una actualización genuina de una lista reproducida o sustituida

Límites del analizador y nada de DTD

TPdfTrustedListOptions acota el tamaño del XML, el recuento de tokens, la profundidad de anidamiento, el número de servicios, el número de certificados y el tamaño de un certificado individual, con una función de clase Default que proporciona valores utilizables. Las listas de confianza son documentos publicados de tamaño previsible, así que los límites son baratos de fijar y no hay lista legítima que necesite superarlos

Por separado e incondicionalmente, el analizador rechaza las declaraciones de DTD y de entidades. Eso cierra tanto la denegación de servicio por expansión de entidades como la vía de divulgación por entidades externas en un solo rechazo, y no cuesta nada porque las listas de confianza no usan entidades. Cualquier analizador XML alcanzable desde entrada no confiable debería configurarse así; la diferencia aquí es que el rechazo no es configurable, así que no puede apagarse con un cambio de opción bien intencionado

El estado cualificado se registra junto a la confianza de cadena, no se funde con ella

El lado de la evaluación es deliberadamente separado. TPadesTrustValidationOptions.QualifiedTrustEvaluator recibe un IPdfQualifiedTrustEvaluator, que la instantánea de la lista de confianza implementa. Durante la validación el evaluador recibe el certificado hoja, la cadena y una hora de validación, iguala los certificados de servicio por comparación exacta de DER contra el firmante y la cadena, combina el estado del servicio, el identificador de tipo de servicio y las URI de calificadores en ese punto del tiempo, y devuelve un record de evaluación

El resultado aterriza en dos lugares de cada firma: QualifiedTrustStatus como estado grueso, y QualifiedTrust como la evaluación completa con territorio, nombre del proveedor, nombre del servicio, identificador de tipo, estado y hora de inicio del estado. Lo que no hace es cambiar CertificateTrustStatus. La confianza de cadena del sistema y el estado cualificado responden preguntas distintas, y un reporte que las colapsa no puede distinguir "confiable pero no cualificado" de "cualificado pero la cadena no valida", ambos reales y ambos con manejo diferente

Evaluación de confianza cualificada PAdES en Delphi: el IPdfQualifiedTrustEvaluator iguala certificados de servicio por comparación exacta de DER y llena QualifiedTrustStatus y QualifiedTrust mientras CertificateTrustStatus queda intacto
La confianza de cadena y el estado cualificado eIDAS se registran lado a lado para que ambos hallazgos sigan visibles
var
  Options: TPadesTrustValidationOptions;
  Report: TPadesValidationResult;
  I: Integer;
begin
  Options := TPadesTrustValidationOptions.Default;
  Options.CheckRevocation := True;
  Options.QualifiedTrustEvaluator := List;      // la instantánea autenticada
  Options.QualifiedValidationTime := SigningTime; // no 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
      // Sin servicio coincidente, o la instantánea no puede responder por esta hora
      Writeln(Format('signature %d: qualified status undetermined', [I]));
end;

Por qué la hora de validación no es ahora

Porque la cualificación es una propiedad de un momento. A un servicio de confianza se le puede conceder el estado cualificado, después retirárselo y después aún rehabilitarlo, y cada una de esas transiciones lleva una hora de inicio en la lista. Una firma hecha mientras el servicio era cualificado sigue cualificada después; una firma hecha antes de la concesión no se vuelve cualificada retroactivamente. Evaluar contra la hora actual da entonces la respuesta equivocada en ambas direcciones

La lista lleva lo necesario para esto: cada record de servicio tiene una hora de inicio de estado y una bandera que distingue las entradas históricas de las actuales, y el evaluador las combina contra la hora que ustedes suministran. En la práctica esa hora viene de una marca de tiempo confiable sobre la firma y no de la hora de firma declarada en el CMS, que es por lo que el material de validación a largo plazo importa incluso para una pregunta que parece una consulta de política; el lado de marca de tiempo y DSS se cubre en el artículo de firmas a largo plazo

Lo que todavía tienen que construir

Tres cosas, y ninguna pertenece a una biblioteca PDF. El autenticador, es decir, la verificación XMLDSIG real contra un certificado firmante de lista que obtuvieron por un canal de confianza. La política de obtención, es decir, cómo y con qué frecuencia refrescan, y qué hace su aplicación cuando un refresco falla. Y el alcance territorial, es decir, qué listas llevan en absoluto, que es una decisión de negocio sobre en qué estados miembros firman sus contrapartes

Lo que reciben del componente es la parte que es fácil hacer sutilmente mal: el orden de autenticar antes de analizar, el análisis XML acotado y libre de entidades, los invariantes de actualización monótonos, la igualación de servicios por DER exacto, la evaluación de estado histórico y un resultado que se mantiene separado de la confianza de cadena ordinaria. Si su problema inmediato es más básico, que un validador rechaza una firma que ustedes creen correcta, las causas usuales están catalogadas en por qué los validadores rechazan firmas PAdES, y la superficie de inspección de firmas se describe en inspección de firmas y niveles PAdES. Las capacidades del componente están en la página del producto PDFium Delphi component