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
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
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
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